#include <string>
#include <map>
#include <set>
#include <iostream>
#include <functional>
#include <iomanip>
#include <vector>
#include <stack>

#include "node.h"
#include "parser.h"
#include "modules.h"
#include "cdo_output.h"

#include "process_int.h"

constexpr int BREAK = 1;

namespace Parser
{

static int staticParserCounter = 0;
std::stack<std::vector<std::string>::iterator> bracketPositions = {};

#define debug_parser(...) Debug(tab() + "line: " + std::to_string(__LINE__) + " " + __VA_ARGS__)

std::string
tab()
{
  return "|" + std::string(staticParserCounter, '\t');
}

std::string
result_to_string(std::vector<std::shared_ptr<Node>> p_roots, std::string p_text = "returning: ")
{
  for (auto &x : p_roots)
    {
      p_text += x->to_string() + "\n";
    }
  return p_text;
}

class Parser
{

public:
  void
  add_to_root(std::shared_ptr<Node> &p_nodePtr, std::vector<std::shared_ptr<Node>> &p_roots)
  {
    debug_parser("Adding to Roots %s", p_nodePtr->oper);
    p_roots.push_back(p_nodePtr);
  }

  void
  push_on_stack(std::shared_ptr<Node> &p_nodePtr, std::stack<std::shared_ptr<Node>> &p_operatorStack)
  {
    debug_parser("pushing on stack %s", p_nodePtr->oper);
    p_operatorStack.push(p_nodePtr);
  }

  std::vector<std::string>
  generate_tokens(const std::string &p_oper)
  {
    std::vector<std::string> result = {};

    auto end = p_oper.find(' ');
    auto start = 0;
    while (end != std::string::npos)
      {
        auto oper = p_oper.substr(start, end - start);
        result.push_back(oper);
        start = end + 1;
        end = p_oper.find(' ', start);
      }
    auto oper = p_oper.substr(start, end - start);
    result.push_back(oper);
    debug_parser("%s", cdo_argv_to_string(result));

    return result;
  }

  std::shared_ptr<Node>
  create_operator_node(std::vector<std::string>::iterator &p_curentArgument)
  {
    debug_parser("Creating new operator node: %s", *p_curentArgument);

    std::string operatorName = "";
    std::string operatorArguments = "";

    extract_name_and_argument(*p_curentArgument, operatorName, operatorArguments);

    auto moduleIterator = find_module(operatorName);

    if (moduleIterator == get_modules().end())
      {
        auto similarOperators = find_similar_operators(operatorName);
        if (similarOperators.size()) similarOperators = "\nSimilar operators: " + similarOperators;
        throw CdoSyntaxError(p_curentArgument, "Operator not found" + similarOperators);
      }

    auto mod = moduleIterator->second;
    auto newNode = std::make_shared<Node>(p_curentArgument, operatorName, operatorArguments, mod.streamInCnt, mod.streamOutCnt);
    return newNode;
  }

  std::shared_ptr<Node>
  create_file_node(std::vector<std::string>::iterator &p_curentArgument)
  {
    debug_parser("Creating new file node, %s", *p_curentArgument);
    auto newNode = std::make_shared<Node>(p_curentArgument, false);
    return newNode;
  }

  std::shared_ptr<Node>
  create_node(std::vector<std::string>::iterator &p_curentArgument)
  {
    debug_parser("Creating Node for %s", *p_curentArgument);
    return ((*p_curentArgument)[0] == '-') ? create_operator_node(p_curentArgument) : create_file_node(p_curentArgument);
  }

  void
  append_to_parent(std::shared_ptr<Node> &p_node, std::stack<std::shared_ptr<Node>> &p_operator_stack)
  {
    p_operator_stack.top()->append(p_node);
  }

  bool
  is_keyword(const std::string &p_argument)
  {
    std::set<std::string> keywords = { "[", "]", ":" };
    auto funcIter = keywords.find(p_argument);
    auto pos = p_argument.find("apply");
    if (pos == 0 || pos == 1) return true;
    return funcIter != keywords.end();
  }

  void
  handle_node(std::vector<std::string>::iterator &cur_arg, std::vector<std::shared_ptr<Node>> &roots,
              std::stack<std::shared_ptr<Node>> &operator_stack, bool &containsVariableInput)
  {
    debug_parser("handling Node");
    auto node = create_node(cur_arg);
    if (node->numMaxChildren == -1)
      {
        if (containsVariableInput == true)
          {
            throw CdoSyntaxError(cur_arg, "Using two operators with variable input without using sub groups [] is not allowed");
          }
        containsVariableInput = true;
      }

    if (operator_stack.empty())
      {
        add_to_root(node, roots);
        push_on_stack(node, operator_stack);
      }
    else
      {
        append_to_parent(node, operator_stack);
        push_on_stack(node, operator_stack);
      }
  }

  void
  handle_apply(std::vector<std::string>::iterator &cur_arg, std::vector<std::shared_ptr<Node>> &roots,
               std::vector<std::string>::iterator &end)
  {

    bool containsVariableInput = false;
    auto apply_to_these_roots = internal_parse(++cur_arg, end, containsVariableInput);
    auto root_copy = roots[0]->copy();
    if (root_copy->numMaxChildren != 1)
      {
        throw CdoSyntaxError(root_copy->iter, "Only operators with a single in and output allowed");
      }
    roots.clear();
    for (auto &r : apply_to_these_roots)
      {
        // on this level we are in the 2nd part of the [ : ] clause
        // we want to get all the roots from the second part and deep copy
        // then we are here because the end of the clause was found by the
        // deeper parser
        auto new_root = root_copy->copy();
        debug_parser("copied node as additional root: %s ", new_root->oper);

        new_root->add_leaf(r);
        roots.push_back(new_root);
      }
    cur_arg--;
  }

  void
  pop(std::stack<std::shared_ptr<Node>> &p_operator_stack)
  {
    auto node = p_operator_stack.top();
    if (node->has_missing_input())
      {
        debug_parser("number of children = %d", node->children.size());
        throw CdoSyntaxError(node->iter, "Missing Inputs");
      }
    p_operator_stack.pop();
  }

  void
  handle_old_apply(std::vector<std::string>::iterator &cur_arg, std::vector<std::shared_ptr<Node>> &p_roots,
                   std::stack<std::shared_ptr<Node>> &p_operator_stack, std::vector<std::string>::iterator &end)
  {
    debug_parser("handling old apply");
    debug_parser("Input roots: %s", result_to_string(p_roots, "Input roots: "));
    debug_parser("%s", *cur_arg);

    auto currentArgv = (*cur_arg);
    const auto pos = currentArgv.find(',');
    if (pos == std::string::npos)
      {
        throw CdoSyntaxError(cur_arg, "cdo apply: missing argument for apply.");
      }

    auto parameter = currentArgv.substr(pos + 1);
    auto tokens = generate_tokens(parameter);
    std::vector<std::string>::iterator iterBegin = tokens.begin();
    std::vector<std::string>::iterator iterEnd = tokens.end();
    std::stack<std::shared_ptr<Node>> operator_stack = {};

    bool containsVariableInput = false;
    auto local_roots = loop(iterBegin, iterEnd, operator_stack, containsVariableInput);

    debug_parser("%s", result_to_string(local_roots, "local roots: "));
    std::string next_arg = *(cur_arg + 1);
    if (next_arg.find("[") == std::string::npos)
      {
        throw CdoSyntaxError(cur_arg, "Apply keyword without following subgroup");
      }
    bracketPositions.push(cur_arg);

    handle_apply(++cur_arg, local_roots, end);
    debug_parser("%s", result_to_string(local_roots, "Input roots: "));
    if (!p_operator_stack.empty())
      {
        p_operator_stack.top()->append(local_roots);
        pop(p_operator_stack);
      }
    else
      {
        p_roots.insert(p_roots.end(), local_roots.begin(), local_roots.end());
      }
  }

  void
  handle_sub_group(std::vector<std::string>::iterator &cur_arg, std::vector<std::shared_ptr<Node>> &roots,
                   std::stack<std::shared_ptr<Node>> &operator_stack, std::vector<std::string>::iterator &end)
  {
    debug_parser("handling sub group");
    bool hasVariableInput = false;
    auto returned_roots = internal_parse(++cur_arg, end, hasVariableInput);
    if (returned_roots.empty())
      {
        // TODO: fix exception
        throw CdoSyntaxError(cur_arg - 1, "Empty subgroup");
      }

    if (operator_stack.empty())
      {
        debug_parser("handle_sub_group returned an empty operator_stack");
        roots = returned_roots;
      }
    else
      {
        debug_parser("returned root is appended to top");
        operator_stack.top()->append(returned_roots);
        pop(operator_stack);
      }
    debug_parser("poping after brakets");
  }

  int
  handle_keyword(std::vector<std::string>::iterator &cur_arg, std::vector<std::shared_ptr<Node>> &roots,
                 std::stack<std::shared_ptr<Node>> &operator_stack, std::vector<std::string>::iterator &end)
  {
    debug_parser("handling keyword %s", *cur_arg);
    if (*cur_arg == "]")
      {
        if (bracketPositions.empty())
          {
            throw CdoSyntaxError(cur_arg, "Closing bracket without open subgroup");
          }
        bracketPositions.pop();
        return BREAK;  // break the while loop
      }

    if (*cur_arg == "[")
      {
        bracketPositions.push(cur_arg);
        handle_sub_group(cur_arg, roots, operator_stack, end);
        return 0;
      }
    if (*cur_arg == ":")
      {
        handle_apply(cur_arg, roots, end);
        operator_stack = {};
        return BREAK;  // break the while loop
      }
    if ((*cur_arg).find("apply") <= 1)
      {
        handle_old_apply(cur_arg, roots, operator_stack, end);
        return BREAK;
      }
    throw std::invalid_argument("key word " + *cur_arg + "does not exist");
    return 0;
  }

  std::shared_ptr<Node>
  handle_first_operator(std::vector<std::string>::iterator &cur_arg, std::vector<std::shared_ptr<Node>> &roots,
                        std::vector<std::string>::iterator &end, bool &containsVariableInput)
  {
    auto node = create_operator_node(cur_arg);
    if (node->isFile == true)
      {
        throw NodeAttachException(node, "No operator found");
      }

    containsVariableInput = (node->numMaxChildren == -1) ? true : false;

    int numOut = node->numOut;
    if (numOut == -1) numOut = 1;
    if(numOut >= end - cur_arg){

            throw MissingOutFileException("Missing outfile");
    }

    for (auto it = end - numOut; it != end; it++)
      {
        if (is_keyword(*it))
          {
            throw MissingOutFileException("Missing outfile");
          }
        if ((*it)[0] == '-')
          {
            throw MissingOutFileException("Missing outfile: outfile is an operator");
          }

        auto outFileNode = std::make_shared<Node>(it, true);
        add_to_root(outFileNode, roots);
        outFileNode->append(node);
      }
    end -= numOut;
    return node;
  }

  void
  clear_stack(std::stack<std::shared_ptr<Node>> &p_operator_stack)
  {
    debug_parser("clearing stack");
    while (!p_operator_stack.empty())
      {
        auto r = p_operator_stack.top();
        pop(p_operator_stack);
        debug_parser("Node %s checked.", r->oper);
      }
  }

  std::vector<std::shared_ptr<Node>>
  loop(std::vector<std::string>::iterator &p_curentArgument, std::vector<std::string>::iterator &p_end,
       std::stack<std::shared_ptr<Node>> &p_operator_stack, bool p_containsVariableInput)
  {
    staticParserCounter++;
    std::vector<std::shared_ptr<Node>> roots;

    while (p_curentArgument != p_end)
      {
        debug_parser("Current argv: %s", *p_curentArgument);
        if (is_keyword(*p_curentArgument))
          {
            if (handle_keyword(p_curentArgument, roots, p_operator_stack, p_end) == BREAK)
              {
                p_curentArgument++;
                break;
              }
          }
        else
          {
            handle_node(p_curentArgument, roots, p_operator_stack, p_containsVariableInput);
            p_curentArgument++;
          }

        while (!p_operator_stack.empty() && p_operator_stack.top()->is_done())
          {
            p_operator_stack.pop();
          }
      }
    staticParserCounter--;
    return roots;
  }

  std::vector<std::shared_ptr<Node>>
  internal_parse(std::vector<std::string>::iterator &p_curArg, std::vector<std::string>::iterator p_end,
                 bool &p_containsVariableInput)
  {
    std::stack<std::shared_ptr<Node>> operator_stack = {};

    auto roots = loop(p_curArg, p_end, operator_stack, p_containsVariableInput);

    clear_stack(operator_stack);
    debug_parser("%s", result_to_string(roots));

    return roots;
  }

  std::vector<std::shared_ptr<Node>>
  parse(std::vector<std::string> &p_argv)
  {

    auto cur_arg = p_argv.begin();
    auto end = p_argv.end();
    std::vector<std::shared_ptr<Node>> roots = {};
    // auto begin = cur_arg;
    //
    bool containsVariableInput = false;
    auto first_operator = handle_first_operator(cur_arg, roots, end, containsVariableInput);
    auto main_roots = internal_parse(++cur_arg, end, containsVariableInput);
    if (roots.size() == 0)
      {
        roots = { first_operator };
      }
    first_operator->append(main_roots);
    // Check for missing brackets
    if (!bracketPositions.empty())
      {
        throw CdoSyntaxError(bracketPositions.top(), "Bracket not closed");
      }
    // Check for missing input and
    if (first_operator->has_missing_input())
      {
        debug_parser("number of children = %d", first_operator->children.size());
        throw NodeAttachException(first_operator, "Missing Inputs");
      }
    debug_parser("%s", result_to_string(roots));
    return roots;
  }
};

std::pair<std::string, std::string>
build_err_msg(std::vector<std::string> &p_argv, const std::vector<std::string>::iterator &iter)
{

  auto prompt = std::string(process_inq_prompt());
  std::string padding = std::string(prompt.size() + 10, ' ');
  std::string errLine = "";
  bool addPadding = true;
  for (auto it = p_argv.begin(); it < p_argv.end(); it++)
    {
      if (it != iter)
        {
          if (addPadding) padding += std::string((*it).length() + 1, ' ');
          errLine += *it;
        }
      else
        {
          if (addPadding) padding += std::string((*it).length() / 2, ' ');
          addPadding = false;
          errLine += Red("\033[4m" + *it + "\033[0m");
        }
      errLine += " ";
    }
  return std::make_pair(errLine, padding);
}

std::vector<std::shared_ptr<Node>>
parse(std::vector<std::string> p_argv)
{
  Debug(cdo_argv_to_string(p_argv));
  Parser p = Parser();
  try
    {
      auto roots = p.parse(p_argv);
      return roots;
    }
  catch (const NodeAttachException &e)
    {
      auto errMsg = build_err_msg(p_argv, e.iter);
      cdo_abort("%s \n%s^ %s", errMsg.first, errMsg.second, e.what());
    }
  catch (const CdoSyntaxError &e)
    {
      auto errMsg = build_err_msg(p_argv, e.iter);
      cdo_abort("%s \n%s^ %s", errMsg.first, errMsg.second, e.what());
    }

  catch (const MissingOutFileException &e)
    {
      std::string errLine = Red("\033[4m" + p_argv[0] + "\033[0m") + " ";
      auto prompt = std::string(process_inq_prompt());
      std::string padding = std::string(prompt.size() + 10, ' ');
      for (auto it = p_argv.begin() + 1; it < p_argv.end(); it++)
        {
          errLine += *it + " ";
        }
      cdo_abort("%s \n%s^ %s", errLine, padding, e.what());
    }
  return {};
}

}  // namespace Parser
