/*
 * 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.activemq.artemis.cli;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

import org.apache.activemq.artemis.cli.commands.Connect;
import org.apache.activemq.artemis.cli.commands.messages.ConnectionAbstract;
import org.jline.console.SystemRegistry;
import org.jline.console.impl.SystemRegistryImpl;
import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.MaskingCallback;
import org.jline.reader.Parser;
import org.jline.reader.UserInterruptException;
import org.jline.reader.impl.DefaultParser;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.shell.jline3.PicocliCommands;

@Command(name = "shell", description = "JLine3 shell helping using the CLI")
public class Shell implements Runnable {

   @CommandLine.Option(names = "--url", description = "It will be used for an initial connection if set.")
   protected String brokerURL = ConnectionAbstract.DEFAULT_BROKER_URL;

   @CommandLine.Option(names = "--user", description = "It will be used for an initial connection if set.")
   protected String user;

   @CommandLine.Option(names = "--password", description = "It will be used for an initial connection if set.")
   protected String password;

   public Shell(CommandLine commandLine) {
   }

   @Override
   public void run() {
      setInShell();
      printBanner();
      if (brokerURL != ConnectionAbstract.DEFAULT_BROKER_URL || user != null || password != null) {
         Connect connect = new Connect();
         connect.setUser(user).setPassword(password).setBrokerURL(brokerURL);
         connect.run();
      }
      runShell(false);
   }

   private static ThreadLocal<AtomicBoolean> IN_SHELL = ThreadLocal.withInitial(() -> new AtomicBoolean(false));
   private static ThreadLocal<AtomicBoolean> CONNECTED = ThreadLocal.withInitial(() -> new AtomicBoolean(false));
   private static ThreadLocal<String> PROMPT = new ThreadLocal<>();

   public static boolean inShell() {
      return IN_SHELL.get().get();
   }

   public static void setInShell() {
      IN_SHELL.get().set(true);
   }

   public static boolean isConnected() {
      return CONNECTED.get().get();
   }

   public static void setConnected(boolean connected) {
      CONNECTED.get().set(connected);
   }

   public static void runShell(boolean printBanner) {
      try {
         setInShell();

         boolean isInstance = System.getProperty("artemis.instance") != null;

         Supplier<Path> workDir = () -> Paths.get(System.getProperty("user.dir"));

         PicocliCommands.PicocliCommandsFactory factory = new PicocliCommands.PicocliCommandsFactory();

         CommandLine commandLine = Artemis.buildCommand(isInstance, !isInstance, false);

         PicocliCommands picocliCommands = new PicocliCommands(commandLine);

         Parser parser = new DefaultParser();
         try (Terminal terminal = TerminalBuilder.terminal()) {
            SystemRegistry systemRegistry = new SystemRegistryImpl(parser, terminal, workDir, null);
            systemRegistry.setCommandRegistries(picocliCommands);
            systemRegistry.register("help", picocliCommands);

            LineReader reader = LineReaderBuilder.builder()
               .terminal(terminal)
               .completer(systemRegistry.completer())
               .parser(parser)
               .variable(LineReader.LIST_MAX, 50)   // max tab completion candidates
               .build();
            factory.setTerminal(terminal);

            String rightPrompt = null;

            if (printBanner) {
               printBanner();
            }

            System.out.println("For a list of commands, type " + org.apache.activemq.artemis.cli.Terminal.RED_UNICODE + "help" + org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE + " or press " + org.apache.activemq.artemis.cli.Terminal.RED_UNICODE + "<TAB>" + org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE + ":");
            System.out.println("Type " + org.apache.activemq.artemis.cli.Terminal.RED_UNICODE + "exit" + org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE + " or press " + org.apache.activemq.artemis.cli.Terminal.RED_UNICODE + "<CTRL-D>" + org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE + " to leave the session:");

            // start the shell and process input until the user quits with Ctrl-D
            String line;
            while (true) {
               try {
                  // We build a new command every time, as they could have state from previous executions
                  systemRegistry.setCommandRegistries(new PicocliCommands(Artemis.buildCommand(isInstance, !isInstance, false)));
                  systemRegistry.cleanUp();
                  line = reader.readLine(getPrompt(), rightPrompt, (MaskingCallback) null, null);
                  systemRegistry.execute(line);
               } catch (InterruptedException e) {
                  e.printStackTrace();
                  // Ignore
               } catch (UserInterruptException userInterruptException) {
                  // ignore
               } catch (EndOfFileException e) {
                  if (isConnected()) {
                     //if connected, [Ctrl + D] tries to disconnect instead of close
                     systemRegistry.execute("disconnect");
                     continue;
                  }
                  return;
               } catch (Exception e) {
                  systemRegistry.trace(e);
               }
            }
         }
      } catch (Throwable t) {
         t.printStackTrace();
      } finally {
         IN_SHELL.get().set(false);
      }

   }

   private static void printBanner() {
      System.out.print(org.apache.activemq.artemis.cli.Terminal.YELLOW_UNICODE);
      try {
         Artemis.printBanner(System.out);
      } catch (Exception e) {
         System.out.println("Error recovering the banner:");
         e.printStackTrace();
      }
      System.out.print(org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE);
   }

   private static String getPrompt() {
      if (PROMPT.get() == null) {
         setDefaultPrompt();
      }

      return PROMPT.get();
   }

   public static void setDefaultPrompt() {
      try {
         setPrompt(Artemis.getNameFromBanner());
      } catch (Exception e) {
         System.out.println("Error when getting prompt name from banner:");
         e.printStackTrace();

         setPrompt("Artemis Shell");
      }
   }

   public static void setPrompt(String prompt) {
      PROMPT.set(org.apache.activemq.artemis.cli.Terminal.YELLOW_UNICODE + prompt + " > " + org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE);
   }

}
