#include "bandit/bandit/bandit.h"
// BANDIT NEEDS TO BE INCLUDED FIRST!!!

#include "../../src/parser.h"
#include "../../src/node.h"
#include "../../src/modules.h"
#include "../../src/process_int.h"
#include "../../src/cdo_options.h"
#include "test_module_list.h"
#include <iostream>

using namespace snowhouse;
void
cdoExit()
{
}

unsigned int
getRequiredElements(std::vector<std::string> input)
{
  unsigned int numNodes = 0;
  for (auto x : input)
    {
      if (x != "[" && x != "]" && x != ":")
        {
          numNodes += 1;
        }
    }
  return numNodes;
}

unsigned int
getNumChildren(std::shared_ptr<Parser::Node> root)
{
  if (root->children.size() == 0) return 1;
  unsigned int sum = 1;
  for (auto &c : root->children)
    {
      sum += getNumChildren(c);
    }
  return sum;
}

void
check(std::string description, std::vector<std::string> in, std::string out)
{
  bandit::describe(description, [&]() {
    auto res = Parser::parse(in);
    bandit::it("returned a list with a single root",
               [&]() { AssertThat(res, Is().OfLength(1)); });
    std::string node_structure;
    unsigned numChildrenExpected;
    if (res.size() > 0)
      {
        node_structure = res[0]->to_string();
        numChildrenExpected = getNumChildren(res[0]);
      }
    else
      {
        node_structure = "";
        numChildrenExpected = -1337;
      }
    bandit::it("returned an empty node structure",
               [&]() { AssertThat(node_structure, Equals(out)); });
    bandit::it("returned the correct number of connected nodes", [&]() {
      AssertThat(numChildrenExpected, Equals(getRequiredElements(in)));
    });
  });
}

void
checkNegative(std::string description, std::vector<std::string> in)
{
  bandit::describe(description, [&]() {
    auto res = Parser::parse(in);
    bandit::it("returned a list with no root",
               [&]() { AssertThat(res.size(), Equals(0ul)); });
    std::string node_structure;
    unsigned numChildrenExpected;
    if (res.size() > 0)
      {
        node_structure = res[0]->to_string();
      }
    else
      {
        node_structure = "";
      }
    bandit::it("returned the correct node structure",
               [&]() { AssertThat(node_structure, Equals("")); });
  });
}
void
checkApply(std::string description, std::vector<std::string> in,
           std::string out, unsigned int numChildren)
{
  bandit::describe(description, [&]() {
    auto res = Parser::parse(in);
    bandit::it("returned a list with a single root",
               [&]() { AssertThat(res, Is().OfLength(1)); });
    std::string node_structure;
    unsigned numChildrenExpected;
    if (res.size() > 0)
      {
        node_structure = res[0]->to_string();
        numChildrenExpected = getNumChildren(res[0]);
      }
    else
      {
        node_structure = "";
        numChildrenExpected = -1337;
      }
    bandit::it("returned the correct node structure",
               [&]() { AssertThat(node_structure, Equals(out)); });
    bandit::it("returned the correct number of nodes",
               [&]() { AssertThat(numChildrenExpected, Equals(numChildren)); });
  });
}

go_bandit([]() {
  //==============================================================================
  cdo::progname = "cdo_bandit_test";
  cdo::set_exit_function(cdoExit);
  cdo::set_context_function(process_inq_prompt);

  // cdo::set_debug(1024);
  //-----------------------------Test_01------------------------------------------
  //------------------------------------------------------------------------------
  check("accepted a single operator with in and output files",
        { "-in1_out1", "in", "out" }, "out [ in1_out1 [ in ] ]");
  check("accepted a single operator with 2 in files and a single output file",
        { "-in2_out1", "in1", "in2", "out" }, "out [ in2_out1 [ in1 in2 ] ]");
  check("accepted a single operator with 0 input and a single output",
        { "-in0_out1", "out" }, "out [ in0_out1 ]");

  check("accepted a single operator with 1 input and no output",
        { "-in1_out0", "in" }, "in1_out0 [ in ]");

  check("accepted a single operator with variable input and no output",
        { "-inVariable_out0", "in1", "in2", "in3" },
        "inVariable_out0 [ in1 in2 in3 ]");

  check("accepted a single operator with 2 input and no output",
        { "-in2_out0", "in1", "in2" }, "in2_out0 [ in1 in2 ]");

  bandit::describe("Variable input do accept variable inputs", [&]() {
    check("accepted a single operator with variable inputs (1) and a single "
          "outfile",
          { "-inVariable_out1", "in1", "out" },
          "out [ inVariable_out1 [ in1 ] ]");

    check("accepted a single operator with variable inputs (2) and a single "
          "outfile",
          { "-inVariable_out1", "in1", "in2", "out" },
          "out [ inVariable_out1 [ in1 in2 ] ]");

    check("accepted a single operator with variable inputs (3) and a single "
          "outfile",
          { "-inVariable_out1", "in1", "in2", "in3", "out" },
          "out [ inVariable_out1 [ in1 in2 in3 ] ]");
  });

  check("accepted a multiple nested variable input operators while using "
        "subgroups",
        { "-inVariable_out1", "[", "in1", "-inVariable_out1", "in2", "in3", "]",
          "out" },
        "out [ inVariable_out1 [ in1 inVariable_out1 [ in2 in3 ] ] ]");

  check("accepted a operator with obase feature "
        "subgroups",
        { "-in2_outObase", "f1", "f2", "obase" },
        "obase [ in2_outObase [ f1 f2 ] ]");

  checkApply("apply accepts chains",
        { "-inVariable_out0", "[", "-in1_out1", "-in1_out1", ":", "f1", "f2", "]" },
        "inVariable_out0 [ in1_out1 [ in1_out1 [ f1 ] ] in1_out1 [ in1_out1 [ "
        "f2 ] ] ]",7);

  // DO NOT SOURROUND THE APPLY WITH \" does not work
  checkApply("Old apply keyword works",
             { "-inVariable_out1", "-apply,-in1_out1", "[", "in1", "in2", "in3",
               "]", "out" },
             "out [ inVariable_out1 [ in1_out1 [ in1 ] in1_out1 [ in2 ] "
             "in1_out1 [ in3 ] ] ]",
             8);

  checkApply("Apply symbol works",
             { "-inVariable_out1", "[", "-in1_out1", ":", "in1", "in2", "in3",
               "]", "out" },
             "out [ inVariable_out1 [ in1_out1 [ in1 ] in1_out1 [ in2 ] "
             "in1_out1 [ in3 ] ] ]",
             8);

  bandit::describe("Negative tests", [&]() {
    checkNegative("Missing description",
                  { "-in2_out1", "-in0_out1", "-in0_out1", "-in_out1", "out" });

    checkNegative(
        "error on a single operator with variable inputs (0) and a single "
        "outfile",
        { "-inVariable_out1", "out" });

    checkNegative(
        "multiple variable input operators are not allowed without grouping "
        "feature ",
        { "-inVariable_out1", "-inVariable_out1", "in1", "in2", "out" });

    checkNegative("using 0 output operators as input for other node is caught",
                  { "-in1_out1", "-in1_out0", "out" });

    checkNegative("sub groups cannot overflow",
                  { "-in2_out1", "[", "in1", "in2", "in3", "]", "out" });

    checkNegative("fails on file at pos 1", { "in", "in2" });
    checkNegative("fails on file at pos 1 with other operators follwing",
                  { "in", "in1_out1", "in2", "out" });
    checkNegative("unattached files", { "in1_out1", "in", "in2", "out" });

    checkNegative("empty [ ] are not ignored",
                  { "-in2_out0", "in1", "[", "]", "in2", "[", "]" });
    checkNegative("too many files", { "in2_out1", "in1", "in2", "in3", "out" });

    checkNegative("apply handles 2 in 1 out ",
                  { "inVariable_out1", "[", "in2_out1", ":", "in1", "in2",
                    "in3", "]", "out" });
    checkNegative("missing bracket detected",
                  { "inVariable_out1", "[", "in2_out1", ":", "in1", "in2",
                    "in3", "out" });

    checkNegative("apply finds error with missing bracket ",
                  { "inVariable_out1", "WRONG", "[", "in2_out1", ":", "in1",
                    "in2", "in3", "]", "out" });
    checkNegative("missing bracket detected",
                  { "inVariable_out1", "[", "in2_out1", ":", "in1", "in2",
                    "in3", "out" });
    checkNegative("apply only allows operators with single in and output",
                  { "inVariable_out1", "[", "in1_out1 -topo -in1_out1", ":",
                    "in1", "in2", "out" });
  });
});

//==============================================================================
int
main(int argc, char **argv)
{
  int result = bandit::run(argc, argv);

  return result;
}
