/*
 * Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You
 * may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License. See accompanying
 * LICENSE file.
 */
package com.gemstone.gemfire.management.internal.cli;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.Completion;
import org.springframework.shell.core.Converter;
import org.springframework.shell.core.MethodTarget;
import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import com.gemstone.gemfire.management.cli.Result;
import com.gemstone.gemfire.management.internal.cli.annotation.CliArgument;
import com.gemstone.gemfire.management.cli.CliMetaData;
import com.gemstone.gemfire.management.cli.ConverterHint;
import com.gemstone.gemfire.management.internal.cli.i18n.CliStrings;
import com.gemstone.gemfire.management.internal.cli.parser.Argument;
import com.gemstone.gemfire.management.internal.cli.parser.AvailabilityTarget;
import com.gemstone.gemfire.management.internal.cli.parser.CommandTarget;
import com.gemstone.gemfire.management.internal.cli.parser.Option;

/**
 * CommandManagerTest - Includes tests to check the CommandManager functions
 * 
 * @author apande
 */

public class CommandManagerJUnitTest extends TestCase {
  private static final String COMMAND1_NAME = "command1";
  private static final String COMMAND1_NAME_ALIAS = "command1_alias";
  private static final String COMMAND2_NAME = "c2";

  private static final String COMMAND1_HELP = "help for " + COMMAND1_NAME;
  // ARGUMENTS
  private static final String ARGUMENT1_NAME = "argument1";
  private static final String ARGUMENT1_HELP = "help for argument1";
  private static final String ARGUMENT1_CONTEXT = "context for argument 1";
  private static final Completion[] ARGUMENT1_COMPLETIONS = {
      new Completion("arg1"), new Completion("arg1alt") };
  private static final String ARGUEMNT2_NAME = "argument2";
  private static final String ARGUMENT2_CONTEXT = "context for argument 2";
  private static final String ARGUMENT2_HELP = "help for argument2";
  private static final String ARGUMENT2_UNSPECIFIED_DEFAULT_VALUE = "{unspecified default value for argument2}";
  private static final Completion[] ARGUMENT2_COMPLETIONS = {
      new Completion("arg2"), new Completion("arg2alt") };

  // OPTIONS
  private static final String OPTION1_NAME = "option1";
  private static final String OPTION1_SYNONYM = "opt1";
  private static final String OPTION1_HELP = "help for option1";
  private static final String OPTION1_CONTEXT = "context for option1";
  private static final String OPTION1_SPECIFIED_DEFAULT_VALUE = "{specified default value for option1}";
  private static final Completion[] OPTION1_COMPLETIONS = {
      new Completion("option1"), new Completion("option1Alternate") };
  private static final String OPTION2_NAME = "option2";
  private static final String OPTION2_HELP = "help for option2";
  private static final String OPTION2_CONTEXT = "context for option2";
  private static final String OPTION2_SPECIFIED_DEFAULT_VALUE = "{specified default value for option2}";
  private static final String OPTION3_NAME = "option3";
  private static final String OPTION3_SYNONYM = "opt3";
  private static final String OPTION3_HELP = "help for option3";
  private static final String OPTION3_CONTEXT = "context for option3";
  private static final String OPTION3_SPECIFIED_DEFAULT_VALUE = "{specified default value for option3}";
  private static final String OPTION3_UNSPECIFIED_DEFAULT_VALUE = "{unspecified default value for option3}";

  // tests loadCommands()
  public void testCommandManagerLoadCommands() throws Exception {

    CommandManager commandManager = null;
    try {
      commandManager = CommandManager.getInstance(true);
    } catch (Exception e) {
      throw e;
    }
    assertNotNull(commandManager);
    assertNotSame(0, commandManager.getCommands().size());

  }

  // tests commandManagerInstance method
  public void testCommandManagerInstance() throws Exception {

    CommandManager commandManager = null;
    try {
      commandManager = CommandManager.getInstance(true);
    } catch (Exception e) {
      throw e;
    }
    assertNotNull(commandManager);
  }

  // tests createOption method for creating option
  public void testCommandManagerCreateOption() throws Exception {
    CommandManager commandManager = null;
    try {
      commandManager = CommandManager.getInstance(true);
    } catch (Exception e) {
      throw e;
    }
    assertNotNull(commandManager);

    Method method = Commands.class.getMethod(COMMAND1_NAME, String.class,
        String.class, String.class, String.class, String.class);

    Annotation[][] annotations = method.getParameterAnnotations();
    Class<?>[] parameterTypes = method.getParameterTypes();
    List<String> optionNames = new ArrayList<String>();
    optionNames.add(OPTION1_NAME);
    optionNames.add(OPTION2_NAME);
    optionNames.add(OPTION3_NAME);

    int parameterNo = 0;
    for (int i = 0; i < annotations.length; i++) {
      Annotation[] annotation = annotations[i];
      for (Annotation ann : annotation) {
        if (ann instanceof CliOption) {
          Option createdOption = commandManager.createOption((CliOption) ann,
              parameterTypes[i], parameterNo);
          assertTrue(optionNames.contains(createdOption.getLongOption()));          
          assertEquals(((CliOption) ann).help(), createdOption.getHelp());          
          if (((CliOption) ann).specifiedDefaultValue() != null
              && ((CliOption) ann).specifiedDefaultValue().length() > 0) {            
            assertEquals(((CliOption) ann).specifiedDefaultValue().trim(),
                createdOption.getSpecifiedDefaultValue().trim());
          }
          if (((CliOption) ann).unspecifiedDefaultValue() != null
              && ((CliOption) ann).unspecifiedDefaultValue().length() > 0) {           
            assertEquals(((CliOption) ann).specifiedDefaultValue().trim(),
                createdOption.getSpecifiedDefaultValue().trim());
          }

        }
      }
    }
  }

  // tests createArgument method for creating argument
  public void testCommandManagerCreateArgument() throws Exception {
    CommandManager commandManager = null;
    try {
      commandManager = CommandManager.getInstance(true);
    } catch (Exception e) {
      throw e;
    }
    assertNotNull(commandManager);

    Method method = Commands.class.getMethod(COMMAND1_NAME, String.class,
        String.class, String.class, String.class, String.class);

    Annotation[][] annotations = method.getParameterAnnotations();
    Class<?>[] parameterTypes = method.getParameterTypes();
    List<String> argumentList = new ArrayList<String>();
    argumentList.add(ARGUMENT1_NAME);
    argumentList.add(ARGUEMNT2_NAME);

    int parameterNo = 0;
    for (int i = 0; i < annotations.length; i++) {
      Annotation[] annotation = annotations[i];
      for (Annotation ann : annotation) {
        if (ann instanceof CliArgument) {
          Argument arg = commandManager.createArgument((CliArgument) ann,
              parameterTypes[i], parameterNo);
          assertEquals(true, argumentList.contains(arg.getArgumentName()));
          assertEquals(((CliArgument) ann).mandatory(), arg.isRequired());
          assertEquals(((CliArgument) ann).name().trim(), arg.getArgumentName()
              .trim());
          assertEquals(((CliArgument) ann).argumentContext().trim(), arg
              .getContext().trim());
          assertEquals(((CliArgument) ann).help().trim(), arg.getHelp().trim());
        }
      }
    }
  }

  // tests availabilityIndicator for a method
  public void testCommandManagerAvailabilityIndicator() throws Exception {

    try {

      CommandManager commandManager = null;
      try {
        commandManager = CommandManager.getInstance(true);
      } catch (Exception e) {
        throw e;
      }
      assertNotNull(commandManager);
      commandManager.add(Commands.class.newInstance());
      Map<String, CommandTarget> commands = commandManager.getCommands();
      for (String commandName : commands.keySet()) {
        if (commandName.equals(COMMAND1_NAME)) {
          CommandTarget commandTarget = commands.get(commandName);
          AvailabilityTarget availabilityIndicator = commandTarget
              .getAvailabilityIndicator();
          if (availabilityIndicator == null) {
            availabilityIndicator = commandManager
                .getAvailabilityIndicator(COMMAND1_NAME);
            commandTarget.setAvailabilityIndicator(availabilityIndicator);
          }
          assertEquals(true, commandTarget.isAvailable());
          break;
        }
      }
    } catch (Exception e) {
      fail("Exception in testCommandManagerAvailabilityIndicator is "
          + e.getMessage());
    }
  }

  // class that represents dummy commands
  static public class Commands implements CommandMarker {
    @CliCommand(value = { COMMAND1_NAME, COMMAND1_NAME_ALIAS }, help = COMMAND1_HELP)
    @CliMetaData(shellOnly = true, relatedTopic = { "relatedTopicOfCommand1" })
    public static String command1(
        @CliArgument(name = ARGUMENT1_NAME, argumentContext = ARGUMENT1_CONTEXT, help = ARGUMENT1_HELP, mandatory = true)
        String argument1,
        @CliArgument(name = ARGUEMNT2_NAME, argumentContext = ARGUMENT2_CONTEXT, help = ARGUMENT2_HELP, mandatory = false, unspecifiedDefaultValue = ARGUMENT2_UNSPECIFIED_DEFAULT_VALUE, systemProvided = false)
        String argument2,
        @CliOption(key = { OPTION1_NAME, OPTION1_SYNONYM }, help = OPTION1_HELP, mandatory = true, optionContext = OPTION1_CONTEXT, specifiedDefaultValue = OPTION1_SPECIFIED_DEFAULT_VALUE)
        String option1,
        @CliOption(key = { OPTION2_NAME }, help = OPTION2_HELP, mandatory = false, optionContext = OPTION2_CONTEXT, specifiedDefaultValue = OPTION2_SPECIFIED_DEFAULT_VALUE)
        String option2,
        @CliOption(key = { OPTION3_NAME, OPTION3_SYNONYM }, help = OPTION3_HELP, mandatory = false, optionContext = OPTION3_CONTEXT, unspecifiedDefaultValue = OPTION3_UNSPECIFIED_DEFAULT_VALUE, specifiedDefaultValue = OPTION3_SPECIFIED_DEFAULT_VALUE)
        String option3) {
      return null;
    }

    @CliCommand(value = { COMMAND2_NAME })
    public static String command2() {
      return null;
    }

    @CliCommand(value = { "testParamConcat" })
    public static Result testParamConcat(
        @CliOption(key = { "string" })
        String string,
        @CliOption(key = { "stringArray" })
        @CliMetaData(valueSeparator = ",")
        String[] stringArray,
        @CliOption(key = { "stringList" }, optionContext = ConverterHint.STRING_LIST)
        @CliMetaData(valueSeparator = ",")
        List<String> stringList, @CliOption(key = { "integer" })
        Integer integer, @CliOption(key = { "colonArray" })
        @CliMetaData(valueSeparator = ":")
        String[] colonArray) {
      return null;
    }

    @CliCommand(value = { "testMultiWordArg" })
    public static Result testMultiWordArg(@CliArgument(name = "arg1")
    String arg1, @CliArgument(name = "arg2")
    String arg2) {
      return null;
    }

    @CliAvailabilityIndicator({ COMMAND1_NAME })
    public boolean isAvailable() {
      return true; // always available on server

    }

  }

  static class SimpleConverter implements Converter<String> {

    public boolean supports(Class<?> type, String optionContext) {
      if (type.isAssignableFrom(String.class)) {
        return true;
      }
      return false;
    }

    public String convertFromText(String value, Class<?> targetType,
        String optionContext) {
      return value;
    }

    public boolean getAllPossibleValues(List<Completion> completions,
        Class<?> targetType, String existingData, String context,
        MethodTarget target) {
      if (context.equals(ARGUMENT1_CONTEXT)) {
        for (Completion completion : ARGUMENT1_COMPLETIONS) {
          completions.add(completion);
        }
      } else if (context.equals(ARGUMENT2_CONTEXT)) {
        for (Completion completion : ARGUMENT2_COMPLETIONS) {
          completions.add(completion);
        }
      } else if (context.equals(OPTION1_CONTEXT)) {
        for (Completion completion : OPTION1_COMPLETIONS) {
          completions.add(completion);
        }
      }
      return true;
    }
  }

}