/*
 * Copyright (C) 2003-2014 eXo Platform SAS.
 *
 * This file is part of eXo Platform - Add-ons Manager.
 *
 * eXo Platform - Add-ons Manager is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * eXo Platform - Add-ons Manager software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with eXo Platform - Add-ons Manager; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see <http://www.gnu.org/licenses/>.
 */
package org.exoplatform.platform.am.cli

import com.beust.jcommander.JCommander
import com.beust.jcommander.ParameterDescription
import com.beust.jcommander.ParameterException
import org.exoplatform.platform.am.ex.CommandLineParsingException
import org.exoplatform.platform.am.utils.Logger

/**
 * Parser for command line arguments
 * @author Arnaud HÃ©ritier <aheritier@exoplatform.com>
 */
class CommandLineParser {

  /**
   * Logger
   */
  private static final Logger LOG = Logger.getInstance()

  /**
   * JCommander instance used to process args
   */
  private JCommander _jCommander

  /**
   * Object to populate from CL args
   */
  private CommandLineParameters _cliArgs

  private String _scriptName

  /**
   * Default Constructor
   * @param scriptName The name of the script (used to display usage message)
   * @param columnSize The number of characters to in an output line  (used to display usage message)
   */
  CommandLineParser(String scriptName, int columnSize) {
    // Output
    _cliArgs = new CommandLineParameters()
    _jCommander = new JCommander(_cliArgs);
    _jCommander.addCommand(_cliArgs.commandList)
    _jCommander.addCommand(_cliArgs.commandDescribe)
    _jCommander.addCommand(_cliArgs.commandInstall)
    _jCommander.addCommand(_cliArgs.commandUninstall)
    _jCommander.setColumnSize(columnSize)
    _jCommander.setProgramName(scriptName)
    _scriptName = scriptName
  }

  /**
   * Display in the output the usage message to explain how to use the program and its parameters
   */
  void usage() {
    LOG.info("@|underline Usage:|@")
    LOG.info("")
    LOG.info("  ${_scriptName} [options] @|yellow command|@ @|bold [command parameter(s)]|@ [command options]")
    LOG.info("")
    LOG.info("@|underline Options:|@")
    LOG.info("")
    _jCommander.parameters.findAll { !it.parameter.hidden() }.sort { it.names }.each { globalParam ->
      LOG.info(
          String.format("  @|yellow %-${_jCommander.parameters.collect { computeOptionSyntax(it) }*.size().max()}s|@ : %s %s",
                        computeOptionSyntax(globalParam),
                        globalParam.description,
                        computeDefaultValue(globalParam) ? "(Default: ${computeDefaultValue(globalParam)})" : ""))
    }
    LOG.info("")
    LOG.info("@|underline Commands:|@")
    _jCommander.commands.each { command, commandDescription ->
      LOG.info("")
      List<String> commandHelp = new ArrayList<>()
      commandHelp << _scriptName
      commandHelp << "[options]"
      commandHelp << "@|yellow ${command}|@"
      if (commandDescription.mainParameter) {
        commandHelp << "@|bold ${commandDescription.mainParameter?.description}|@"
      }
      if (_jCommander.commands[command].parameters.findAll { !it.parameter.hidden() }.size() > 0) {
        commandHelp << "[${command} options]"
      }
      LOG.info("  ${commandHelp.join(" ")}")
      commandDescription.parameters.findAll { !it.parameter.hidden() }.sort { it.names }.each { commandParam ->
        LOG.info(
            String.format(
                "    @|yellow %-${commandDescription.parameters.findAll { !it.parameter.hidden() }.collect { computeOptionSyntax(it) }*.trim()*.size().max()}s|@ : %s %s",
                computeOptionSyntax(commandParam),
                commandParam.description,
                computeDefaultValue(commandParam) ? "(Default: ${computeDefaultValue(commandParam)})" : ""))
      }
    }
    LOG.info("")
  }

  private String computeOptionSyntax(ParameterDescription parameterDescription) {
    parameterDescription.parameter.names().collect { paramName ->
      if (parameterDescription.parameterized.type == Boolean) {
        "${paramName}"
      } else if (parameterDescription.parameterized.type == URL) {
        "${paramName}=URL"
      } else if (parameterDescription.parameterized.type.isEnum()) {
        "${paramName}=[${parameterDescription.parameterized.type.enumConstants.collect { it.toString().toLowerCase() }.join("|")}]"
      } else {
        "${paramName}=value"
      }
    }.join(", ")
  }

  private String computeDefaultValue(ParameterDescription parameterDescription) {
    if (parameterDescription.parameterized.type == Boolean && !parameterDescription.default) {
      "false"
    } else {
      parameterDescription.default ? parameterDescription.default?.toString()?.toLowerCase() : ""
    }
  }

  /**
   * Initialize settings from command line parameters
   * @param args Command line parameters to analyze
   * @return a CommandLineParameters instance populated with data coming from the command line
   * @thows CommandLineParsingException if something goes wrong while analyzing CL parameters
   */
  CommandLineParameters parse(String[] args) {
    LOG.debug("Parameters to parse : ${args}")
    try {
      _jCommander.parse(args);
    } catch (ParameterException pe) {
      throw new CommandLineParsingException("Invalid command line parameter(s) : ${pe.message}", pe);
    }
    if (_cliArgs.verbose) {
      LOG.enableDebug()
    }

    // Show usage text when -h or --help option is used.
    if (_cliArgs.help) {
      return _cliArgs
    }

    if (CommandLineParameters.LIST_CMD.equals(_jCommander.getParsedCommand())) {
      _cliArgs.command = CommandLineParameters.Command.LIST
    } else if (CommandLineParameters.DESCRIBE_CMD.equals(_jCommander.getParsedCommand())) {
      _cliArgs.command = CommandLineParameters.Command.DESCRIBE
      if (_cliArgs?.commandDescribe?.addon?.size() != 1) {
        throw new CommandLineParsingException(
            "Invalid command line parameter(s) : Command ${CommandLineParameters.Command.DESCRIBE} must have one and only one value (found : ${_cliArgs?.commandDescribe?.addon})");
      }
      if (_cliArgs.commandDescribe.addon[0].indexOf(':') > 0) {
        // A specific version is asked
        _cliArgs.commandDescribe.addonId = _cliArgs.commandDescribe.addon[0].substring(0,
                                                                                       _cliArgs.commandDescribe.addon[0].indexOf(
                                                                                           ':'))
        _cliArgs.commandDescribe.addonVersion = _cliArgs.commandDescribe.addon[0].substring(
            _cliArgs.commandDescribe.addon[0].indexOf(':') + 1,
            _cliArgs.commandDescribe.addon[0].length())
      } else {
        _cliArgs.commandDescribe.addonId = _cliArgs.commandDescribe.addon[0]
      }
    } else if (CommandLineParameters.INSTALL_CMD.equals(_jCommander.getParsedCommand())) {
      _cliArgs.command = CommandLineParameters.Command.INSTALL
      if (_cliArgs?.commandInstall?.addon?.size() != 1) {
        throw new CommandLineParsingException(
            "Invalid command line parameter(s) : Command ${CommandLineParameters.Command.INSTALL} must have one and only one value (found : ${_cliArgs?.commandInstall?.addon})");
      }
      if (_cliArgs.commandInstall.addon[0].indexOf(':') > 0) {
        // A specific version is asked
        _cliArgs.commandInstall.addonId = _cliArgs.commandInstall.addon[0].substring(0, _cliArgs.commandInstall.addon[0].indexOf(
            ':'))
        _cliArgs.commandInstall.addonVersion = _cliArgs.commandInstall.addon[0].substring(
            _cliArgs.commandInstall.addon[0].indexOf(':') + 1,
            _cliArgs.commandInstall.addon[0].length())
      } else {
        _cliArgs.commandInstall.addonId = _cliArgs.commandInstall.addon[0]
      }
      if (_cliArgs.commandInstall.force) {
        LOG.debug("Force mode activated")
      }
    } else if (CommandLineParameters.UNINSTALL_CMD.equals(_jCommander.getParsedCommand())) {
      _cliArgs.command = CommandLineParameters.Command.UNINSTALL
      if (_cliArgs?.commandUninstall?.addon?.size() != 1) {
        throw new CommandLineParsingException(
            "Invalid command line parameter(s) : Command ${CommandLineParameters.Command.UNINSTALL} must have one and only one value (found : ${_cliArgs?.commandUninstall?.addon})");
      }
      if (_cliArgs.commandUninstall.addon[0].indexOf(':') > 0) {
        // A specific version is asked
        _cliArgs.commandUninstall.addonId = _cliArgs.commandUninstall.addon[0].substring(0, _cliArgs.commandUninstall.addon
        [0].indexOf(':'))
        // Version is useless. Let's warn
        String addonVersion = _cliArgs.commandUninstall.addon[0].substring(
            _cliArgs.commandUninstall.addon[0].indexOf(':') + 1,
            _cliArgs.commandUninstall.addon[0].length())
        LOG.warn(
            "Command line parameter(s) : The add-on version (${addonVersion}) is useless for command ${CommandLineParameters.Command.UNINSTALL} and won't be used")
      } else {
        _cliArgs.commandUninstall.addonId = _cliArgs.commandUninstall.addon[0]
      }
    } else {
      throw new CommandLineParsingException("Invalid command line parameter(s) : No command defined")
    }
    return _cliArgs
  }

}