#include "Output.h"
namespace mdd {
    //template<typename T> class shared : public std::shared_ptr<T> {};
    int Output::addConnection(std::shared_ptr<IInput> input)
    {
        _connections.push_back(input);
        return _connections.size();
    }
    int Output::removeConnection(std::shared_ptr<IInput> input)
    {
        for (auto it = _connections.begin(); it != _connections.end(); ++it) {
            if (auto in_ptr = it->lock())
            {
                if (in_ptr->getIdentifier() == input->getIdentifier())
                {
                    _connections.erase(it);
                    return _connections.size();
                }
            }
        }
        return _connections.size();
    }
    Output::Output(const std::string& name, int appendix, const std::vector<double>& initial) :
            _state(state::UNCHANGED)
    {
        _appendix = appendix;
        _name = name;
        _value = initial;
        _optimizable = false;

        type = "output";
        key = "Output";
    }

    const std::vector<double>& Output::getValue() { return _value; }

    std::vector<double>& Output::setValue() {
        _state = state::CHANGED;
        return _value;
    }
    state Output::setValue(const std::vector<double>& val) {
        if (val != _value)
        {
            _value = val;
            _state = state::CHANGED;
        }
        else {
            _state = state::UNCHANGED;
        }
        return _state;
    }
    bool Output::isOptimizable() {
        return _optimizable;
    }

    void Output::setOptimizability(bool state) {
        _optimizable = state;
    }

    state Output::getState() {
        return _state;
    }
    
    void Output::resetState() {
        _state = state::UNCHANGED;
    }

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

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

    std::string Output::setName(const std::string& name) {
        _name = name;
        return getID();
    }

    std::string Output::getName() {
        return  _name;
    }

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

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

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

    void Output::setParent(IModule::Ptr parent) {
        _parent = parent;
    }

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

    bool Output::connect(std::shared_ptr<IInput> input)
    {
        if (input->getConnection() != nullptr) {
            if (input->getConnection()->getIdentifier() != getIdentifier())
            {
                removeConnection(input);
                input->removeConnection();
            }
        }
        if (input->getConnection() == nullptr)
        {
            addConnection(input);
            input->addConnection(shared_from_this());
        }
        return true;
    }
    std::vector<std::shared_ptr<IInput>> Output::getConnections()
    {
        std::vector<std::shared_ptr<IInput>> ret;
        for (size_t i = 0; i < _connections.size(); i++)
        {
            if (auto& ptr = _connections[i].lock())
            {
                ret.push_back(ptr);
            }
        }
        return ret;
    }
    void Output::disconnect() {
        for (size_t i = 0; i < _connections.size(); i++)
        {
            if (auto ptr = _connections[i].lock())
            {
                ptr->removeConnection(std::make_shared<Output>((*this)));
            }
        }
        _connections.clear();
    }

    bool Output::configure(const json& config)
    {
        json jparse = config;
        bool success = false;
        auto jit_config = jparse.find("configure");
        json jconfig = jparse;
        if (jit_config != jparse.end())
        {
            jconfig = *jit_config;
        }
        auto jit = jconfig.find("name");
        if (jit != jconfig.end())
        {
            _name = jit.value().get<std::string>();
            success = true;
        }

        jit = jparse.find("ID");
        if (jit != jparse.end())
        {
            auto jit2 = jit->find("appendix");
            if (jit2 != jit->end())
            {
                _appendix = jit2.value().get<int>();
                success = true;
            }
        }

        jit = jconfig.find("optimizable");
        if (jit != jconfig.end())
        {
            auto jopt = jit->find("value");
            if (jopt != jit->end())
            {
                json jval = jopt.value();
                if (jval.is_string()) {
                    jval = json::parse(jval.get<std::string>());
                }
                setOptimizability(jval.get<bool>());
            }
            else
            {
                setOptimizability(jit.value().get<bool>());
            }
            success = true;
        }

        jit = jconfig.find("value");
        if (jit != jconfig.end())
        {
            json jval = jit.value();
            if (jval.is_string()) {
                jval = json::parse(jval.get<std::string>());
            }
            _value = jval.get<std::vector<double>>();
            success = true;
        }
        return success;
    }

    const json& Output::getConfiguration()
    {
        json jconfig;
        jconfig["name"] = _name;
        jconfig["optimizable"]["value"]= _optimizable;
        jconfig["optimizable"]["options"] = {true, false};
        jconfig["value"] = _value;
        _base_config["configure"] = jconfig;
        return _base_config;
    }

    void Output::load(const json& j)
    {
        configure(j);
        return;
    }

    json Output::dump()
    {
        json jdump = getConfiguration();
        jdump["ID"] = getIdentifier();
        jdump["type"] = "output";
        jdump["key"] = "Output";
        return jdump;
    }

    json Output::getIdentifier() {
        json jID;
        jID["key"] = key;
        jID["appendix"] = _appendix;
        jID["type"] = "output";
        jID["prefix"]= getParentID();
        return jID;
    }
}