#include "ModuleBase.h"

#include <iostream>
#include <cmath>

namespace mdd {

    class ModuleMath : public ModuleBase {
        enum class MathOperation : char {
            ADD = 1,
            SUBTRACT = 2,
            MULTIPLY = 3,
            DIVIDE = 4,
            POWER = 5,
            LOGARITHM = 6,
            MINIMUM = 7,
            MAXIMUM = 8,
            LESS_THAN = 9,
            GREATER_THAN = 10
        };
        const std::map<std::string, std::function<double(double, double)>> _ops = {
            {"mutiply", [](double a, double b) {return a * b; }}
        };
        std::function<double(double, double)> operation;
    private:
        MathOperation _operation;
        Property<std::string>::Ptr _operation_prop;

        static std::vector<double> add(const std::vector<double>& val1, const std::vector<double>& val2);
        static std::vector<double> subtract(const std::vector<double>& val1, const std::vector<double>& val2);
        static std::vector<double> multiply(const std::vector<double>& val1, const std::vector<double>& val2);
        static std::vector<double> divide(const std::vector<double>& val1, const std::vector<double>& val2);
        static std::vector<double> power(const std::vector<double>& val1, const std::vector<double>& val2);
        static std::vector<double> logarithm(const std::vector<double>& val1, const std::vector<double>& val2);
        static std::vector<double> minimum(const std::vector<double>& val1, const std::vector<double>& val2);
        static std::vector<double> maximum(const std::vector<double>& val1, const std::vector<double>& val2);
        static std::vector<double> less(const std::vector<double>& val1, const std::vector<double>& val2);
        static std::vector<double> greater(const std::vector<double>& val1, const std::vector<double>& val2);

    public:
        explicit ModuleMath();
        void configure();
        state update() override;
        void setMathOperation(MathOperation operation);
        MathOperation getMathOperation();
    };

    ModuleMath::ModuleMath() {
        std::vector<double> default_val = {1};
        addInput("Value", default_val);
        addInput("Value", default_val);
        addOutput("Value", default_val);
        setType("Math");
        _operation_prop = addSubProperty<std::string>("operation");
    }

    void ModuleMath::configure()
    {
        _operation = ModuleMath::MathOperation::ADD;
        /*if (_operation_prop->Value() == "multiply")
        {
            _operation = ModuleMath::MathOperation::MULTIPLY;
        }
        else if (_operation_prop->Value() == "add")
        {
            _operation = ModuleMath::MathOperation::ADD;
        }
        */
    }

    std::vector<double> ModuleMath::add(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(val1[i] + val2[i]);
            }
            else if (1 == val1.size()) {
                ret.push_back(val1[0] + val2[i]);
            }
            else if (1 == val2.size()) {
                ret.push_back(val1[i] + val2[0]);
            }
        }
        return ret;
    }

    std::vector<double> ModuleMath::subtract(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(val1[i] - val2[i]);
            }
            else if (1 == val1.size()) {
                ret.push_back(val1[0] - val2[i]);
            }
            else if (1 == val2.size()) {
                ret.push_back(val1[i] - val2[0]);
            }
        }
        return ret;
    }

    std::vector<double> ModuleMath::multiply(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(val1[i] * val2[i]);
            }
            else if (1 == val1.size()) {
                ret.push_back(val1[0] * val2[i]);
            }
            else if (1 == val2.size()) {
                ret.push_back(val1[i] * val2[0]);
            }
        }
        return ret;
    }

    std::vector<double> ModuleMath::divide(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(val1[i] / val2[i]);
            }
            else if (1 == val1.size()) {
                ret.push_back(val1[0] / val2[i]);
            }
            else if (1 == val2.size()) {
                ret.push_back(val1[i] / val2[0]);
            }
        }
        return ret;
    }

    std::vector<double> ModuleMath::power(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(pow(val1[i] , val2[i]));
            }
            else if (1 == val1.size()) {
                ret.push_back(pow(val1[0] , val2[i]));
            }
            else if (1 == val2.size()) {
                ret.push_back(pow(val1[i] , val2[0]));
            }
        }
        return ret;
    }

    std::vector<double> ModuleMath::logarithm(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(log(val2[i]) / log(val1[i]));
            }
            else if (1 == val1.size()) {
                ret.push_back(log(val2[i]) / log(val1[0]));
            }
            else if (1 == val2.size()) {
                ret.push_back(log(val2[0]) / log(val1[i]));
            }
        }
        return ret;
    }

    std::vector<double> ModuleMath::minimum(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(std::min(val1[i], val2[i]));
                }
                else if (1 == val1.size()) {
                    ret.push_back(std::min(val1[0], val2[i]));
                }
                else if (1 == val2.size()) {
                    ret.push_back(std::min(val1[i], val2[0]));
                }
            }
            return ret;
    }

    std::vector<double> ModuleMath::maximum(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(std::max(val1[i], val2[i]));
            }
            else if (1 == val1.size()) {
                ret.push_back(std::max(val1[0], val2[i]));
            }
            else if (1 == val2.size()) {
                ret.push_back(std::max(val1[i], val2[0]));
            }
        }
        return ret;
    }

    std::vector<double> ModuleMath::less(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(val1[i] > val2[i]);
            }
            else if (1 == val1.size()) {
                ret.push_back(val1[0] > val2[i]);
            }
            else if (1 == val2.size()) {
                ret.push_back(val1[i] > val2[0]);
            }
        }
        return ret;
    }

    std::vector<double> ModuleMath::greater(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(val1[i] < val2[i]);
            }
            else if (1 == val1.size()) {
                ret.push_back(val1[0] < val2[i]);
            }
            else if (1 == val2.size()) {
                ret.push_back(val1[i] < val2[0]);
            }
        }
        return ret;
    }

    state ModuleMath::update() {
        std::vector<double> ret = getOutput(0)->getValue();

        switch (_operation) {
        case MathOperation::ADD:
                ret = add(getInput(0)->getValue(), getInput(1)->getValue());
                break;

            case MathOperation::SUBTRACT:
                ret = subtract(getInput(0)->getValue(), getInput(1)->getValue());
                break;

            case MathOperation::MULTIPLY:
                ret = multiply(getInput(0)->getValue(), getInput(1)->getValue());
                break;

            case MathOperation::DIVIDE:
                ret = divide(getInput(0)->getValue(), getInput(1)->getValue());
                break;

            case MathOperation::POWER:
                ret = power(getInput(0)->getValue(), getInput(1)->getValue());
                break;

            case MathOperation::LOGARITHM:
                ret = logarithm(getInput(0)->getValue(), getInput(1)->getValue());
                break;

            case MathOperation::MINIMUM:
                ret = minimum(getInput(0)->getValue(), getInput(1)->getValue());
                break;

            case MathOperation::MAXIMUM:
                ret = maximum(getInput(0)->getValue(), getInput(1)->getValue());
                break;

            case MathOperation::LESS_THAN:
                ret = less(getInput(0)->getValue(), getInput(1)->getValue());
                break;

            case MathOperation::GREATER_THAN:
                ret = greater(getInput(0)->getValue(), getInput(1)->getValue());
                break;
        }
        
        return getOutput(0)->setValue(ret);
    }


    void ModuleMath::setMathOperation(MathOperation operation){
        _operation = operation;
    }

    ModuleMath::MathOperation ModuleMath::getMathOperation(){
        return _operation;
    }

}
ADD_GENERATOR(mdd::ModuleMath);

//gdb
//catch throw
//run