#include "ProcessorBase.h"

namespace mdd{
    ProcessorBase::ProcessorBase(const std::string& base_config)
    {
        json jparse = json::parse(base_config);
        if (jparse.contains("configure"))
        {
            _base_config = jparse;
        }
        else {
            _base_config["configure"] = jparse;
        }
    }

    bool ProcessorBase::configure(const json& config) {
        json jconfig = config;
        bool sucess = false;
        auto jit = jconfig.find("GUI");
        if (jit != jconfig.end())
        {
            _base_config["GUI"].merge_patch(jit.value());
        }
        jit = jconfig.find("params");
        if (jit != jconfig.end())
        {
            
            auto jit2 = jit.value().find("inputs");
            if (jit2 != jit.value().end())
            {
                for (size_t i = 0; i < jit.value().size(); i++)
                {
                    inputs.push_back(std::make_shared<Parameter>());
                    if (auto input_ptr = inputs.back())
                    {
                        input_ptr->setParent(shared_from_this());
                        input_ptr->setName(jit2.value()["type"].get<std::string>());
                        input_ptr->setAppendix(jit2.value()["appendix"].get<int>());
                    }
                }
                sucess = true;
            }

            jit2 = jit.value().find("outputs");
            if (jit2 != jit.value().end())
            {
                for (size_t i = 0; i < jit.value().size(); i++)
                {
                    outputs.push_back(std::make_shared<Parameter>());
                    if (auto output_ptr = outputs.back())
                    {
                        output_ptr->setParent(shared_from_this());
                        output_ptr->setName(jit2.value()["type"].get<std::string>());
                        output_ptr->setAppendix(jit2.value()["appendix"].get<int>());
                    }
                }
                sucess = true;
            }
        }

        jit = jconfig.find("modules");
        if (jit != jconfig.end()) {
            auto regi = Registration();
            for (size_t i = 0; i < jit.value().size(); i++)
            {
                const json& jmodule = jit.value()[i];
                addModule(regi.generateModule(jmodule["id"].get<std::string>()));
                if (auto module_ptr = modules.back())
                {
                    module_ptr->setParent(shared_from_this());
                    module_ptr->setAppendix(jmodule["appendix"].get<int>());
                    module_ptr->load(jmodule["load"]);
                }
            }
            sucess = true;
        }

        jit = jconfig.find("connections");
        if (jit != jconfig.end()) {
            for (size_t i = 0; i < jit.value().size(); i++)
            {
                const json& jconn = jit.value()[i];
                connect(jconn["output"].get<std::string>(), jconn["inputs"].get<std::vector<std::string>>());
            }
            sucess = true;
        }

        jit = jconfig.find("configure");
        if (jit != jconfig.end()) {
            sucess = configureChild(jit.value());
        }
        return sucess;
    }

    bool ProcessorBase::configureChild(const json& config) {
        return true;
    }

    const json& ProcessorBase::getConfiguration() {
        //update module apendix ? 
        return _base_config;
    }

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

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

    std::vector<std::string> ProcessorBase::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 ProcessorBase::getGeneratorKey() {
        return  key;
    }

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

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

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


    std::string ProcessorBase::addModule(std::shared_ptr<IModule> module){
        modules.emplace_back(module);
        if (auto module_ptr = modules.back())
        {
            module_ptr->setParent(shared_from_this());
            module_ptr->setAppendix(module_counter++);
            return module_ptr->getID();
        }
        return "";
    }

    void ProcessorBase::removeModule(std::shared_ptr<IModule> module)
    {
        for (auto it = modules.begin(); it != modules.end(); ++it) {
            if ((*it) == module)
            {
                (*it)->disconnect();
                modules.erase(it);
                return;
            }
        }
    }

    json ProcessorBase::addInput(std::shared_ptr<IInput> input) {
        auto param_ptr = std::make_shared<Parameter>();
        param_ptr->setName(input->getName());
        param_ptr->getOutput(0)->connect(input);
        getInputParams().push_back(param_ptr);
        param_ptr->setKey("Parameter/Input");
        param_ptr->setAppendix(input_counter++);
        param_ptr->setParent(shared_from_this());
        json ret;
        ret["operation"] = "change";
        json jargs;
        jargs["subject"] = getIdentifier();
        jargs["object"] = dump();
        ret["args"] = jargs;
        return ret;
    }

    json ProcessorBase::removeInput(std::shared_ptr<IInput> input) {
        json ret;
        return ret;
    }

    json ProcessorBase::addOutput(std::shared_ptr<IOutput> output) {
        auto param_ptr = std::make_shared<Parameter>();
        param_ptr->setName(output->getName());
        param_ptr->getInput(0)->connect(output);
        getOutputParams().push_back(param_ptr);
        param_ptr->setKey("Parameter/Output");
        param_ptr->setAppendix(output_counter++);
        param_ptr->setParent(shared_from_this());
        json ret;
        ret["operation"] = "change";
        json jargs;
        jargs["subject"] = getIdentifier();
        jargs["object"] = dump();
        ret["args"] = jargs;
        return ret;
    }

    json ProcessorBase::removeOutput(std::shared_ptr<IOutput> output) {
        json ret;
        return ret;
    }

    std::vector<std::shared_ptr<Parameter>>& ProcessorBase::getInputParams() {
        return inputs;
    }

    std::vector<std::shared_ptr<Parameter>>& ProcessorBase::getOutputParams() {
        return outputs;
    }

    size_t ProcessorBase::getNumInputs() {
        return processor_inputs.size() + inputs.size();
    }

    size_t ProcessorBase::getNumOutputs() {
        return processor_outputs.size() + outputs.size();
    }

    std::vector< std::shared_ptr<IModule >>& ProcessorBase::getModules(){
        return modules;
    }

    size_t ProcessorBase::getNumModuls() {
        return modules.size();
    }

    std::shared_ptr<IModule> ProcessorBase::getModule(size_t index) {
        if (index < modules.size())
        {
            return modules[index];
        }
        return nullptr;
    }

    bool ProcessorBase::connect(const std::string& output_id, const std::vector<std::string>& input_ids)
    {
        std::size_t found = output_id.find_first_of("/");
        std::string str_module_id = output_id.substr(0, found);
        std::string str_output_id = output_id.substr( found + 1);
        std::shared_ptr<IOutput> out_ptr;
        if (str_module_id.empty())
        {
            out_ptr = getOutput(str_output_id);
        }
        else {
            for (size_t i = 0; i < modules.size(); i++)
            {
                if (auto module_ptr = modules[i])
                {
                    if (module_ptr->getID() == str_module_id)
                    {
                        out_ptr = module_ptr->getOutput(str_output_id);
                        break;
                    }
                }
            }
        }

        for (size_t i = 0; i < input_ids.size(); i++)
        {
            found = input_ids[i].find_first_of("/");
            str_module_id = input_ids[i].substr(0, found);
            std::string str_input_id = input_ids[i].substr(found + 1);

            if (str_module_id.empty())
            {
                auto in_ptr = getInput(str_input_id);
                in_ptr->connect(out_ptr);
            }
            else {
                for (size_t i = 0; i < modules.size(); i++)
                {
                    if (auto module_ptr = modules[i])
                    {
                        if (module_ptr->getID() == str_module_id)
                        {
                            auto in_ptr = module_ptr->getInput(str_input_id);
                            in_ptr->connect(out_ptr);
                            break;
                        }
                    }
                }
            }
        }
        return true;
    }

    void ProcessorBase::disconnect() {
        for (size_t i = 0; i < processor_inputs.size(); i++)
        {
            processor_inputs[i]->disconnect();
        }
        for (size_t i = 0; i < processor_outputs.size(); i++)
        {
            processor_outputs[i]->disconnect();
        }
        for (size_t i = 0; i < inputs.size(); i++)
        {
            inputs[i]->disconnect();
        }
        for (size_t i = 0; i < outputs.size(); i++)
        {
            outputs[i]->disconnect();
        }
    }

    void ProcessorBase::load(const json& j) {
        disconnect();
        auto jit = j.find("ID");
        if (jit != j.end())
        {
            setAppendix((*jit)["appendix"].get<int>());
            j["ID"]["type"];

        }
        jit = j.find("key");
        if (jit != j.end())
        {

        }
        jit = j.find("type");
        if (jit != j.end())
        {

        }
        jit = j.find("configure");
        if (jit != j.end())
        {
            json jcon;
            jcon["configure"] = *jit;
            configure(jcon);
        }
        jit = j.find("GUI");
        if (jit != j.end())
        {
            json jgui;
            jgui["GUI"] = *jit;
            configure(jgui);
        }
        jit = j.find("modules");
        if (jit != j.end())
        {
            Registration regi = Registration();
            for (json jmodule : *jit)
            {
                if (jmodule["type"].get<std::string>() == "module")
                {
                    addModule(regi.generateModule(jmodule["key"]));
                }
                else if (jmodule["type"].get<std::string>() == "processor")
                {
                    addModule(regi.generateProcessor(jmodule["key"]));
                }
                if (auto module_ptr = modules.back())
                {
                    module_ptr->setParent(shared_from_this());
                    module_ptr->load(jmodule);
                }
            }
        }

        jit = j.find("params");
        if (jit != j.end())
        {
            auto jit_params = jit->find("inputs");
            if (jit_params != jit->end())
            {
                const size_t start_counter = processor_outputs.size();
                for (size_t i = start_counter; i < (*jit_params).size(); i++)
                {
                    inputs.push_back(std::make_shared<Parameter>());
                    inputs.back()->load((*jit_params)[i]);
                }
            }

            jit_params = jit->find("outputs");
            if (jit_params != jit->end())
            {
                const size_t start_counter = processor_inputs.size();
                for (size_t i = start_counter; i < (*jit_params).size(); i++)
                {
                    outputs.push_back(std::make_shared<Parameter>());
                    outputs.back()->load((*jit_params)[i]);
                }
            }
        }
        

        jit = j.find("connections");
        if (jit != j.end())
        {
            for(json jcon : *jit)
            {
                json jout = jcon["output"];
                IModule::Ptr module_out_ptr = getModule(jout);
                IOutput::Ptr out_ptr = nullptr;
                std::string id = jout["key"].get<std::string>() + std::to_string(jout["appendix"].get<int>());
                for (size_t i = 0; i < module_out_ptr->getNumOutputs(); i++)
                {
                    auto out = module_out_ptr->getOutput(i);
                    if (out->getID() == id)
                    {
                        out_ptr = out;
                        break;
                    }
                }
                for (json jin : jcon["inputs"])
                {
                    IModule::Ptr module_in_ptr = getModule(jin);
                    IInput::Ptr in_ptr = nullptr;
                    std::string id = jin["key"].get<std::string>() + std::to_string(jin["appendix"].get<int>());
                    for (size_t i = 0; i < module_in_ptr->getNumInputs(); i++)
                    {
                        auto in = module_in_ptr->getInput(i);
                        if (in->getID() == id)
                        {
                            in_ptr = in;
                            break;
                        }
                    }
                    if (in_ptr != nullptr && out_ptr != nullptr)
                    {
                        in_ptr->connect(out_ptr);
                    }
                }
            }
        }

        //update appendix
        for (int i = 0; i < modules.size(); ++i)
        {
            modules[i]->setAppendix(i);
        }
        for (int i = 0; i < inputs.size(); ++i)
        {
            inputs[i]->setAppendix(i);
        }
        input_counter = inputs.size();
        for (int i = 0; i < outputs.size(); ++i)
        {
            outputs[i]->setAppendix(i);
        }
        output_counter = outputs.size();
    }

    json ProcessorBase::dump() {
        json ret = getConfiguration();
        ret["ID"] = getIdentifier();
        ret["type"] = type;
        ret["key"] = key;

        ret["params"];
        ret["params"]["inputs"];
        for (auto& out : processor_outputs)
        {
            out->setParent(shared_from_this());
            ret["outputs"].push_back(out->dump());

            json param;
            param["GUI"] = getConfiguration()["GUI"];
            param["ID"] = out->getIdentifier();
            param["ID"]["type"] = "module";
            param["type"] = "module";
            param["key"] = "Input";
            param["GUI"]["name"]["value"] = param["GUI"]["name"]["value"].get<std::string>() + "/" + out->getName();
            param["outputs"].push_back(out->dump());
            ret["params"]["inputs"].push_back(param);
        }
        for (size_t i = 0; i < inputs.size(); i++)
        {
            if (auto in_ptr = inputs[i])
            {
                ret["inputs"].push_back(in_ptr->getInput(0)->dump());
                ret["params"]["inputs"].push_back(in_ptr->dump());
            }
        }

        ret["params"]["outputs"];
        for (auto& in : processor_inputs)
        {
            in->setParent(shared_from_this());
            ret["inputs"].push_back(in->dump());

            json param;
            param["GUI"] = getConfiguration()["GUI"];
            param["ID"] = in->getIdentifier();
            param["ID"]["type"] = "module";
            param["type"] = "module";
            param["key"] = "Output";
            param["GUI"]["name"]["value"] = param["GUI"]["name"]["value"].get<std::string>() + "/" + in->getName();
            param["inputs"].push_back(in->dump());
            ret["params"]["outputs"].push_back(param);
        }
        for (size_t i = 0; i < outputs.size(); i++)
        {
            if (auto out_ptr = outputs[i])
            {
                ret["outputs"].push_back(out_ptr->getOutput(0)->dump());
                ret["params"]["outputs"].push_back(out_ptr->dump());
            }
        }

        for (auto& mod : modules)
        {
            if (auto module_ptr = mod)
            {
                if (module_ptr->getType() == "module")
                {
                    ret["modules"].push_back(module_ptr->dump());
                }
                else
                {
                    ret["modules"].push_back(module_ptr->dump());
                }
            }
        }

        for (auto& mod : modules)
        {
            if (auto module_ptr = mod)
            {
                for (size_t j = 0; j < module_ptr->getNumOutputs(); j++)
                {
                    auto input_connections = module_ptr->getOutput(j)->getConnections();
                    if (!input_connections.empty())
                    {
                        json connect;
                        connect["output"] = module_ptr->getOutput(j)->getIdentifier();
                        for (size_t k = 0; k < input_connections.size(); k++)
                        {
                            connect["inputs"].push_back(input_connections[k]->getIdentifier());
                        }
                        ret["connections"].push_back(connect);
                    }
                }
            }
        }
        for (auto& mod : inputs)
        {
            if (auto module_ptr = mod)
            {
                auto input_connections = module_ptr->getOutput(0)->getConnections();
                if (!input_connections.empty())
                {
                    json connect;
                    connect["output"] = module_ptr->getOutput(0)->getIdentifier();
                    for (size_t k = 0; k < input_connections.size(); k++)
                    {
                        connect["inputs"].push_back(input_connections[k]->getIdentifier());
                    }
                    ret["connections"].push_back(connect);
                }
            }
        }
        return ret;
    }

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

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

    std::vector<std::shared_ptr<IInput>> ProcessorBase::getOptimizableInputs() {
        std::vector<std::shared_ptr<IInput>> ret;
        for (size_t i = 0; i < processor_inputs.size(); i++)
        {
            if (processor_inputs[i]->isOptimizable())
            {
                ret.push_back(processor_inputs[i]);
            }
        }
        for (size_t i = 0; i < inputs.size(); i++)
        {
            auto list = inputs[i]->getOptimizableInputs();
            ret.insert(ret.end(), list.begin(), list.end());
        }
        for (size_t i = 0; i < modules.size(); i++)
        {
            auto list = modules[i]->getOptimizableInputs();
            ret.insert(ret.end(), list.begin(), list.end());
        }
        return ret;
    }

    std::vector<std::shared_ptr<IOutput>> ProcessorBase::getOptimizableOutputs() {
        std::vector<std::shared_ptr<IOutput>> ret;
        for (size_t i = 0; i < processor_outputs.size(); i++)
        {
            if (processor_outputs[i]->isOptimizable())
            {
                ret.push_back(processor_outputs[i]);
            }
        }
        for (size_t i = 0; i < outputs.size(); i++)
        {
            auto list = outputs[i]->getOptimizableOutputs();
            ret.insert(ret.end(), list.begin(), list.end());
        }
        for (size_t i = 0; i < modules.size(); i++)
        {
            auto list = modules[i]->getOptimizableOutputs();
            ret.insert(ret.end(), list.begin(), list.end());
        }
        return ret;
    }
   
    std::shared_ptr<IOutput> ProcessorBase::getOutput(const json& jid){
        auto jid_pref = jid.find("prefix");
        if (jid_pref != jid.end())
        {
            auto mod = getModule(jid);
            if (mod == shared_from_this())
            {
                std::string sid = jid["key"].get<std::string>() + std::to_string(jid["appendix"].get<int>());
                for (auto& output : processor_outputs) {
                    if (output->getID() == sid) {
                        return output;
                    }
                }
                for (auto& output : outputs) {
                    if (output->getOutput(0)->getID() == sid) {
                        return output->getOutput(0);
                    }
                }
            }
            else
            {
                return mod->getOutput(jid);
            }
        }
        return nullptr;
    }

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

    std::shared_ptr<IInput> ProcessorBase::getInput(const json& jid){
        auto jid_pref = jid.find("prefix");
        if (jid_pref != jid.end())
        {
            auto mod = getModule(jid);
            if (mod == shared_from_this())
            {
                std::string sid = jid["key"].get<std::string>() + std::to_string(jid["appendix"].get<int>());
                for (auto& input : processor_inputs) {
                    if (input->getID() == sid) {
                        return input;
                    }
                }
                for (auto& input : inputs) {
                    if (input->getInput(0)->getID() == sid) {
                        return input->getInput(0);
                    }
                }
            }
            else
            {
                return mod->getInput(jid);
            }
        }
        return nullptr;
    }

    std::shared_ptr<IModule> ProcessorBase::getModule(const json& jid){
        auto jid_typ = jid.find("type");
        auto jid_pref = jid.find("prefix");
        
        json own_prefix = getIdentifier()["prefix"];

        int final_size = 0;
        if (jid_typ != jid.end())
        {
            if (*jid_typ == "input" || *jid_typ == "output")
            {
                final_size = 1;
            }
        }

        //check if called non recursive
        int same_counter = 0;
        for (size_t i = 0; i < own_prefix.size(); i++)
        {
            if (jid_pref->at(i).get<std::string>() != own_prefix.at(i).get<std::string>())
            {
                break;
            }
            ++same_counter;
        }

        if (own_prefix.size() == jid_pref->size())
        {
            if (same_counter == jid_pref->size())
            {
                return shared_from_this();
            }
            else
            {
                return nullptr;
            }
        }
        std::string sid;
        ++same_counter;
        if (same_counter == jid_pref->size() && final_size == 0)
        {
            sid = jid["key"].get<std::string>() + std::to_string(jid["appendix"].get<int>());
        }
        else
        {
            sid = jid_pref->at(same_counter).get<std::string>();
        }

        IModule::Ptr module_ptr;
        for (auto& module : modules) {
            if (module->getID() == sid) {
                module_ptr = module;
                break;
            }
        }
        if (module_ptr == nullptr)
        {
            for (auto& module : inputs) {
                if (module->getID() == sid) {
                    module_ptr = module;
                    break;
                }
            }
        }
        if (module_ptr == nullptr)
        {
            for (auto& module : outputs) {
                if (module->getID() == sid) {
                    module_ptr = module;
                    break;
                }
            }
        }
        
        if (module_ptr != nullptr)
        {
            if (jid_pref->size() >= final_size)
            {
                if (ProcessorBase* proc_ptr = dynamic_cast<ProcessorBase*>(&(*module_ptr)))
                {
                    return proc_ptr->getModule(jid);
                }
            }
            return module_ptr;
        }
        else {
            return shared_from_this();
        }
        //return nullptr;
    }


}
//*/