#pragma once

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

#include <QString>
#include <QMutex>
#include <QPointer>
#include <QObject>
#include <QPoint>

class QPainter;
class QStyleOptionGraphicsItem;

class QCborValue;
class QCborMap;

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

namespace Xybrid::Config {
    class PluginInfo;
}

namespace Xybrid::Audio {
    class AudioEngine;
    class AudioWorker;
    class AudioWorkerCore;
}

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;
        QMutex lock;
        size_t tickUpdatedOn = static_cast<size_t>(-1);

        QPointer<UI::PortObject> obj;

        QString name;

        virtual ~Port();
        Port() = default;
        Port(const 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;
        friend class Audio::AudioWorker;
        friend class Audio::AudioWorkerCore;
        size_t tick_last = 0;
        bool try_process(bool checkDependencies = true);
    public:
        Project* project;
        std::weak_ptr<Graph> parent;
        int x{}, y{};
        QString 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;

        Node() = default;
        virtual ~Node() = default;

        QCborMap toCbor() const;
        static std::shared_ptr<Node> fromCbor(const QCborMap&, std::shared_ptr<Graph> = nullptr);
        static std::shared_ptr<Node> fromCbor(const QCborValue&, std::shared_ptr<Graph> = nullptr);

        static QCborMap multiToCbor(std::vector<std::shared_ptr<Node>>&);
        static std::vector<std::shared_ptr<Node>> multiFromCbor(const QCborMap&, std::shared_ptr<Graph> = nullptr, QPoint = QPoint());
        static std::vector<std::shared_ptr<Node>> multiFromCbor(const QCborValue&, std::shared_ptr<Graph> = nullptr, QPoint = QPoint());

        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, bool allowUpdate = true);
        void removePort(Port::Type, Port::DataType, uint8_t, bool allowUpdate = true);
        bool movePort(Port::Type, Port::DataType, uint8_t, uint8_t, bool allowUpdate = true);
        void collapsePorts(Port::Type, Port::DataType);

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

        virtual void init() { }
        virtual void reset() { } // called on starting playback to prepare for operation
        virtual void release() { } // called on stop to release any resources that may be hanging
        virtual void process() { }
        virtual void saveData(QCborMap&) const { }
        virtual void loadData(const QCborMap&) { }

        virtual QString pluginName() const;
        /// If true, excluded from copy/paste operations.
        virtual bool isVolatile() const { return false; }

        virtual void onGadgetCreated() { }
        virtual void drawCustomChrome(QPainter*, const QStyleOptionGraphicsItem*) { }
        virtual void initUI(UI::NodeUIScene*) { }
        virtual void onRename() { }

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

        virtual void onPortConnected(Port::Type, Port::DataType, uint8_t, std::weak_ptr<Port>) { }
        virtual void onPortDisconnected(Port::Type, Port::DataType, uint8_t, std::weak_ptr<Port>) { }

        virtual void onDoubleClick() { }
    };

}
Q_DECLARE_METATYPE(Xybrid::Data::Port)
