#include <map>
#include <string>
#include <stdexcept>
#include "tpx3_hit.h"
#include "tpx4_hit.h"
#pragma once
#include "macros/clusterer_shared_api.h"

namespace clustering
{
  template <typename T>
  struct tag
  {
    using hit_type = T;
  };

  // the class stores arguments for each node in the computational architecture
  // by default it support four data types of arguments (int, double, bool and
  // string

  class node_args
  {

  public:
        node_args() = default;
    // Copy constructor
        node_args(const node_args&) = default;

        // Move constructor
        node_args(node_args&&) = default;

        // Copy assignment
        node_args& operator=(const node_args&) = default;

        // Move assignment
        node_args& operator=(node_args&&) = default;

    bool exists(const std::string & arg) const
    {
      return args_data.find(arg) != args_data.end() && args_data.at(arg).size() > 0;
    }
    template <typename arg_type>
    inline arg_type get_arg(const std::string &arg_name) const
    {
      // TODO FIXME this assert fails
      //  By default, if no template parameter specified we return
      // static_assert(std::is_integral<arg_type>::value, "argument must be an integral type:");
      std::stringstream ss(args_data.at(arg_name));
      arg_type value;
      ss >> value;

      // Check for errors (e.g., failed conversion or trailing invalid characters)
      if (ss.fail() || !ss.eof())
      {
        throw std::invalid_argument("Invalid input argument value: " + args_data.at(arg_name));
      }
      return value;
    }

    // default values for parameters

    static CLUSTERER_SHARED_API node_args load_tpx3_args(const std::string &filename = "");
    static CLUSTERER_SHARED_API node_args load_tpx4_args(const std::string &filename = "");
    template <typename hit_type>
    node_args(const tag<hit_type> &tag, const std::map<std::string, std::string> &data)
    {
      
      args_data["attribute_frame_length_s"] = "0";
      args_data = data;
      set_hidden_args();
      if (!get_arg<bool>("multichip"))
      {
        args_data["chip_sensor_size_x"] = std::to_string(hit_type::detector_type::size_x());
        args_data["chip_sensor_size_y"] = std::to_string(hit_type::detector_type::size_y());
      }
      validate_args(tag);
    }

    inline const std::string &at(const std::string &arg_name) const
    {
  
      return args_data.at(arg_name);
    }
    // can be used for modification from outside
    inline std::string &operator[](const std::string &arg_name)
    {
      return args_data[arg_name];
    }
  private:
    void set_hidden_args();
    template <typename hit_type>
    void validate_resources(tag<hit_type> tag);
    template <typename hit_type>
    void validate_args(tag<hit_type> tag);
    void validate_domain();
    static node_args load_default_tpx3_args();
    static node_args load_default_tpx4_args();
    static std::map<std::string, std::string> load_dict_from_file(const std::string &filename);
    // safe accessor, throws exception on not_found

    std::map<std::string, std::string> args_data;
    inline void check_existence(const std::string &arg_name) const
    {
      if (args_data.count(arg_name) == 0)
      {
        throw std::invalid_argument("Argument '" + arg_name + "' not found");
      }
    }
  };
  // parse string argument
  template <>
  inline std::string node_args::get_arg<std::string>(const std::string &arg_name) const
  {
    std::cout << "Returning string arg " << arg_name << " " <<args_data.at(arg_name) << std::endl; 
    check_existence(arg_name);
    return args_data.at(arg_name);
  };
  // parse boolean argument
  template <>
  inline bool node_args::get_arg<bool>(const std::string &arg_name) const
  {
    if(args_data.find(arg_name) == args_data.end())
      return false;
    return args_data.at(arg_name) == "true" ? true : false;
  };
  template <>
    inline std::pair<double, double> node_args::get_arg<std::pair<double, double>>(const std::string &arg_name) const
    {
        check_existence(arg_name);
        std::string interval_str = args_data.at(arg_name);
        interval_str.erase(std::remove_if(interval_str.begin(), interval_str.end(), [](unsigned char c)
                                          { return std::isspace(c); }),
                           interval_str.end());
        auto sep_pos = interval_str.find(':');
        if (sep_pos == std::string::npos)
        {
            throw std::invalid_argument("malformated filter interval, use ':' as a delimiter e.g. '2.5:30.1'");
        }
        double min_val = std::stod(interval_str.substr(0, sep_pos));
        double max_val = std::stod(interval_str.substr(sep_pos + 1));
        return std::make_pair(min_val, max_val);
    };
}