#include "ProcessorStandard.h"
#include <iostream>

namespace mdd {
    ProcessorStandard::ProcessorStandard(priority priorityEvaluation , int maxIterations):
        priorityEvaluation(priorityEvaluation),
        maxIterations(maxIterations)
    {
        setType("StandardProcessor");
        addProcessorOutput("Iterator", {0});
    }

    std::string ProcessorStandard::addModule(std::shared_ptr<IModule> module)
    {
        std::string id = ProcessorBase::addModule(module);
        _priority_list.emplace_back(module_priority(module));
        return id;
    }

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

    std::vector<std::shared_ptr<IModule>> ProcessorStandard::getModulePriority()
    {
        std::vector <std::shared_ptr<IModule>> ret;
        for (auto& p : _priority_list) {
            ret.push_back(p.module_ptr);
        }
        return ret;
    }

    state ProcessorStandard::update() {
        if (priorityEvaluation != MANUAL)
        {
            //update priorities
            for (auto it = _priority_list.begin(); it != _priority_list.end(); ++it) {
                //collect connected inputs
                auto input_connections = it->module_ptr->getInputConnections();
                it->inputCounter = 0;
                for (auto it_c = input_connections.begin(); it_c != input_connections.end(); ++it_c) {
                    if ((*it_c) != nullptr)
                    {
                        ++(it->inputCounter);
                    }
                }
                //collect connected outputs
                auto output_connections = it->module_ptr->getOutputConnections();
                it->outputCounter = 0;
                for (auto it_c = output_connections.begin(); it_c != output_connections.end(); ++it_c) {
                    if (!(*it_c).empty())
                    {
                        ++(it->outputCounter);
                    }
                }
            }
            //sort by connections
            std::sort(_priority_list.begin(), _priority_list.end(), [](module_priority a, module_priority b) {
                if (a.inputCounter == b.inputCounter)
                {
                    return a.outputCounter > b.outputCounter;
                }
                return a.inputCounter < b.inputCounter;
            });
        }
        
        typedef std::chrono::high_resolution_clock Time;
        //update
        state ret = state::UNCHANGED;
        state group_state = state::CHANGED;
        size_t restart = 0;
        while (group_state == state::CHANGED) {
            getProcessorOutput(0)->setValue()[0] += 1;
            getProcessorOutput(0)->resetState();
            group_state = state::UNCHANGED;
            for (int i = restart; i < _priority_list.size(); ++i) {
                auto t_start = Time::now();
                state module_state = _priority_list[i].module_ptr->update();
                auto t_end = Time::now();
                if (priorityEvaluation == TIME)
                {
                    _priority_list[i].time_priority = std::round(std::log10(std::chrono::duration_cast<std::chrono::milliseconds>(t_end - t_start).count()));
                }
                if (module_state == state::CHANGED) {
                    group_state = state::CHANGED;
                    ret = state::CHANGED;
                }
                // ignore modules which have to initilize once in the future
                if (_priority_list[i].inputCounter == 0 && priorityEvaluation != MANUAL) {
                    restart = i;
                }
                if ((priorityEvaluation == DYNAMIC || priorityEvaluation == TIME) && _priority_list.size() < i+1) {
                    if (_priority_list[i + 1].inputCounter == _priority_list[i].inputCounter && _priority_list[i + 1].inputCounter != 0)
                    {
                        //collect changes
                        for (auto it = _priority_list.begin() + i + 1; it != _priority_list.end(); ++it) {
                            auto input_states = it->module_ptr->getInputStates();
                            it->changeCounter = 0;
                            for (auto it_c = input_states.begin(); it_c != input_states.end(); ++it_c) {
                                if ((*it_c) == state::CHANGED)
                                {
                                    ++(it->changeCounter);
                                }
                            }
                        }
                        std::sort(_priority_list.begin() + i + 1, _priority_list.end(), [](module_priority a, module_priority b) {
                            if (a.inputCounter == b.inputCounter)
                            {
                                if (a.time_priority == b.time_priority)
                                {
                                    if ((a.inputCounter - a.changeCounter) == (b.inputCounter - b.changeCounter)) {
                                        if (a.changeCounter == b.changeCounter) {
                                            return a.outputCounter > b.outputCounter;
                                        }
                                        return a.changeCounter > b.changeCounter;
                                    }
                                    return (a.inputCounter - a.changeCounter) < (b.inputCounter - b.changeCounter);
                                }
                                return a.time_priority < b.time_priority;
                            }
                            return a.inputCounter < b.inputCounter;
                            
                        });
                    }
                }
            }
            if (maxIterations != -1 && getProcessorOutput(0)->getValue()[0] >= maxIterations)
            {
                return state::STATE_ERROR;
            }
        }
        return ret;
    }

    std::shared_ptr<IOutput> ProcessorStandard::getIteration(){
        return getProcessorOutput(0);
    }
    ProcessorStandard::module_priority::module_priority(std::shared_ptr<IModule> module, size_t inputs, size_t outputs, size_t change)
    {
        module_ptr = module;
        inputCounter = inputs;
        outputCounter = outputs;
        changeCounter = change;
        time_priority = 0;
    }
}