/*
 * Copyright 2011-2012 the original author or authors.
 * 
 * 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.
 */
package org.apache.zeppelin.lens;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.shell.CommandLine;
import org.springframework.shell.plugin.BannerProvider;
import org.springframework.shell.plugin.HistoryFileNameProvider;
import org.springframework.shell.plugin.PluginUtils;
import org.springframework.shell.plugin.PromptProvider;
import org.springframework.shell.core.*;

/**
 * workaround for https://github.com/spring-projects/spring-shell/issues/73
 */
public class LensJLineShellComponent extends JLineShell 
  implements SmartLifecycle, ApplicationContextAware, InitializingBean {

  @Autowired
  private CommandLine commandLine;
  
  private volatile boolean running = false;
  private Thread shellThread;

  private ApplicationContext applicationContext;
  private boolean printBanner = true;

  private String historyFileName;
  private String promptText;
  private String productName;
  private String banner;
  private String version;
  private String welcomeMessage;

  private ExecutionStrategy executionStrategy = new LensSimpleExecutionStrategy();
  private SimpleParser parser = new SimpleParser();

  private static final Logger LOGGER = LoggerFactory.getLogger(LensJLineShellComponent.class);

  public SimpleParser getSimpleParser() {
    return parser;
  }

  public boolean isAutoStartup() {
    return false;
  }
  
  public void stop(Runnable callback) {
    stop();
    callback.run();
  }
  
  public int getPhase() {
    return 1;
  }
  
  public void start() {
    //customizePlug must run before start thread to take plugin's configuration into effect
    customizePlugin();
    shellThread = new Thread(this, "Spring Shell");
    shellThread.start();
    running = true;
  }


  public void stop() {
    if (running) {
      closeShell();
      running = false;
    }
  }

  public boolean isRunning() {
    return running;
  }
  
  @SuppressWarnings("rawtypes")
  public void afterPropertiesSet() {

    Map<String, CommandMarker> commands = 
      BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext,
        CommandMarker.class);
    for (CommandMarker command : commands.values()) {
      getSimpleParser().add(command);
    }

    Map<String, Converter> converters = BeanFactoryUtils
      .beansOfTypeIncludingAncestors(applicationContext, Converter.class);
    for (Converter<?> converter : converters.values()) {
      getSimpleParser().add(converter);
    }
    
    setHistorySize(commandLine.getHistorySize());
    if (commandLine.getShellCommandsToExecute() != null) {
      setPrintBanner(false);
    }
  }

  /**
   * wait the shell command to complete by typing "quit" or "exit" 
   * 
   */
  public void waitForComplete() {
    try {
      shellThread.join();
    } catch (InterruptedException e) {
      LOGGER.error(e.toString(), e);
    }
  }

  @Override
  protected ExecutionStrategy getExecutionStrategy() {
    return executionStrategy;
  }

  @Override
  protected Parser getParser() {
    return parser;
  }

  @Override
  public String getStartupNotifications() {
    return null;
  }

  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }

  public void customizePlugin() {
    this.historyFileName = getHistoryFileName();
    this.promptText = getPromptText();
    String[] banner = getBannerText();
    this.banner = banner[0];
    this.welcomeMessage = banner[1];
    this.version = banner[2];
    this.productName = banner[3];
  }

  /**
   * get history file name from provider. The provider has highest order 
   * <link>org.springframework.core.Ordered.getOder</link> will win. 
   * 
   * @return history file name 
   */
  protected String getHistoryFileName() {
    HistoryFileNameProvider historyFileNameProvider = PluginUtils
      .getHighestPriorityProvider(this.applicationContext, HistoryFileNameProvider.class);
    String providerHistoryFileName = historyFileNameProvider.getHistoryFileName();
    if (providerHistoryFileName != null) {
      return providerHistoryFileName;
    } else {
      return historyFileName;
    }
  }

  /**
   * get prompt text from provider. The provider has highest order 
   * <link>org.springframework.core.Ordered.getOder</link> will win. 
   * 
   * @return prompt text
   */
  protected String getPromptText() {
    PromptProvider promptProvider = PluginUtils
      .getHighestPriorityProvider(this.applicationContext, PromptProvider.class);
    String providerPromptText = promptProvider.getPrompt();
    if (providerPromptText != null) {
      return providerPromptText;
    } else {
      return promptText;
    }
  }

  /**
   * Get Banner and Welcome Message from provider. The provider has highest order 
   * <link>org.springframework.core.Ordered.getOder</link> will win. 
   * @return BannerText[0]: Banner
   *         BannerText[1]: Welcome Message
   *         BannerText[2]: Version
   *         BannerText[3]: Product Name
   */
  private String[] getBannerText() {
    String[] bannerText = new String[4];
    BannerProvider provider = PluginUtils
      .getHighestPriorityProvider(this.applicationContext, BannerProvider.class);
    bannerText[0] = provider.getBanner();
    bannerText[1] = provider.getWelcomeMessage();
    bannerText[2] = provider.getVersion();
    bannerText[3] = provider.getProviderName();
    return bannerText;
  }

  public void printBannerAndWelcome() {
    if (printBanner) {
      logger.info(this.banner);
      logger.info(getWelcomeMessage());
    }
  }


  /**
   * get the welcome message at start.
   * 
   * @return welcome message
   */
  public String getWelcomeMessage() {
    return this.welcomeMessage;
  }


  /**
   * @param printBanner the printBanner to set
   */
  public void setPrintBanner(boolean printBanner) {
    this.printBanner = printBanner;
  }
  
  protected String getProductName() {
    return productName;
  }
  
  protected String getVersion() {
    return version;
  }
}
