////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////

#include <cmake.h>
// cmake.h include header must come first

#include <CmdAdd.h>
#include <CmdAliases.h>
#include <CmdAnnotate.h>
#include <CmdAppend.h>
#include <CmdAttributes.h>
#include <CmdBurndown.h>
#include <CmdCalc.h>
#include <CmdCalendar.h>
#include <CmdColor.h>
#include <CmdColumns.h>
#include <CmdCommands.h>
#include <CmdConfig.h>
#include <CmdContext.h>
#include <CmdCount.h>
#include <CmdCustom.h>
#include <CmdDelete.h>
#include <CmdDenotate.h>
#include <CmdDiagnostics.h>
#include <CmdDone.h>
#include <CmdDuplicate.h>
#include <CmdEdit.h>
#include <Command.h>
#include <format.h>
#include <shared.h>
#include <util.h>

#include <iostream>
#include <vector>
#ifdef HAVE_EXECUTE
#include <CmdExec.h>
#endif
#include <CmdExport.h>
#include <CmdGet.h>
#include <CmdHelp.h>
#include <CmdHistory.h>
#include <CmdIDs.h>
#include <CmdImport.h>
#include <CmdImportV2.h>
#include <CmdInfo.h>
#include <CmdLog.h>
#include <CmdLogo.h>
#include <CmdModify.h>
#include <CmdNews.h>
#include <CmdPrepend.h>
#include <CmdProjects.h>
#include <CmdPurge.h>
#include <CmdReports.h>
#include <CmdShow.h>
#include <CmdStart.h>
#include <CmdStats.h>
#include <CmdStop.h>
#include <CmdSummary.h>
#include <CmdSync.h>
#include <CmdTags.h>
#include <CmdTimesheet.h>
#include <CmdUDAs.h>
#include <CmdUndo.h>
#include <CmdUnique.h>
#include <CmdUrgency.h>
#include <CmdVersion.h>
#include <ColDue.h>
#include <ColProject.h>
#include <Context.h>

////////////////////////////////////////////////////////////////////////////////
void Command::factory(std::map<std::string, Command*>& all) {
  Command* c;

  c = new CmdAdd();
  all[c->keyword()] = c;
  c = new CmdAnnotate();
  all[c->keyword()] = c;
  c = new CmdAppend();
  all[c->keyword()] = c;
  c = new CmdBurndownAnnual();
  all[c->keyword()] = c;
  c = new CmdBurndownDaily();
  all[c->keyword()] = c;
  c = new CmdBurndownMonthly();
  all[c->keyword()] = c;
  c = new CmdBurndownWeekly();
  all[c->keyword()] = c;
  c = new CmdCalc();
  all[c->keyword()] = c;
  c = new CmdCalendar();
  all[c->keyword()] = c;
  c = new CmdColor();
  all[c->keyword()] = c;
  c = new CmdColumns();
  all[c->keyword()] = c;
  c = new CmdCommands();
  all[c->keyword()] = c;
  c = new CmdCompletionAliases();
  all[c->keyword()] = c;
  c = new CmdCompletionColumns();
  all[c->keyword()] = c;
  c = new CmdCompletionCommands();
  all[c->keyword()] = c;
  c = new CmdCompletionConfig();
  all[c->keyword()] = c;
  c = new CmdCompletionContext();
  all[c->keyword()] = c;
  c = new CmdCompletionIds();
  all[c->keyword()] = c;
  c = new CmdCompletionUDAs();
  all[c->keyword()] = c;
  c = new CmdCompletionUuids();
  all[c->keyword()] = c;
  c = new CmdCompletionProjects();
  all[c->keyword()] = c;
  c = new CmdCompletionTags();
  all[c->keyword()] = c;
  c = new CmdCompletionVersion();
  all[c->keyword()] = c;
  c = new CmdConfig();
  all[c->keyword()] = c;
  c = new CmdContext();
  all[c->keyword()] = c;
  c = new CmdCount();
  all[c->keyword()] = c;
  c = new CmdDelete();
  all[c->keyword()] = c;
  c = new CmdDenotate();
  all[c->keyword()] = c;
  c = new CmdDiagnostics();
  all[c->keyword()] = c;
  c = new CmdDone();
  all[c->keyword()] = c;
  c = new CmdDuplicate();
  all[c->keyword()] = c;
  c = new CmdEdit();
  all[c->keyword()] = c;
#ifdef HAVE_EXECUTE
  c = new CmdExec();
  all[c->keyword()] = c;
#endif
  c = new CmdExport();
  all[c->keyword()] = c;
  c = new CmdGet();
  all[c->keyword()] = c;
  c = new CmdGHistoryDaily();
  all[c->keyword()] = c;
  c = new CmdGHistoryWeekly();
  all[c->keyword()] = c;
  c = new CmdGHistoryMonthly();
  all[c->keyword()] = c;
  c = new CmdGHistoryAnnual();
  all[c->keyword()] = c;
  c = new CmdHelp();
  all[c->keyword()] = c;
  c = new CmdHistoryDaily();
  all[c->keyword()] = c;
  c = new CmdHistoryWeekly();
  all[c->keyword()] = c;
  c = new CmdHistoryMonthly();
  all[c->keyword()] = c;
  c = new CmdHistoryAnnual();
  all[c->keyword()] = c;
  c = new CmdIDs();
  all[c->keyword()] = c;
  c = new CmdImport();
  all[c->keyword()] = c;
  c = new CmdImportV2();
  all[c->keyword()] = c;
  c = new CmdInfo();
  all[c->keyword()] = c;
  c = new CmdLog();
  all[c->keyword()] = c;
  c = new CmdLogo();
  all[c->keyword()] = c;
  c = new CmdModify();
  all[c->keyword()] = c;
  c = new CmdNews();
  all[c->keyword()] = c;
  c = new CmdPrepend();
  all[c->keyword()] = c;
  c = new CmdProjects();
  all[c->keyword()] = c;
  c = new CmdPurge();
  all[c->keyword()] = c;
  c = new CmdReports();
  all[c->keyword()] = c;
  c = new CmdShow();
  all[c->keyword()] = c;
  c = new CmdShowRaw();
  all[c->keyword()] = c;
  c = new CmdStart();
  all[c->keyword()] = c;
  c = new CmdStats();
  all[c->keyword()] = c;
  c = new CmdStop();
  all[c->keyword()] = c;
  c = new CmdSummary();
  all[c->keyword()] = c;
  c = new CmdSync();
  all[c->keyword()] = c;
  c = new CmdTags();
  all[c->keyword()] = c;
  c = new CmdTimesheet();
  all[c->keyword()] = c;
  c = new CmdUDAs();
  all[c->keyword()] = c;
  c = new CmdUndo();
  all[c->keyword()] = c;
  c = new CmdUnique();
  all[c->keyword()] = c;
  c = new CmdUrgency();
  all[c->keyword()] = c;
  c = new CmdUUIDs();
  all[c->keyword()] = c;
  c = new CmdVersion();
  all[c->keyword()] = c;
  c = new CmdZshAttributes();
  all[c->keyword()] = c;
  c = new CmdZshCommands();
  all[c->keyword()] = c;
  c = new CmdZshCompletionIds();
  all[c->keyword()] = c;
  c = new CmdZshCompletionUuids();
  all[c->keyword()] = c;

  // Instantiate a command object for each custom report.
  std::vector<std::string> reports;
  for (auto& i : Context::getContext().config) {
    if (i.first.substr(0, 7) == "report.") {
      std::string report = i.first.substr(7);
      auto columns = report.find(".columns");
      if (columns != std::string::npos) reports.push_back(report.substr(0, columns));
    }
  }

  for (auto& report : reports) {
    // Make sure a custom report does not clash with a built-in command.
    if (all.find(report) != all.end())
      throw format("Custom report '{1}' conflicts with built-in task command.", report);

    c = new CmdCustom(report, "task <filter> " + report,
                      Context::getContext().config.get("report." + report + ".description"));

    all[c->keyword()] = c;
  }
}

////////////////////////////////////////////////////////////////////////////////
const std::map<Command::Category, std::string> Command::categoryNames = {
    // These strings are intentionally not l10n'd: they are used as identifiers.
    {Command::Category::unassigned, "unassigned"}  // should never happen
    ,
    {Command::Category::metadata, "metadata"},
    {Command::Category::report, "report"},
    {Command::Category::operation, "operation"},
    {Command::Category::context, "context"},
    {Command::Category::graphs, "graphs"},
    {Command::Category::config, "config"},
    {Command::Category::migration, "migration"},
    {Command::Category::misc, "misc"},
    {Command::Category::internal, "internal"},
    {Command::Category::UNDOCUMENTED, "undocumented"}};

////////////////////////////////////////////////////////////////////////////////
Command::Command()
    : _keyword(""),
      _usage(""),
      _description(""),
      _read_only(true),
      _displays_id(true),
      _needs_confirm(false),
      _needs_gc(true),
      _needs_recur_update(false),
      _uses_context(false),
      _accepts_filter(false),
      _accepts_modifications(false),
      _accepts_miscellaneous(false),
      _category(Category::unassigned),
      _permission_quit(false),
      _permission_all(false),
      _first_iteration(true) {}

////////////////////////////////////////////////////////////////////////////////
std::string Command::keyword() const { return _keyword; }

////////////////////////////////////////////////////////////////////////////////
std::string Command::usage() const { return _usage; }

////////////////////////////////////////////////////////////////////////////////
std::string Command::description() const { return _description; }

////////////////////////////////////////////////////////////////////////////////
bool Command::read_only() const { return _read_only; }

////////////////////////////////////////////////////////////////////////////////
bool Command::displays_id() const { return _displays_id; }

////////////////////////////////////////////////////////////////////////////////
bool Command::needs_gc() const { return _needs_gc; }

////////////////////////////////////////////////////////////////////////////////
bool Command::needs_recur_update() const { return _needs_recur_update; }

////////////////////////////////////////////////////////////////////////////////
bool Command::uses_context() const { return _uses_context; }

////////////////////////////////////////////////////////////////////////////////
bool Command::accepts_filter() const { return _accepts_filter; }

////////////////////////////////////////////////////////////////////////////////
bool Command::accepts_modifications() const { return _accepts_modifications; }

////////////////////////////////////////////////////////////////////////////////
bool Command::accepts_miscellaneous() const { return _accepts_miscellaneous; }

////////////////////////////////////////////////////////////////////////////////
Command::Category Command::category() const { return _category; }

////////////////////////////////////////////////////////////////////////////////
// Returns true or false indicating whether to proceed with a write command, on
// a per-task basis, after (potentially) asking for permission.
//
// Factors:
//   filtered.size ()
//   rc.bulk
//   rc.confirmation
//   this->_read_only
bool Command::permission(const std::string& question, unsigned int quantity) {
  // Read-only commands do not need to seek permission.  Write commands are
  // granted permission automatically if the 'all' selection was made in an
  // earlier call.  Or if the 'all' option has already been made.
  if (_read_only || _permission_all) return true;

  // If the 'quit' selection has already been made.
  if (_permission_quit) return false;

  // What remains are write commands that have not yet selected 'all' or 'quit'.
  // Describe the task.
  bool confirmation = Context::getContext().config.getBoolean("confirmation");
  unsigned int bulk = Context::getContext().config.getInteger("bulk");

  // Quantity 1 modifications have optional confirmation, and only (y/n).
  if (quantity == 1) {
    if (!_needs_confirm || !confirmation) return true;

    bool answer = confirm(question);
    return answer;
  }

  // 1 < Quantity < bulk modifications have optional confirmation, in the (y/n/a/q)
  // style. Bulk = 0 denotes infinite bulk.
  if ((bulk == 0 || quantity < bulk) && (!_needs_confirm || !confirmation)) return true;

  if (Context::getContext().verbose("blank") && !_first_iteration) std::cout << '\n';
  int answer = confirm4(question);
  _first_iteration = false;
  switch (answer) {
    case 1:
      return true;  // yes
    case 2:
      _permission_all = true;
      return true;  // all
    case 3:
      _permission_quit = true;
      return false;  // quit
  }

  return false;  // This line keeps the compiler happy.
}

////////////////////////////////////////////////////////////////////////////////
