#include "ModuleBase.h"

#include <iostream>
#include <cmath>
#include <json.hpp>

namespace mdd {

    class ModuleMath : public ModuleBase {
    private:
        const std::map<std::string, std::function<double(double, double)>> _ops = {
            {"add", [](double a, double b) {return a + b; }},
            {"subtract", [](double a, double b) {return a - b; }},
            {"multiply", [](double a, double b) {return a * b; }},
            {"divide", [](double a, double b) {return a / b; }},
            {"power", [](double a, double b) {return pow(a, b); }},
            {"logarithm", [](double a, double b) {return log(b) / log(a); }},
            {"minimum", [](double a, double b) {return std::min(a, b); }},
            {"maximum", [](double a, double b) {return std::max(a, b); }},
            {"lesser", [](double a, double b) {return a > b; }},
            {"greater", [](double a, double b) {return a < b; }}
        };
        std::function<double(double, double)> _operation;

        std::vector<double> applyOperation(const std::vector<double>& val1, const std::vector<double>& val2);
        bool configureChild(const json& config) override;

    public:
        ModuleMath();
        state update() override;
    };

    ModuleMath::ModuleMath()
        : ModuleBase(R"JSON(
        {"configure":{
            "operation":{
            "value":"add",
            "options":  [
                            "add",
                            "subtract",
                            "multiply",
                            "divide",
                            "power",
                            "logarithm",
                            "minimum",
                            "maximum",
                            "lesser", 
                            "greater"
                        ]
            }
        },
        "GUI":{
            "name":{"value":"Math"},
            "color":{"value":"#b91203"}
        }
        })JSON")
    {
        _ops.at("add");
        _operation = _ops.find("add")->second;
        //std::vector<double> default_val = {1};
        inputs.push_back(std::make_shared<Input>("Value", 0));
        inputs.push_back(std::make_shared<Input>("Value", 1));
        outputs.push_back(std::make_shared<Output>("Value", 0));
        key = "ModuleMath";
    }

    bool ModuleMath::configureChild(const json& config)
    {
        auto jit = config.find("operation");
        if (jit != config.end())
        {
            const std::string op = jit.value()["value"].get<std::string>();
            auto it = _ops.find(op);
            if (it != _ops.end())
            {
                _operation = it->second;
                _base_config["configure"]["operation"]["value"] = op;
                return true;
            }
        }
        return false;
    }

    std::vector<double> ModuleMath::applyOperation(const std::vector<double>&val1, const std::vector<double>&val2) {
        std::vector<double> ret; //= json::array();
        size_t length= std::max(val1.size(), val2.size());
        for (int i = 0; i < length; i++) {
            if (val1.size() == val2.size()) {
                ret.push_back(_operation(val1[i], val2[i]));
            }
            else if (1 == val1.size()) {
                ret.push_back(_operation(val1[0], val2[i]));
            }
            else if (1 == val2.size()) {
                ret.push_back(_operation(val1[i], val2[0]));
            }
        }
        return ret;
    }

    state ModuleMath::update() {
        std::vector<double> ret = applyOperation(getInput(0)->getValue(), getInput(1)->getValue());
        return getOutput(0)->setValue(ret);
    }

}

//gdb
//catch throw
//run