#include "ModuleBase.h"
#include <algorithm>

namespace mdd {

    size_t ModuleBase::getNumInputs(){
        return inputs.size();
    }

    size_t ModuleBase::getNumOutputs() {
        return outputs.size();
    }


    std::shared_ptr<IInput> ModuleBase::getInput(size_t index) {
        if (index <= inputs.size())
        {
            return inputs[index];
        }
        return nullptr;
    }

    std::shared_ptr<IInput> ModuleBase::getInput(const json& jid) {
        std::string sid = jid["key"].get<std::string>() + std::to_string(jid["appendix"].get<int>());
        for(auto& input : inputs){
            if (auto in_ptr = input)
            {
                if (in_ptr->getID() == sid) {
                    return in_ptr;
                }
            }
        }
        return nullptr;
    }

    std::shared_ptr<IOutput> ModuleBase::getOutput(size_t index) {
        if (index <= outputs.size())
        {
            return outputs[index];
        }
        return nullptr;
    }

    std::shared_ptr<IOutput> ModuleBase::getOutput(const json& jid) {
        std::string sid = jid["key"].get<std::string>() + std::to_string(jid["appendix"].get<int>());
        for (auto& output : outputs) {
            if (auto out_ptr = output)
            {
                if (out_ptr->getID() == sid) {
                    return out_ptr;
                }
            }
        }
        return nullptr;
    }

    std::vector<std::shared_ptr<IInput>> ModuleBase::getOptimizableInputs() {
        std::vector<std::shared_ptr<IInput>> ret;
        for (size_t i = 0; i < inputs.size(); i++)
        {
            if (auto in_ptr = inputs[i])
            {
                if (in_ptr->isOptimizable())
                {
                    ret.push_back(in_ptr);
                }
            }
        }
        
        return ret;
    }

    std::vector<std::shared_ptr<IOutput>> ModuleBase::getOptimizableOutputs() {
        std::vector<std::shared_ptr<IOutput>> ret;
        for (size_t i = 0; i < outputs.size(); i++)
        {
            if (auto out_ptr = outputs[i])
            {
                if (out_ptr->isOptimizable())
                {
                    ret.push_back(out_ptr);
                }
            }
        }
        
        return ret;
    }

    ModuleBase::ModuleBase(const std::string& base_config)
    {
        json jparse = json::parse(base_config);
        if (jparse.contains("configure"))
        {
            _base_config = jparse;
        }
        else {
            _base_config["configure"] = jparse;
        }
    }
    bool ModuleBase::configure(const json& config) {
        json jconfig = config;
        bool res = true;
        auto jit = jconfig.find("GUI");
        if (jit != jconfig.end())
        {
            _base_config["GUI"].merge_patch(jit.value());
        }
        jit = jconfig.find("configure");
        if (jit != jconfig.end()) {
            res = configureChild(jit.value());
        }
        jit = jconfig.find("appendix");
        if (jit != jconfig.end()) {
            setAppendix(jit.value().get<int>());
        }
        jit = jconfig.find("key");
        if (jit != jconfig.end()) {
            key = jit.value().get<std::string>();
        }
        return res;
    }
   
    bool configureThis(const json& config) {
        return true;
    }

    const json& ModuleBase::getConfiguration()
    {
        return _base_config;
    }

    std::string ModuleBase::getType() {
        return  type;
    }

    void ModuleBase::setParent(std::shared_ptr<IModule> parent) {
        _parent = parent;
    }

    std::vector<std::string> ModuleBase::getParentID() {
        std::vector<std::string> ret;
        if (auto parent_ptr = _parent.lock()) {
            ret = parent_ptr->getParentID();
            ret.push_back(parent_ptr->getID());
        }
        return ret;
    }

    std::string ModuleBase::getGeneratorKey() {
        return  key;
    }

    std::string ModuleBase::setAppendix(int appendix) {
        _appendix = appendix;
        return  getID();
    }

    int ModuleBase::getAppendix() {
        return _appendix;
    }

    std::string ModuleBase::getID() {
        return key + std::to_string(_appendix);
    }

    void ModuleBase::load(const json& j)
    {
        configure(j);

        auto it = j.find("inputs");
        if (it!=j.end())
        {
            const size_t start_counter = inputs.size();
            const json& jin = it.value();
            std::vector<double> default_val = { 1 };
            for (size_t i = start_counter; i < jin.size(); i++)
            {
                inputs.push_back(std::make_shared<Input>("Value", 0, default_val));
                inputs.back()->load(jin[i]);
            }
            for (size_t i = 0; i < jin.size(); i++)
            {
                inputs[i]->setParent(shared_from_this());
                inputs[i]->configure(jin[i]);
            }
        }

        it = j.find("outputs");
        if (it != j.end())
        {
            const size_t start_counter = outputs.size();
            const json& jout = it.value();
            for (size_t i = start_counter; i < j["outputs"].size(); i++)
            {
                outputs.push_back(std::make_shared<Output>( "Value", 0));
                outputs.back()->load(jout[i]);
            }
            for (size_t i = 0; i < jout.size(); i++)
            {
                outputs[i]->setParent(shared_from_this());
                outputs[i]->configure(jout[i]);
            }
        }
    }
    json ModuleBase::dump()
    {
        json jdump;
        jdump = getConfiguration();
        jdump["ID"] = getIdentifier();
        jdump["type"] = type;
        jdump["key"] = key;

        jdump["inputs"].clear();
        jdump["outputs"].clear();
        
        for (auto& in : inputs)
        {
            in->setParent(shared_from_this());
            jdump["inputs"].push_back(in->dump());
        }

        for (auto& out : outputs)
        {
            out->setParent(shared_from_this());
            jdump["outputs"].push_back(out->dump());
        }

        return jdump;
    }

    void ModuleBase::disconnect() {
        for (size_t i = 0; i < inputs.size(); i++)
        {
            if (auto in_ptr = inputs[i])
            {
                in_ptr->disconnect();
            }
        }
        for (size_t i = 0; i < outputs.size(); i++)
        {
            if (auto out_ptr = outputs[i])
            {
                out_ptr->disconnect();
            }
        }
    }

    json ModuleBase::getIdentifier() {
        json jID;
        jID["key"] = key;
        jID["appendix"] = getAppendix();
        jID["type"] = type;
        jID["prefix"] = getParentID(); 
        return jID;
    }
}