// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This program 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 General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

#include "../src/Args.hpp"
#include "TestUtil.hpp"

#include <util/file.hpp>

#include "third_party/doctest.h"

TEST_SUITE_BEGIN("Args");

using TestUtil::TestContext;

TEST_CASE("Args default constructor")
{
  Args args;
  CHECK(args.size() == 0);
}

TEST_CASE("Args copy constructor")
{
  Args args1;
  args1.push_back("foo");
  args1.push_back("bar");

  Args args2(args1);
  CHECK(args1 == args2);
}

TEST_CASE("Args move constructor")
{
  Args args1;
  args1.push_back("foo");
  args1.push_back("bar");
  const char* foo_pointer = args1[0].c_str();
  const char* bar_pointer = args1[1].c_str();

  Args args2(std::move(args1));
  CHECK(args1.size() == 0);
  CHECK(args2.size() == 2);
  CHECK(args2[0].c_str() == foo_pointer);
  CHECK(args2[1].c_str() == bar_pointer);
}

TEST_CASE("Args::from_argv")
{
  int argc = 2;
  const char* argv[] = {"a", "b"};
  Args args = Args::from_argv(argc, argv);
  CHECK(args.size() == 2);
  CHECK(args[0] == "a");
  CHECK(args[1] == "b");
}

TEST_CASE("Args::from_string")
{
  Args args = Args::from_string(" c  d\te\r\nf ");
  CHECK(args.size() == 4);
  CHECK(args[0] == "c");
  CHECK(args[1] == "d");
  CHECK(args[2] == "e");
  CHECK(args[3] == "f");
}

TEST_CASE("Args::from_atfile")
{
  TestContext test_context;

  Args args;

  SUBCASE("Nonexistent file")
  {
    CHECK(Args::from_atfile("at_file") == std::nullopt);
  }

  SUBCASE("Empty")
  {
    util::write_file("at_file", "");
    args = *Args::from_atfile("at_file");
    CHECK(args.size() == 0);
  }

  SUBCASE("One argument without newline")
  {
    util::write_file("at_file", "foo");
    args = *Args::from_atfile("at_file");
    CHECK(args.size() == 1);
    CHECK(args[0] == "foo");
  }

  SUBCASE("One argument with newline")
  {
    util::write_file("at_file", "foo\n");
    args = *Args::from_atfile("at_file");
    CHECK(args.size() == 1);
    CHECK(args[0] == "foo");
  }

  SUBCASE("Multiple simple arguments")
  {
    util::write_file("at_file", "x y z\n");
    args = *Args::from_atfile("at_file");
    CHECK(args.size() == 3);
    CHECK(args[0] == "x");
    CHECK(args[1] == "y");
    CHECK(args[2] == "z");
  }

  SUBCASE("Tricky quoting")
  {
    util::write_file(
      "at_file",
      "first\rsec\\\tond\tthi\\\\rd\nfourth  \tfif\\ th \"si'x\\\" th\""
      " 'seve\nth'\\");
    args = *Args::from_atfile("at_file");
    CHECK(args.size() == 7);
    CHECK(args[0] == "first");
    CHECK(args[1] == "sec\tond");
    CHECK(args[2] == "thi\\rd");
    CHECK(args[3] == "fourth");
    CHECK(args[4] == "fif th");
    CHECK(args[5] == "si'x\" th");
    CHECK(args[6] == "seve\nth");
  }

  SUBCASE("Only escape double quote in alternate format")
  {
    util::write_file("at_file", "\"\\\"\\a\\ \\b\\\"\"\\");
    args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
    CHECK(args.size() == 1);
    CHECK(args[0] == "\"\\a\\ \\b\"\\");
  }

  SUBCASE("Ignore single quote in alternate format")
  {
    util::write_file("at_file", "'a b'");
    args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
    CHECK(args.size() == 2);
    CHECK(args[0] == "'a");
    CHECK(args[1] == "b'");
  }

  SUBCASE("Do not escape backslash in alternate format")
  {
    util::write_file("at_file", "\"-DDIRSEP='\\\\'\"");
    args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
    CHECK(args.size() == 1);
    CHECK(args[0] == "-DDIRSEP='\\\\'");
  }
}

TEST_CASE("Args copy assignment operator")
{
  Args args1 = Args::from_string("x y");
  Args args2;
  args2 = args1;
  CHECK(args2.size() == 2);
  CHECK(args2[0] == "x");
  CHECK(args2[1] == "y");
}

TEST_CASE("Args move assignment operator")
{
  Args args1 = Args::from_string("x y");
  const char* x_pointer = args1[0].c_str();
  const char* y_pointer = args1[1].c_str();

  Args args2;
  args2 = std::move(args1);
  CHECK(args1.size() == 0);
  CHECK(args2.size() == 2);
  CHECK(args2[0].c_str() == x_pointer);
  CHECK(args2[1] == y_pointer);
}

TEST_CASE("Args equality operators")
{
  Args args1 = Args::from_string("x y");
  Args args2 = Args::from_string("x y");
  Args args3 = Args::from_string("y x");
  CHECK(args1 == args1);
  CHECK(args1 == args2);
  CHECK(args2 == args1);
  CHECK(args1 != args3);
  CHECK(args3 != args1);
}

TEST_CASE("Args::empty")
{
  Args args;
  CHECK(args.empty());
  args.push_back("1");
  CHECK(!args.empty());
}

TEST_CASE("Args::size")
{
  Args args;
  CHECK(args.size() == 0);
  args.push_back("1");
  CHECK(args.size() == 1);
  args.push_back("2");
  CHECK(args.size() == 2);
}

TEST_CASE("Args indexing")
{
  const Args args1 = Args::from_string("1 2 3");
  CHECK(args1[0] == "1");
  CHECK(args1[1] == "2");
  CHECK(args1[2] == "3");

  Args args2 = Args::from_string("1 2 3");
  CHECK(args2[0] == "1");
  CHECK(args2[1] == "2");
  CHECK(args2[2] == "3");
}

TEST_CASE("Args::to_argv")
{
  Args args = Args::from_string("1 2 3");
  auto argv = args.to_argv();
  CHECK(std::string(argv[0]) == "1");
  CHECK(std::string(argv[1]) == "2");
  CHECK(std::string(argv[2]) == "3");
  CHECK(argv[3] == nullptr);
}

TEST_CASE("Args::to_string")
{
  CHECK(Args::from_string("a little string").to_string() == "a little string");
}

TEST_CASE("Args operations")
{
  Args args = Args::from_string("eeny meeny miny moe");
  Args more_args = Args::from_string("x y");
  Args no_args;

  SUBCASE("erase_last")
  {
    Args repeated_args = Args::from_string("one two twotwo one two twotwo");

    repeated_args.erase_last("three");
    CHECK(repeated_args == Args::from_string("one two twotwo one two twotwo"));

    repeated_args.erase_last("two");
    CHECK(repeated_args == Args::from_string("one two twotwo one twotwo"));

    repeated_args.erase_last("two");
    CHECK(repeated_args == Args::from_string("one twotwo one twotwo"));

    repeated_args.erase_last("two");
    CHECK(repeated_args == Args::from_string("one twotwo one twotwo"));
  }

  SUBCASE("erase_with_prefix")
  {
    args.erase_with_prefix("m");
    CHECK(args == Args::from_string("eeny"));
  }

  SUBCASE("insert empty args")
  {
    args.insert(2, no_args);
    CHECK(args == Args::from_string("eeny meeny miny moe"));
  }

  SUBCASE("insert non-empty args")
  {
    args.insert(4, more_args);
    args.insert(2, more_args);
    args.insert(0, more_args);
    CHECK(args == Args::from_string("x y eeny meeny x y miny moe x y"));
  }

  SUBCASE("pop_back")
  {
    args.pop_back();
    CHECK(args == Args::from_string("eeny meeny miny"));

    args.pop_back(2);
    CHECK(args == Args::from_string("eeny"));
  }

  SUBCASE("pop_front")
  {
    args.pop_front();
    CHECK(args == Args::from_string("meeny miny moe"));

    args.pop_front(2);
    CHECK(args == Args::from_string("moe"));
  }

  SUBCASE("push_back string")
  {
    args.push_back("foo");
    CHECK(args == Args::from_string("eeny meeny miny moe foo"));
  }

  SUBCASE("push_back args")
  {
    args.push_back(more_args);
    CHECK(args == Args::from_string("eeny meeny miny moe x y"));
  }

  SUBCASE("push_front string")
  {
    args.push_front("foo");
    CHECK(args == Args::from_string("foo eeny meeny miny moe"));
  }

  SUBCASE("replace")
  {
    args.replace(3, more_args);
    args.replace(2, no_args);
    args.replace(0, more_args);
    CHECK(args == Args::from_string("x y meeny x y"));
  }
}

TEST_SUITE_END();
