/*
  This file is part of CDO. CDO is a collection of Operators to manipulate and analyse Climate model Data.

  Author: Uwe Schulzweida
          Oliver Heidmann

*/
#ifndef FACTORY_H
#define FACTORY_H

#include <vector>
#include <iostream>
#include <map>
#include <memory>
#include <functional>

#include "cdo_module.h"
#include "cdo_output.h"
#include "process.h"

namespace Factory
{
// string -> pair(CdoModule,std::function)
typedef std::function<std::shared_ptr<Process>(int, const std::string &, const std::vector<std::string> &)> ModuleConstructor;

struct FactoryEntry
{
  const CdoModule &module;
  Factory::ModuleConstructor constructor;
  ArgumentHandler argHandlers;

  FactoryEntry(const CdoModule &mod, Factory::ModuleConstructor con, ArgumentHandler &p_argHandlers)
      : module(mod), constructor(con), argHandlers(p_argHandlers)
  {
  }
};
typedef std::map<std::string, FactoryEntry> OperatorMap;

std::string err_msg_oper_not_found(const std::string &operatorname);

std::string find_similar_operators(const std::string &operatorName);

std::vector<std::string> get_module_operator_names(const std::string &module_name);

std::string get_original(const std::string &operatorName);

std::vector<std::string> get_sorted_operator_name_list();

OperatorMap &get();  // Factory::get()

OperatorMap::iterator find_module(const std::string &operatorName);
OperatorMap::iterator find(const std::string &p_opername);
OperatorMap::iterator find(const std::string &p_opername, std::function<void()> p_onError);

const CdoModule &get_module(const std::string &p_operName);
const CdoModule &get_module(const OperatorMap::iterator &it);

ModuleConstructor get_constructor(const std::string &p_operName);
ModuleConstructor get_constructor(const OperatorMap::iterator it);

const CdoHelp &get_help(const std::string &p_operName);
const CdoHelp &get_help(OperatorMap::iterator p_it);
};  // namespace Factory

template <typename T>
struct RegisterEntry
{
  Factory::ModuleConstructor
  create_constructor(const CdoModule &mod)
  {
    return
        [&mod](int p_ID, const std::string &p_operName, const std::vector<std::string> &p_operatorArguments) -> std::shared_ptr<T> {
          Debug(FACTORY, "Creating process via factory function, %d = ID, %s = name, %s = mod_name", p_ID, p_operName, mod.name);
          auto new_process = std::make_shared<T>(p_ID, p_operName, p_operatorArguments, mod);
          return new_process;
        };
  }
  void
  register_operator(const CdoModule &mod, std::string &p_oper_name, ArgumentHandler &arghandler)
  {
    Factory::get().insert(std::make_pair(p_oper_name, Factory::FactoryEntry(mod, create_constructor(mod), arghandler)));
  }

public:
  RegisterEntry(CdoModule &module, ArgumentHandler arghandler = ArgumentHandler())
  {
    for (auto &oper : module.operators) { register_operator(module, oper.name, arghandler); }
    for (auto &alias : module.aliases) { register_operator(module, alias.alias, arghandler); }
  };
};
#endif
