// bandit needs to be included!!!

#include "bandit/bandit/bandit.h"

#include <string>
#include <vector>
#include <map>

#include <stdlib.h>
#include "../../src/pmlist.h"

class KVTest
{
public:
  std::string msg = "UNINITALIZED";
  std::vector<std::string> input = {};
  int retval = -2;
  size_t num_keys = 0;
  std::vector<size_t> num_vars = {};
  std::map<std::string, std::vector<std::string>> expected = {};
};

void
check_key_and_values(const std::pair<const std::string, std::vector<std::string>> &expected, KVList &KV)
{
  bandit::it(KV.name + " has the right values in the right keys", [&] {
    const KeyValues *ptr = KV.search(expected.first);

    AssertThat(ptr, snowhouse::Is().Not().Null());
    AssertThat(expected.second.size(), snowhouse::Is().EqualTo(ptr->values.size()));

    for (size_t i = 0; i < ptr->values.size(); i++) { AssertThat(ptr->values[i], snowhouse::Is().EqualTo(expected.second[i])); }
  });
}

void
main_check(const KVTest &curTest, KVList &test_obj)
{

  bandit::describe(test_obj.name, [&] {
    int retVal = -2;
    bandit::describe("runs and  (" + std::to_string(curTest.retval) + ")", [&] {
      retVal = test_obj.parse_arguments(curTest.input);
      bandit::it("returns the right exit code", [&] { AssertThat(retVal, snowhouse::Is().EqualTo(curTest.retval)); });
      bandit::it("has the right number of keys", [&] { AssertThat(test_obj.size(), snowhouse::Is().EqualTo(curTest.num_keys)); });
    });

    for (auto &expected : curTest.expected) { check_key_and_values(expected, test_obj); }
  });
}

// TESTS
go_bandit([]() {
  std::vector<KVTest> tests
      = { { .msg = "handles mutliple keys with multiple values",
            .input = { "key1=1.0", "2.0", "key2=2.0", "2.2" },
            .retval = 0,
            .num_keys = 2,
            .num_vars = { 2, 2 },
            .expected = { { "key1", { "1.0", "2.0" } }, { "key2", { "2.0", "2.2" } } } },

          { .msg = "handles missing value after '='", .input = { "key1=" }, .retval = 0, .num_keys = 1, .expected = {} },

          { .msg = "handles missing value after '=' in the second argument",
            .input = { "key1=1.0", "key2=" },
            .retval = 0,
            .num_keys = 2,
            .expected = { { "key1", { "1.0" } }, { "key2", {} } } },

          { .msg = "handles missing '='", .input = { "key12.0" }, .retval = -1, .num_keys = 0, .expected = {} },

          { .msg = "handles missing '=' in the second value",
            .input = { "key=12.0", "key2" },
            .retval = 0,
            .num_keys = 1,
            .expected = { { "key", { "12.0", "key2" } } } } };

  bandit::describe("KvList parse_arguments", [&]() {
    for (auto &curTest : tests)
      {
        bandit::describe(curTest.msg, [&] {
          KVList kv;
          kv.name = "parse_arguments test";
          main_check(curTest, kv);
        });
      }
  });

  bandit::describe("KvList search", [&]() {
    KVList kvlist;
    auto test = tests[0];
    auto retVal = kvlist.parse_arguments(test.input);
    if (retVal != 0) { AssertThat(retVal, snowhouse::Is().EqualTo(0)); }
    bandit::describe("KvList search returns correct result", [&]() {
      auto resKey1 = kvlist.search("key1");
      bandit::describe("finds both keys and the correct values", [&]() {
        bandit::it("finds the first keys", [&]() { AssertThat(resKey1->key, snowhouse::Is().EqualTo("key1")); });
        bandit::it("contains the right values",
                   [&]() { AssertThat(resKey1->values, snowhouse::EqualsContainer(test.expected["key1"])); });
        auto resKey2 = kvlist.search("key2");
        bandit::it("finds the second keys", [&]() { AssertThat(resKey2->key, snowhouse::Is().EqualTo("key2")); });
        bandit::it("contains the right values",
                   [&]() { AssertThat(resKey2->values, snowhouse::EqualsContainer(test.expected["key2"])); });
      });
    });
  });
  /* append interface removed
    bandit::describe("KvList append", [&]() {
      KVList kvlist;
      std::vector<std::string> to_be_inserted = { "1", "2" };
      std::vector<std::string> to_be_appended = { "3", "4" };
      std::string key = "key1";
      bandit::it("adds the values to a new key when no key was found",
                 [&]() { kvlist.append(key, to_be_inserted); });
      bandit::it("adds the values to the right key",
                 [&]() { kvlist.append(key, to_be_appended); });
    });
  */
  bandit::describe("KvList append", [&]() {
    KVList kvlist;
    std::string key = "key1";
    std::string false_key = "does_not_exist";

    auto retVal = kvlist.parse_arguments(tests[0].input);
    (void) retVal;
    bandit::describe("removes given key", [&]() {
      bandit::it("has size 2 before remove", [&]() { AssertThat(kvlist.size(), snowhouse::Is().EqualTo(tests[0].num_keys)); });
      kvlist.remove(key);
      bandit::it("does no longer contain the key:" + key, [&]() { AssertThat(kvlist.search(key), snowhouse::Is().Null()); });
      bandit::it("has size 1 after remove", [&]() { AssertThat(kvlist.size(), snowhouse::Is().EqualTo(1ul)); });
    });
    bandit::describe("can handle removing a non existing key", [&]() {
      auto before = kvlist.size();
      kvlist.remove(false_key);
      auto after = kvlist.size();
      bandit::it("did not insert another key on accident", [&]() { AssertThat(kvlist.search(false_key), snowhouse::Is().Null()); });
      bandit::it("has not removed anything (size is the same)", [&]() { AssertThat(before, snowhouse::Is().EqualTo(after)); });
    });
  });
});
#define EXCEPTION_EXTRA_INFO = 1;

int
main(int argc, char **argv)
{
  std::vector<char *> argv_v(argv, argv + argc);
  std::string reporter = "--reporter=spec";
  argv_v.push_back(&reporter[0]);
  int result = bandit::run(argc + 1, argv_v.data());

  return result;
}
