#include "module_info.h"
#include "modules.h"
#include "mpmo_color.h"
#include "modules.h"
#include "util_string.h"
#include "factory.h"
#include <algorithm>
#include <iostream>
#include <functional>
#include <string>

typedef std::function<bool(const CdoModule &mod)> ModuleQuery;

bool
ModListOptions::requested(const std::string &name)
{
  return opt[name];
}

bool
ModListOptions::mod_info_requested()
{
  return (operInfoRequested || printAll || requested(s_obase) || requested(s_arbIn) || requested(s_filesOnly)
          || requested(s_onlyFirst) || requested(s_arbIn) || requested(s_noOutput));
}

bool
ModListOptions::parse_request(const std::string &requestString)
{
  auto all = true;
  const auto splitString = split_with_seperator(requestString, ',');

  if (requestString.size() > 0)
    {
      all = false;
      for (size_t i = 0, n = splitString.size(); i < n; ++i)
        {
          auto it = Factory::find_module(splitString[i]);
          if (it != Factory::get().end())
            {
              auto &module = Factory::get_module(it);
              operInfoRequested = true;
              std::cerr << splitString[i] << ": " << module.toString() << std::endl;
            }
          else
            {
              if (opt.find(splitString[i]) != opt.end()) { opt[splitString[i]] = 1; }
              else
                {
                  std::cerr << "option " << splitString[i] << " not found" << std::endl;
                  return false;
                }
            }
        }
    }
  printAll = all;

  return true;
}

std::string
get_operator_description(const std::string &p_current_op_name, const CdoHelp &p_help)
{
  std::string description = "";
  if (p_help.empty()) return description;

  // search for operator section

  auto it = std::find_if(begin(p_help), end(p_help), [&](const auto &l) { return l.find("OPERATORS") != std::string::npos; });
  // if no operator section is found
  if (it == end(p_help))
    {
      std::string name_section = std::string(p_help[0]);
      it = std::find_if(begin(p_help), end(p_help),
                        [&](const auto &l) { return l.find("    " + p_current_op_name) != std::string::npos; });

      if (it != end(p_help))
        {
          name_section += *it;
          description = name_section.substr(name_section.find_first_of('-') + 2, name_section.size());
        }
    }
  else
    {
      it = std::find_if(++it, end(p_help),
                        [&](const auto &l) { return l.find("    " + p_current_op_name + " ") != std::string::npos; });
      if (it != p_help.end())
        {
          std::string line = std::string(*it);
          auto pos = line.find("    " + p_current_op_name + " ");
          if (pos != std::string::npos)
            {
              auto op_name_start = line.find_first_not_of(" \t");

              description = line.substr(line.find_first_not_of(" \t", op_name_start + p_current_op_name.size()), line.size());
            }
        }
    }

  return description;
}

// helper function for setting the spacing in operator_print_list
static std::string
get_spacing_for(int p_space, const std::string &str)
{
  std::string spacing = "";
  for (int i = str.size(); i <= p_space; ++i) spacing += " ";
  return spacing;
}

static std::string
operatorGetShortInfoString(std::string &current_op_name, const CdoModule &p_module)
{
  std::string shortInfo = current_op_name;
  int alias_index = p_module.is_alias(current_op_name);
  if (-1 != alias_index)
    {
      shortInfo += std::string(get_spacing_for(16, current_op_name) + "--> " + p_module.aliases[alias_index].original);
    }
  else if (!p_module.get_help(current_op_name).empty())
    {
      // add spaceing and saving output line to the output list
      const auto description = get_operator_description(current_op_name, p_module.get_help(current_op_name));
      shortInfo += get_spacing_for(16, current_op_name) + description;
    }
  std::string in_out_info
      = "(" + std::to_string(p_module.get_stream_in_cnt()) + "|" + std::to_string(p_module.get_stream_out_cnt()) + ")";
  shortInfo += get_spacing_for(90, shortInfo) + in_out_info;
  return shortInfo;
}

void
operator_print_list(std::function<bool(const CdoModule &)> selectionCriteria)
{
  std::vector<std::string> output_list;

  for (auto &current_op_name : Factory::get_sorted_operator_name_list())
    {
      const CdoModule &current_module = Factory::get_module(current_op_name);
      if (selectionCriteria(current_module)) { output_list.push_back(operatorGetShortInfoString(current_op_name, current_module)); }
    }
  // print generated output list
  for (const std::string &str : output_list) { std::cout << str << std::endl; }
}

void
operator_print_list(ModListOptions &p_opt)
{
  set_text_color(stderr, GREEN);

  if (p_opt.printAll == true)
    {
      operator_print_list([](const CdoModule &) { return true; });
    }
  else
    {

      ModuleQuery defaultModuleQuery = [](const CdoModule &) -> bool { return false; };
      ModuleQuery runquestDefaultModuleQuery = [](const CdoModule &) -> bool { return true; };

      // clang-format off
      ModuleQuery hasObase  = p_opt.requested(s_obase)     ? [](const CdoModule &mod) -> bool { return mod.get_stream_out_cnt() == -1;        } : defaultModuleQuery;
      ModuleQuery hasNoOut  = p_opt.requested(s_noOutput)  ? [](const CdoModule &mod) -> bool { return mod.get_stream_out_cnt() ==  0;        } : defaultModuleQuery;
      ModuleQuery hasArb    = p_opt.requested(s_arbIn)     ? [](const CdoModule &mod) -> bool { return mod.get_stream_in_cnt()  == -1;        } : defaultModuleQuery;
      ModuleQuery filesOnly = p_opt.requested(s_filesOnly) ? [](const CdoModule &mod) -> bool { return mod.get_pos_restriction() == FilesOnly; } : defaultModuleQuery;
      ModuleQuery onlyFirst = p_opt.requested(s_onlyFirst) ? [](const CdoModule &mod) -> bool { return mod.get_pos_restriction() == OnlyFirst; } : defaultModuleQuery;
      // clang-format on

      operator_print_list([&](const CdoModule &mod) {
        return (hasObase(mod) || hasArb(mod) || hasNoOut(mod) || filesOnly(mod) || onlyFirst(mod));
      });
    }

  reset_text_color(stderr);

  return;
}

std::vector<std::string>
get_no_output_operator_list()
{
  std::vector<std::string> names;
  auto &factory = Factory::get();
  for (auto &factory_entry : factory)
    {
      auto &module = Factory::get_module(factory_entry.first);
      if (module.mode == 1 && module.constraints.streamOutCnt == 0) { names.push_back(factory_entry.first); }
    }
  std::sort(names.begin(), names.end());

  return names;
}

void
operatorPrintAll(void)
{
  int number_of_chars = 0;
  std::string tab = "   ";
  int tab_width = tab.size();
  // using a set because it sorts the operators alphabetically on its own
  std::vector<std::string> sorted_operator_names = Factory::get_sorted_operator_name_list();

  std::cout << tab;
  for (const auto &operatorName : sorted_operator_names)
    {
      if (number_of_chars > 85)
        {
          number_of_chars = tab_width;
          std::cerr << std::endl << tab;
        }

      std::cerr << " " << operatorName;
      number_of_chars += 1 + operatorName.size();
    }

  std::cerr << std::endl;
}

void
operator_print_list(bool print_no_output)
{
  std::vector<std::string> output_list = print_no_output ? get_no_output_operator_list() : Factory::get_sorted_operator_name_list();

  auto list_length = output_list.size();

  // help variables

  for (size_t out_list_idx = 0; out_list_idx < list_length; out_list_idx++)
    {
      const std::string current_op_name = output_list[out_list_idx];
      auto &current_module = Factory::get_module(current_op_name);
      if (current_module.is_alias(current_op_name) != -1)
        {
          output_list[out_list_idx] += get_spacing_for(16, current_op_name) + "--> " + Factory::get_original(current_op_name);
        }
      else if (current_module.get_help(current_op_name).empty())
        {
          // add spaceing and saving output line to the output list
          auto description = get_operator_description(current_op_name, current_module.get_help(current_op_name));
          output_list[out_list_idx] += get_spacing_for(16, current_op_name) + description;
        }
      std::string in_out_info = " (" + std::to_string(current_module.constraints.streamOutCnt) + "|"
                                + std::to_string(current_module.constraints.streamOutCnt) + ")";
      output_list[out_list_idx] += get_spacing_for(90, output_list[out_list_idx]) + in_out_info;
    }
  // print generated output list
  for (const std::string &str : output_list) { std::cout << str << std::endl; }
}

void
cdo_print_help(const std::string &p_operator_name)
{
  auto it
      = Factory::find(p_operator_name, [&p_operator_name]() { cdo_abort("%s", Factory::err_msg_oper_not_found(p_operator_name)); });
  const CdoHelp &help = Factory::get_help(it);
  if (help.empty())
    fprintf(stderr, "No help available for this operator!\n");
  else
    {
      for (size_t i = 0; i < help.size(); ++i)
        {
          auto doPrint = !(help[i][0] == '\0' && help[i + 1][0] == ' ');
          if (doPrint)
            {
              auto line = help[i];
              auto useBold
                  = (color_enabled()
                     && (line == "NAME" || line == "SYNOPSIS" || line == "DESCRIPTION" || line == "OPERATORS" || line == "NAMELIST"
                         || line == "PARAMETER" || line == "ENVIRONMENT" || line == "NOTE" || line == "EXAMPLES"));
              if (useBold) set_text_color(stdout, BRIGHT);
              std::cout << line << "\n";
              if (useBold) reset_text_color(stdout);
            }
        }
    }
}
