#include "basecommand.h"

#include <boost/optional.hpp>
#include <zypp-core/base/LogControl.h>
#include "utils/flags/flagtypes.h"
#include "utils/messages.h"
#include "commandhelpformatter.h"
#include "solve-commit.h"
#include "global-settings.h"

#include "src/repos.h"

using namespace zypp;

extern ZYpp::Ptr God;

std::ostream & operator<<( std::ostream & str, const SetupSystemFlags & obj )
{
#define OUTS(F) { F, #F }
  static const std::initializer_list<std::pair<SetupSystemFlags,std::string> > strmap {
    OUTS( ResetRepoManager ),
    OUTS( InitTarget ),
    OUTS( InitRepos ),
    OUTS( NoSystemResolvables ),
    OUTS( LoadTargetResolvables ),
    OUTS( LoadRepoResolvables ),
    OUTS( LoadResolvables ),
    OUTS( Resolve ),
  };
#undef OUTS
  return str << zypp::base::stringify( obj, strmap );
}

BaseCommandOptionSet::BaseCommandOptionSet()
{
}

BaseCommandOptionSet::BaseCommandOptionSet(ZypperBaseCommand &parent)
{
  parent.addOptionSet(*this);
}

BaseCommandOptionSet::~BaseCommandOptionSet()
{
}

BaseCommandCondition::~BaseCommandCondition()
{

}

ZypperBaseCommand::ZypperBaseCommand(std::vector<std::string> &&commandAliases_r, std::string &&synopsis_r,
                                     std::string &&summary_r, CommandDescription &&description_r,
                                     SetupSystemFlags systemInitFlags_r)
  : ZypperBaseCommand( std::move(commandAliases_r), std::vector<std::string>{ std::move(synopsis_r) }, std::move(summary_r), std::move(description_r), systemInitFlags_r )
{

}

ZypperBaseCommand::ZypperBaseCommand(std::vector<std::string> &&commandAliases_r, std::vector<std::string> &&synopsis_r,
                                     std::string &&summary_r, CommandDescription &&description_r,
                                     SetupSystemFlags systemInitFlags_r)
  : _commandAliases ( std::move( commandAliases_r ) ),
    _synopsis ( std::move( synopsis_r ) ),
    _summary ( std::move( summary_r ) ),
    _description ( std::move( description_r ) ),
    _systemInitFlags ( systemInitFlags_r )
{

}

ZypperBaseCommand::~ZypperBaseCommand()
{
}

void ZypperBaseCommand::reset()
{
  _helpRequested = false;

  //reset the global settings
  GlobalSettings::reset();

  //reset all registered option sets
  for ( BaseCommandOptionSet *set : _registeredOptionSets )
    set->reset();

  _positionalArguments.clear();
  _rawOptions.clear();

  //call the commands own implementation
  doReset();
}

std::vector<std::string> ZypperBaseCommand::command() const
{
  return _commandAliases;
}

std::string ZypperBaseCommand::summary() const
{
  return _summary;
}

std::vector<std::string> ZypperBaseCommand::synopsis() const
{
  return _synopsis;
}

std::string ZypperBaseCommand::description() const
{
  return _description.empty() ? _summary : _description;
}

int ZypperBaseCommand::parseArguments( Zypper &, const int argc, char * const *argv )
{

  int nextArg = 0;

  if ( _parseArguments )
    nextArg = ZyppFlags::parseCLI( argc, argv, options() );
  else
    nextArg = argc; //we eat all arguments

  MIL << "Done parsing options." << endl;

  if ( _fillRawOptions ) {
    //skip first argument here as well
    for ( int i = 1; i < argc; ++i ) {
      _rawOptions.push_back( argv[ i ] );
    }
  }

  return nextArg;
}

bool ZypperBaseCommand::helpRequested() const
{
  return _helpRequested;
}

SetupSystemFlags ZypperBaseCommand::setupSystemFlags() const
{
  return _systemInitFlags;
}

ZYpp::Ptr ZypperBaseCommand::zyppApi() const
{
  return God;
}

std::vector<BaseCommandConditionPtr> ZypperBaseCommand::conditions() const
{
  return std::vector<BaseCommandConditionPtr>();
}

int ZypperBaseCommand::systemSetup( Zypper &zypper )
{
  return defaultSystemSetup ( zypper, _systemInitFlags );
}

int ZypperBaseCommand::defaultSystemSetup( Zypper &zypper, SetupSystemFlags flags_r )
{
  DBG << "FLAGS:" << flags_r << endl;

  if ( flags_r.testFlag( ResetRepoManager ) )
    zypper.initRepoManager();

  if ( flags_r.testFlag( InitTarget ) ) {
    init_target( zypper );
    if ( zypper.exitCode() != ZYPPER_EXIT_OK )
      return zypper.exitCode();
  } else {
    // bsc#1237044: Even if we don't launch a Target, announce
    // a --root so components refer to the right context.
    ZConfig::instance().announceSystemRoot( zypper.config().root_dir );
  }

  if ( flags_r.testFlag( InitRepos ) ) {
    init_repos( zypper );
    if ( zypper.exitCode() != ZYPPER_EXIT_OK )
      return zypper.exitCode();
    else if ( flags_r.testFlag( FailIfReposFail ) && zypper.exitInfoCode() != ZYPPER_EXIT_OK ) {
      // Fail as demanded by FailIfReposFail. Commands may tune or explain
      // this by overriding systemSetup (e.g. distupgrade).
      // NOTE: Since several exitInfoCodes are documented to be returned as a kind
      // of annotation, if the command would otherwise have returned 0, we indicate
      // a real error here. Just in case the command does not amend it later.
      zypper.setExitCode( ZYPPER_EXIT_ERR_ZYPP ); // intentionally an error and NOT the exitInfoCode
      return zypper.exitInfoCode();               // but return the exitInfoCode
    }
  }

  DtorReset _tmp( zypper.configNoConst().disable_system_resolvables );
  if ( flags_r.testFlag( NoSystemResolvables ) ) {
    zypper.configNoConst().disable_system_resolvables = true;
  }

  if ( flags_r.testFlag( LoadResolvables ) ) {
    load_resolvables( zypper );
  } else if ( flags_r.testFlag( LoadRepoResolvables ) ) {
    load_repo_resolvables( zypper );
  } else if ( flags_r.testFlag( LoadTargetResolvables ) ) {
    load_target_resolvables( zypper );
  }

  if ( zypper.exitCode() != ZYPPER_EXIT_OK )
    return zypper.exitCode();


  return zypper.exitCode();
}

bool ZypperBaseCommand::fillRawOptions() const
{
  return _fillRawOptions;
}

void ZypperBaseCommand::setFillRawOptions(bool fillRawOptions)
{
  _fillRawOptions = fillRawOptions;
}

void ZypperBaseCommand::disableArgumentParser( bool disable )
{
  _parseArguments = !disable;
}

const std::vector<std::string> &ZypperBaseCommand::rawOptions() const
{
  return _rawOptions;
}

std::vector<std::string> ZypperBaseCommand::positionalArguments() const
{
  return _positionalArguments;
}

void ZypperBaseCommand::setPositionalArguments(const std::vector<std::string> &positionalArguments)
{
  _positionalArguments = positionalArguments;
}

int ZypperBaseCommand::earlyPositionalArgsCheck( Zypper &zypper, const std::vector<std::string> &positionalArgs )
{ return ZYPPER_EXIT_OK; }

int ZypperBaseCommand::run( Zypper &zypper )
{
  MIL << "run: " << command().front() << endl;
  try
  {
    for ( const BaseCommandConditionPtr &cond : conditions() ) {
      std::string error;
      int code = cond->check( error );
      if ( code != 0 ) {
        zypper.out().error( error );
        return code;
      }
    }

    if ( int code = earlyPositionalArgsCheck( zypper, _positionalArguments ); code != ZYPPER_EXIT_OK )
      return code;

    if ( int code = systemSetup( zypper ); code != ZYPPER_EXIT_OK )
      return code;

    return execute( zypper, _positionalArguments );
  }

  // ZypperBaseCommand's should actually handle most exceptions themselves.
  // Out::Error as a common way of reporting issues is handled here.
  // Whatever escapes is handled by Zypper::doCommand.
  // TODO Someday redesign the Exceptions flow.
  catch ( const Out::Error & error_r )
  {
    error_r.report( zypper );
    return error_r._exitcode;
  }
  return zypper.exitCode(); // should not be reached.
}

std::string ZypperBaseCommand::help()
{
  CommandHelpFormater help;
  for ( const std::string &syn : synopsis() )
    help.synopsis(syn);
  help.description(description());

  auto renderOption = [&help]( const ZyppFlags::CommandOption &opt ) {
    help.option( opt.flagDesc(), opt.optionHelp() );
  };

  //all the options we have
  std::vector<ZyppFlags::CommandGroup> opts = options();

  //collect all deprecated options
  std::vector<const ZyppFlags::CommandOption*> legacyOptions;

  bool hadOptions = false;
  for ( const ZyppFlags::CommandGroup &grp : opts ) {
    if ( grp.options.size() ) {
      bool wroteSectionHdr = false;
      for ( const ZyppFlags::CommandOption &opt : grp.options ) {
        //even though the option might be hidden, we still do not want to show the
        //"no options" available message anymore. Its misleading.
        hadOptions = true;
        if ( opt.flags & ZyppFlags::Hidden )
          continue;
        if ( opt.flags & ZyppFlags::Deprecated ) {
          legacyOptions.push_back( &opt );
          continue;
        }
        //write the section header only if we actuall have entries
        if ( !wroteSectionHdr ) {
          wroteSectionHdr = true;
          help.optionSection( grp.name );
        }
        renderOption(opt);
      }
    }
  }

  if ( legacyOptions.size() ) {
    help.legacyOptionSection();
    for ( const ZyppFlags::CommandOption *legacyOption : legacyOptions ) {
      renderOption(*legacyOption);
    }
  }

  if ( !hadOptions ) {
    help.noOptionSection();
  }

  return help;
}

std::vector<ZyppFlags::CommandGroup> ZypperBaseCommand::options()
{
  //first get the commands own options
  std::vector<ZyppFlags::CommandGroup> allOpts;

  //merges a group into
  auto mergeGroup = [ &allOpts ] ( ZyppFlags::CommandGroup &&grp ) {
    bool foundCmdGroup = false;
    for ( ZyppFlags::CommandGroup &cmdGroup : allOpts ) {
      if ( grp.name == cmdGroup.name ) {
        foundCmdGroup = true;
        std::move( grp.options.begin(), grp.options.end(), std::back_inserter(cmdGroup.options) );
        std::move( grp.conflictingOptions.begin(), grp.conflictingOptions.end(), std::back_inserter(cmdGroup.conflictingOptions) );
      }
    }
    if ( !foundCmdGroup )
      allOpts.push_back( std::move(grp) );
  };

  //inject a help option at the beginning, we always want help
  //this will also make sure that the default command group is at the beginning
  mergeGroup ( ZyppFlags::CommandGroup {{
    { "help", 'h', ZyppFlags::NoArgument | ZyppFlags::Hidden, ZyppFlags::BoolType( &_helpRequested ), "" }
  }});

  // now add the commands own options
  mergeGroup ( cmdOptions() );

  //now collect all options from registered option sets
  for ( BaseCommandOptionSet *set : _registeredOptionSets ) {

    std::vector<ZyppFlags::CommandGroup> setOpts = set->options();

    //merge the set into the other options
    for ( ZyppFlags::CommandGroup &grp : setOpts ) {
      mergeGroup ( std::move(grp) );
    }
  }
  return allOpts;
}

void ZypperBaseCommand::addOptionSet(BaseCommandOptionSet &set)
{
  _registeredOptionSets.push_back(&set);
}

