#pragma once

#include <memory>
#include <vector>
#include <map>
#include <unordered_set>
#include <string>

#include <QPointer>
#include <QObject>

class QPainter;
class QStyleOptionGraphicsItem;

namespace Xybrid::UI {
    class NodeObject;
    class PortObject;
}

namespace Xybrid::Config {
    class PluginInfo;
}

namespace Xybrid::Audio {
    class AudioEngine;
}

namespace Xybrid::Data {
    class Project;

    class Graph;
    class Node;

    class Port : public std::enable_shared_from_this<Port> {
        Q_GADGET
    public:
        enum Type : uint8_t {
            Input, Output
        };
        Q_ENUM(Type)
        enum DataType : uint8_t {
            Audio, Command, MIDI, Parameter
        };
        Q_ENUM(DataType)
        std::weak_ptr<Node> owner;
        std::vector<std::weak_ptr<Port>> connections;
        std::weak_ptr<Port> passthroughTo;
        Type type;
        uint8_t index;
        size_t tickUpdatedOn = static_cast<size_t>(-1);

        QPointer<UI::PortObject> obj;

        std::string name;

        virtual ~Port();

        virtual DataType dataType() const { return static_cast<DataType>(-1); }
        virtual bool singleInput() const { return false; }
        virtual bool canConnectTo(DataType) const;
        /*virtual*/ bool connect(std::shared_ptr<Port>);
        /*virtual*/ void disconnect(std::shared_ptr<Port>);
        void cleanConnections();

        virtual void pull() { } // make sure data for this tick is available

        static std::shared_ptr<Port> makePort(DataType);
    };

    class Node : public std::enable_shared_from_this<Node> {
        friend class Audio::AudioEngine;
        size_t tick_last = 0;
        bool try_process(bool checkDependencies = true);
    public:
        Project* project;
        std::weak_ptr<Graph> parent;
        int x{}, y{};
        std::string name;

        std::map<Port::DataType, std::map<uint8_t, std::shared_ptr<Port>>> inputs, outputs;

        std::shared_ptr<Config::PluginInfo> plugin;

        QPointer<UI::NodeObject> obj;

        virtual ~Node() = default;

        void parentTo(std::shared_ptr<Graph>);

        std::shared_ptr<Port> port(Port::Type, Port::DataType, uint8_t, bool addIfNeeded = false);
        std::shared_ptr<Port> addPort(Port::Type, Port::DataType, uint8_t);
        void removePort(Port::Type, Port::DataType, uint8_t);

        std::unordered_set<std::shared_ptr<Node>> dependencies() const;
        bool dependsOn(std::shared_ptr<Node>);

        virtual void init() { }
        virtual void reset() { }
        virtual void saveData(QCborMap&) { }
        virtual void loadData(QCborMap&) { }

        virtual void process() { }
        virtual std::string pluginName() const;

        virtual void onGadgetCreated() { }
        virtual void drawCustomChrome(QPainter*, const QStyleOptionGraphicsItem*) { }
        virtual void onRename() { }

        virtual void onUnparent(std::shared_ptr<Graph>) { }
        virtual void onParent(std::shared_ptr<Graph>) { }

        virtual void onDoubleClick() { }
        // something something customChrome?
    };

}
Q_DECLARE_METATYPE(Xybrid::Data::Port)
