#pragma once
#include <set>
#include <string>
#include <utility>
#include <vector>

#include <abstraction/Value.hpp>

class GraphicsBox;

enum class ModelType {
    Input,
    Output,
    Algorithm
};

class ModelBox {
    friend class ParallelExecutor;
protected:
    explicit ModelBox(ModelType p_type, size_t p_maxInputCount);

public:
    virtual ~ModelBox();

    ModelType getType() const { return this->type; }
    virtual std::string getName() const = 0;

    size_t getMaxInputCount() const { return this->maxInputCount; }
    virtual bool canHaveOutput() const { return true; }

    std::shared_ptr<abstraction::Value> getCachedResult() const { return this->result; }
    std::shared_ptr<abstraction::Value> getCachedResultOrEvaluate();
    virtual void clearCachedResult() { this->result.reset(); }
    virtual std::shared_ptr<abstraction::Value> evaluate() = 0;

    static void connect(ModelBox* origin, ModelBox* target, size_t targetSlot);
    static void disconnect(ModelBox* origin, ModelBox* target, size_t targetSlot);

    static const std::vector<ModelBox*>& getAllModelBoxes() { return ModelBox::allModelBoxes; }
    static void clearCachedResults();

private:
    void setInput(size_t slot, ModelBox* model);
    void addOutput(ModelBox* target, size_t targetSlot);
    void removeOutput(ModelBox* target, size_t targetSlot);

protected:
    ModelType type;

    size_t maxInputCount = 0;
    std::vector<ModelBox*> inputs;
    std::set<std::pair<ModelBox*, size_t>> outputs;
    std::shared_ptr<abstraction::Value> result;

private:
    static std::vector<ModelBox*> allModelBoxes;

    // for topological sorting in ParallelExecutor
    mutable size_t id = static_cast<size_t>(-1);
    mutable size_t level = static_cast<size_t>(-1);
};


