/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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.
 */
package org.apache.geode.test.junit.rules;

import static org.mockito.Mockito.spy;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.apache.geode.internal.ClassPathLoader;
import org.apache.geode.management.cli.CliMetaData;
import org.apache.geode.management.cli.Result;
import org.apache.geode.management.internal.cli.CliAroundInterceptor;
import org.apache.geode.management.internal.cli.result.ResultBuilder;
import org.junit.rules.ExternalResource;
import org.springframework.shell.core.Completion;
import org.springframework.shell.core.Converter;
import org.springframework.shell.event.ParseResult;
import org.springframework.util.ReflectionUtils;

import org.apache.geode.management.internal.cli.CommandManager;
import org.apache.geode.management.internal.cli.GfshParseResult;
import org.apache.geode.management.internal.cli.GfshParser;
import org.apache.geode.management.internal.cli.result.CommandResult;

public class GfshParserRule extends ExternalResource {

  private GfshParser parser;
  private CommandManager commandManager;

  @Override
  public void before() {
    commandManager = new CommandManager();
    parser = new GfshParser(commandManager);
  }

  public GfshParseResult parse(String command) {
    return parser.parse(command);
  }

  public <T> CommandResult executeCommandWithInstance(T instance, String command) {
    GfshParseResult parseResult = parse(command);

    CliAroundInterceptor interceptor = null;
    String interceptorClass =
        parseResult.getMethod().getAnnotation(CliMetaData.class).interceptor();
    if (!CliMetaData.ANNOTATION_NULL_VALUE.equals(interceptorClass)) {
      try {
        interceptor = (CliAroundInterceptor) ClassPathLoader.getLatest().forName(interceptorClass)
            .newInstance();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }

      Result preExecResult = interceptor.preExecution(parseResult);
      if (Result.Status.ERROR.equals(preExecResult.getStatus())) {
        return (CommandResult) preExecResult;
      }
    }

    return (CommandResult) ReflectionUtils.invokeMethod(parseResult.getMethod(), instance,
        parseResult.getArguments());
  }

  public CommandCandidate complete(String command) {
    List<Completion> candidates = new ArrayList<>();
    int cursor = parser.completeAdvanced(command, command.length(), candidates);
    return new CommandCandidate(command, cursor, candidates);
  }

  public <T extends Converter> T spyConverter(Class<T> klass) {
    Set<Converter<?>> converters = parser.getConverters();
    T foundConverter = null, spy = null;
    for (Converter converter : converters) {
      if (klass.isAssignableFrom(converter.getClass())) {
        foundConverter = (T) converter;
        break;
      }
    }
    if (foundConverter != null) {
      parser.remove(foundConverter);
      spy = spy(foundConverter);
      parser.add(spy);
    }
    return spy;
  }

  public GfshParser getParser() {
    return parser;
  }

  public CommandManager getCommandManager() {
    return commandManager;
  }

  public static class CommandCandidate {
    private String command;
    private int cursor;
    private List<Completion> candidates;

    public CommandCandidate(String command, int cursor, List<Completion> candidates) {
      this.command = command;
      this.cursor = cursor;
      this.candidates = candidates;
    }

    public String getCandidate(int index) {
      return command.substring(0, cursor) + candidates.get(index).getValue();
    }

    public String getFirstCandidate() {
      return getCandidate(0);
    }

    public int size() {
      return candidates.size();
    }

    public int getCursor() {
      return cursor;
    }

    public List<Completion> getCandidates() {
      return candidates;
    }
  }
}

@@ -871,82 +871,8 @@
 y;%0A%0A
-import java.util.ArrayList;%0Aimport java.util.List;%0Aimport java.util.Set;%0A%0A
 impo
@@ -1138,271 +1138,148 @@
 cli.
-result.ResultBuilder;%0Aimport org.junit.rules.ExternalResource;%0Aimport org.springframework.shell.core.Completion;%0Aimport org.springframework.shell.core.Converter;%0Aimport org.springframework.shell.event.ParseResult;%0Aimport org.springframework.util.ReflectionUtils;%0A
+CommandManager;%0Aimport org.apache.geode.management.internal.cli.GfshParseResult;%0Aimport org.apache.geode.management.internal.cli.GfshParser;
 %0Aimp
@@ -1319,38 +1319,44 @@
 nternal.cli.
-CommandManager
+result.CommandResult
 ;%0Aimport org
@@ -1397,152 +1397,290 @@
 cli.
-GfshParseResult;%0Aimport org.apache.geode.management.internal.cli.GfshParser;%0Aimport org.apache.geode.management.internal.cli.result.CommandResul
+result.ResultBuilder;%0Aimport org.junit.rules.ExternalResource;%0Aimport org.springframework.shell.core.Completion;%0Aimport org.springframework.shell.core.Converter;%0Aimport org.springframework.util.ReflectionUtils;%0A%0Aimport java.util.ArrayList;%0Aimport java.util.List;%0Aimport java.util.Se
 t;%0A%0A
@@ -2159,16 +2159,135 @@
 mand);%0A%0A
+    if (parseResult == null) %7B%0A      return ResultBuilder.createUserErrorResult(%22Invalid command: %22 + command);%0A    %7D%0A%0A
     CliA
@@ -2331,41 +2331,33 @@
     
-String interceptorClass =%0A       
+CliMetaData cliMetaData =
  par
@@ -2409,16 +2409,93 @@
 a.class)
+;%0A%0A    if (cliMetaData != null) %7B%0A      String interceptorClass = cliMetaData
 .interce
@@ -2498,24 +2498,26 @@
 erceptor();%0A
+  
     if (!Cli
@@ -2585,14 +2585,18 @@
     
+  
 try %7B%0A
+  
     
@@ -2702,16 +2702,18 @@
         
+  
 .newInst
@@ -2716,24 +2716,26 @@
 Instance();%0A
+  
       %7D catc
@@ -2748,24 +2748,26 @@
 eption e) %7B%0A
+  
         thro
@@ -2795,27 +2795,31 @@
 n(e);%0A      
+  
 %7D%0A%0A
+  
       Result
@@ -2874,16 +2874,18 @@
 esult);%0A
+  
       if
@@ -2947,24 +2947,26 @@
 ) %7B%0A        
+  
 return (Comm
@@ -2991,16 +2991,26 @@
 Result;%0A
+        %7D%0A
       %7D%0A

Add null guards in the case of invalid commands