////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 - 2021, Thomas Lauf, 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>
#include <timew.h>
#include <shared.h>
#include <format.h>
#include <commands.h>
#include <cstring>
#include <unistd.h>
#include <iostream>

////////////////////////////////////////////////////////////////////////////////
bool lightweightVersionCheck (int argc, const char** argv)
{
  if (argc == 2 && std::string (argv[1]) == "--version")
  {
    std::cout << VERSION << '\n';
    return true;
  }

  return false;
}

////////////////////////////////////////////////////////////////////////////////
void initializeEntities (CLI& cli)
{
  // Command entities.
  cli.entity ("command", "annotate");
  cli.entity ("command", "cancel");
  cli.entity ("command", "config");
  cli.entity ("command", "continue");
  cli.entity ("command", "delete");
  cli.entity ("command", "diagnostics");
  cli.entity ("command", "export");
  cli.entity ("command", "extensions");
  cli.entity ("command", "fill");
  cli.entity ("command", "gaps");
  cli.entity ("command", "get");
  cli.entity ("command", "help");
  cli.entity ("command", "--help");
  cli.entity ("command", "-h");
  cli.entity ("command", "join");
  cli.entity ("command", "lengthen");
  cli.entity ("command", "modify");
  cli.entity ("command", "move");
  cli.entity ("command", "report");
  cli.entity ("command", "resize");
  cli.entity ("command", "shorten");
  cli.entity ("command", "show");
  cli.entity ("command", "split");
  cli.entity ("command", "start");
  cli.entity ("command", "stop");
  cli.entity ("command", "tag");
  cli.entity ("command", "tags");
  cli.entity ("command", "track");
  cli.entity ("command", "undo");
  cli.entity ("command", "untag");

  // Some command list themselves as extensions, to integrate with the real
  // extensions.
  cli.entity ("extension", "day");
  cli.entity ("extension", "month");
  cli.entity ("extension", "summary");
  cli.entity ("extension", "week");

  // Hint entities.
  cli.entity ("hint", ":all");
  cli.entity ("hint", ":adjust");
  cli.entity ("hint", ":blank");
  cli.entity ("hint", ":color");
  cli.entity ("hint", ":day");
  cli.entity ("hint", ":debug");
  cli.entity ("hint", ":fill");
  cli.entity ("hint", ":ids");
  cli.entity ("hint", ":annotations");
  cli.entity ("hint", ":lastmonth");
  cli.entity ("hint", ":lastquarter");
  cli.entity ("hint", ":lastweek");
  cli.entity ("hint", ":lastyear");
  cli.entity ("hint", ":month");
  cli.entity ("hint", ":nocolor");
  cli.entity ("hint", ":quarter");
  cli.entity ("hint", ":quiet");
  cli.entity ("hint", ":week");
  cli.entity ("hint", ":fortnight");
  cli.entity ("hint", ":year");
  cli.entity ("hint", ":yes");
  cli.entity ("hint", ":yesterday");
  cli.entity ("hint", ":monday");
  cli.entity ("hint", ":tuesday");
  cli.entity ("hint", ":wednesday");
  cli.entity ("hint", ":thursday");
  cli.entity ("hint", ":friday");
  cli.entity ("hint", ":saturday");
  cli.entity ("hint", ":sunday");
}

////////////////////////////////////////////////////////////////////////////////
void initializeDataJournalAndRules (
  const CLI& cli,
  Database& database,
  Journal& journal,
  Rules& rules)
{
  // Rose tint my world, make me safe from my trouble and pain.
  rules.set ("color", isatty (STDOUT_FILENO) ? "on" : "off");

  // Make common hints available via rules:
  //   :debug   --> debug=on
  //   :quiet   --> verbose=off
  //   :color   --> color=on
  //   :nocolor --> color=off
  //   :yes     --> confirmation=off
  for (auto& arg : cli._args)
  {
    if (arg.hasTag ("HINT"))
    {
      if (arg.attribute ("canonical") == ":debug")   rules.set ("debug",        "on");
      if (arg.attribute ("canonical") == ":quiet")   rules.set ("verbose",      "off");
      if (arg.attribute ("canonical") == ":color")   rules.set ("color",        "on");
      if (arg.attribute ("canonical") == ":nocolor") rules.set ("color",        "off");
      if (arg.attribute ("canonical") == ":yes")     rules.set ("confirmation", "off");
    }
  }

  enableDebugMode (rules.getBoolean ("debug"));

  // The $TIMEWARRIORDB environment variable overrides the default value of
  // ~/.timewarrior‥
  Directory dbLocation;
  char* override = getenv ("TIMEWARRIORDB");
  dbLocation = Directory (override ? override : "~/.timewarrior");

  // If dbLocation exists, but is not readable/writable/executable, error.
  if (dbLocation.exists () &&
      (! dbLocation.readable () ||
       ! dbLocation.writable () ||
       ! dbLocation.executable ()))
  {
    throw format ("Database is not readable at '{1}'", dbLocation._data);
  }

  // If dbLocation does not exist, ask whether it should be created.
  bool shinyNewDatabase = false;
  if (! dbLocation.exists () &&
      (findHint (cli, ":yes") ||
       confirm ("Create new database in " + dbLocation._data + "?")))
  {
    dbLocation.create (0700);
    shinyNewDatabase = true;
  }

  // Create extensions subdirectory if necessary.
  Directory extensions (dbLocation);
  extensions += "extensions";
  if (! extensions.exists ())
    extensions.create (0700);

  // Create data subdirectory if necessary.
  Directory data (dbLocation);
  data += "data";
  if (! data.exists ())
    data.create (0700);

  // Load the configuration data.
  Path configFile (dbLocation);
  configFile += "timewarrior.cfg";
  if (! configFile.exists ())
  {
    File (configFile).create (0600);
  }
  rules.load (configFile._data);

  // This value is not written out to disk, as there would be no point. Having
  // located the config file, the 'db' location is already known. This is just
  // for subsequent internal use.
  rules.set ("temp.db", dbLocation._data);

  // Perhaps some subsequent code would like to know this is a new db and
  // possibly a first run.
  if (shinyNewDatabase)
    rules.set ("temp.shiny", 1);

  if (rules.has ("debug.indicator"))
    setDebugIndicator (rules.get ("debug.indicator"));

  if (rules.has ("theme.colors.debug"))
    setDebugColor (Color (rules.get ("theme.colors.debug")));

  // Apply command line overrides.
  for (auto& arg : cli._args)
  {
    if (arg.hasTag ("CONFIG"))
    {
      rules.set (arg.attribute ("name"), arg.attribute ("value"));
      debug (format ("Configuration override {1} = {2}", arg.attribute ("name"), arg.attribute ("value")));
    }
  }

  journal.initialize (data._data + "/undo.data", rules.getInteger ("journal.size"));
  // Initialize the database (no data read), but files are enumerated.
  database.initialize (data._data, journal);
}

////////////////////////////////////////////////////////////////////////////////
void initializeExtensions (
  CLI& cli,
  const Rules& rules,
  Extensions& extensions)
{
  Directory extDir (rules.get ("temp.db"));
  extDir += "extensions";

  extensions.initialize (extDir._data);

  // Add extensions as CLI entities.
  for (auto& ext : extensions.all ())
    cli.entity ("extension", File (ext).name ());

  // Extensions have a debug mode.
  if (rules.getBoolean ("debug"))
    extensions.debug ();
}

////////////////////////////////////////////////////////////////////////////////
int dispatchCommand (
  const CLI& cli,
  Database& database,
  Journal& journal,
  Rules& rules,
  const Extensions& extensions)
{
  int status {0};

  // Debug output.
  if (rules.getBoolean ("debug"))
    std::cout << cli.dump () << '\n';

  // Dispatch to the right command function.
  std::string command = cli.getCommand ();

  if (! command.empty ())
  {
    // These signatures are expected to be all different, therefore no
    // command to fn mapping.
         if (command == "annotate")    status = CmdAnnotate      (cli, rules, database, journal            );
    else if (command == "cancel")      status = CmdCancel        (     rules, database, journal            );
    else if (command == "config")      status = CmdConfig        (cli, rules,           journal            );
    else if (command == "continue")    status = CmdContinue      (cli, rules, database, journal            );
    else if (command == "day")         status = CmdChartDay      (cli, rules, database                     );
    else if (command == "delete")      status = CmdDelete        (cli, rules, database, journal            );
    else if (command == "diagnostics") status = CmdDiagnostics   (     rules, database,          extensions);
    else if (command == "export")      status = CmdExport        (cli, rules, database                     );
    else if (command == "extensions")  status = CmdExtensions    (     rules,                    extensions);
/*
    else if (command == "fill")        status = CmdFill          (cli, rules, database, journal            );
//*/
    else if (command == "gaps")        status = CmdGaps          (cli, rules, database                     );
    else if (command == "get")         status = CmdGet           (cli, rules, database                     );
    else if (command == "help" ||
             command == "--help" ||
             command == "-h")          status = CmdHelp          (cli,                           extensions);
    else if (command == "join")        status = CmdJoin          (cli, rules, database, journal            );
    else if (command == "lengthen")    status = CmdLengthen      (cli, rules, database, journal            );
    else if (command == "modify")      status = CmdModify        (cli, rules, database, journal            );
    else if (command == "month")       status = CmdChartMonth    (cli, rules, database                     );
    else if (command == "move")        status = CmdMove          (cli, rules, database, journal            );
    else if (command == "report")      status = CmdReport        (cli, rules, database,          extensions);
    else if (command == "resize")      status = CmdResize        (cli, rules, database, journal            );
    else if (command == "shorten")     status = CmdShorten       (cli, rules, database, journal            );
    else if (command == "show")        status = CmdShow          (     rules                               );
    else if (command == "split")       status = CmdSplit         (cli, rules, database, journal            );
    else if (command == "start")       status = CmdStart         (cli, rules, database, journal            );
    else if (command == "stop")        status = CmdStop          (cli, rules, database, journal            );
    else if (command == "summary")     status = CmdSummary       (cli, rules, database                     );
    else if (command == "tag")         status = CmdTag           (cli, rules, database, journal            );
    else if (command == "tags")        status = CmdTags          (cli, rules, database                     );
    else if (command == "track")       status = CmdTrack         (cli, rules, database, journal            );
    else if (command == "undo")        status = CmdUndo          (     rules, database, journal            );
    else if (command == "untag")       status = CmdUntag         (cli, rules, database, journal            );
    else if (command == "week")        status = CmdChartWeek     (cli, rules, database                     );
    else                               status = CmdReport        (cli, rules, database,          extensions);
  }
  else
  {
    auto words = cli.getWords ();

    if (! words.empty ())
    {
      throw format ("'{1}' is not a timew command. See 'timew help'.", words[0]);
    }
    else
    {
      status = CmdDefault (rules, database);
    }
  }

  return status;
}

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