/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Marcus Britanicus (https://gitlab.com/marcusbritanicus)
 * Copyright (c) 2021 Abrar (https://gitlab.com/s96Abrar)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 **/

#pragma once

#include <QMap>
#include <QSet>
#include <QRect>
#include <QMutex>
#include <QObject>
#include <QString>
#include <QMimeData>
#include <QWaitCondition>
#include <wayland-client-protocol.h>

struct wl_buffer;
struct wl_output;

struct zwlr_data_control_manager_v1;
struct zwlr_data_control_device_v1;
struct zwlr_data_control_offer_v1;
struct zwlr_data_control_source_v1;
struct zwlr_data_control_device_v1_listener;
struct zwlr_data_control_offer_v1_listener;
struct zwlr_data_control_source_v1_listener;

namespace WQt {
    class DataControlManager;
    class DataControlDevice;
    class DataControlSource;
    class DataControlOffer;

    class MimeDataStore;
}

/** Implementational details */
class PipeReader;
class PipeWriter;

class WQt::MimeDataStore {
    public:
        // Constructor and destructor
        MimeDataStore()  = default;
        ~MimeDataStore() = default;

        // Copy constructor
        MimeDataStore( const MimeDataStore& other );

        // Copy assignment operator
        MimeDataStore &operator=( const MimeDataStore& other );

        // Check if a MIME type is present
        bool hasFormat( const QString& mimeType ) const;

        // Get the data for a specific MIME type
        QByteArray data( const QString& mimeType ) const;

        // Set the data for a specific MIME type
        void setData( const QString& mimeType, const QByteArray& data );

        // Remove the data for a specific MIME type
        void removeData( const QString& mimeType );

        // Clear all MIME data
        void clear();

        // Get a list of all MIME types
        QStringList formats() const;

    private:
        QHash<QString, QByteArray> mimeDataMap; // Stores MIME type -> data mappings
        mutable QMutex m_mutex;                 // Protects access to m_data
};

class WQt::DataControlManager : public QObject {
    Q_OBJECT;

    public:
        DataControlManager( zwlr_data_control_manager_v1 *dataMgr );
        ~DataControlManager();

        DataControlSource *createDataSource();
        DataControlDevice *getDataDevice( wl_seat * );

        /** Return the zwlr_data_control_manager_v1 internal pointer  */
        zwlr_data_control_manager_v1 *get();

    private:
        struct ManagerDeleter {
            void operator()( zwlr_data_control_manager_v1 *pointer ) const;
        };

        std::unique_ptr<zwlr_data_control_manager_v1, ManagerDeleter> mObj;
};

class WQt::DataControlDevice : public QObject {
    Q_OBJECT;

    public:
        DataControlDevice( zwlr_data_control_device_v1 *dataDev );
        ~DataControlDevice();

        /** Always call this after connecting the signals to slots */
        void setup();

        void setSelection( DataControlSource *src );
        void setPrimarySelection( DataControlSource *src );

        /** Return the zwlr_data_control_device_v1 internal pointer  */
        zwlr_data_control_device_v1 *get();

    private:
        static void handleDataOffer( void *, struct zwlr_data_control_device_v1 *, struct zwlr_data_control_offer_v1 * );
        static void handleSelection( void *, struct zwlr_data_control_device_v1 *, struct zwlr_data_control_offer_v1 * );
        static void handleFinished( void *, struct zwlr_data_control_device_v1 * );
        static void handlePrimarySelection( void *, struct zwlr_data_control_device_v1 *, struct zwlr_data_control_offer_v1 * );

        struct DeviceDeleter {
            void operator()( zwlr_data_control_device_v1 *pointer ) const;
        };

        std::unique_ptr<zwlr_data_control_device_v1, DeviceDeleter> mObj;

        static const zwlr_data_control_device_v1_listener mListener;

        std::unique_ptr<WQt::DataControlOffer> currentOffer;
        std::unique_ptr<WQt::DataControlOffer> currentSelection;
        std::unique_ptr<WQt::DataControlOffer> currentPrimary;

        std::unique_ptr<WQt::DataControlSource> currentSelectionSrc;
        std::unique_ptr<WQt::DataControlSource> currentPrimarySrc;

        bool mIsSetup = false;
        std::unique_ptr<WQt::DataControlOffer> pendingSelection;
        std::unique_ptr<WQt::DataControlOffer> pendingPrimary;

    Q_SIGNALS:
        void selectionOffered( DataControlOffer * );
        void primarySelectionOffered( DataControlOffer * );
};

class WQt::DataControlSource : public QObject {
    Q_OBJECT;

    public:
        explicit DataControlSource( zwlr_data_control_source_v1 *srcDev );
        ~DataControlSource() override;

        /** Set the data that will be made available */
        void setSelectionData( const WQt::MimeDataStore& mData );

        zwlr_data_control_source_v1 * get();

    private:
        static void handleSend( void *data, struct zwlr_data_control_source_v1 *, const char *mimeType, int32_t fd );
        static void handleCanceled( void *data, struct zwlr_data_control_source_v1 * );

        struct SourceDeleter {
            void operator()( zwlr_data_control_source_v1 *pointer ) const;
        };

        std::unique_ptr<zwlr_data_control_source_v1, SourceDeleter> mObj;
        static const zwlr_data_control_source_v1_listener mListener;

        std::unique_ptr<WQt::DataControlOffer> currentOffer;
        WQt::MimeDataStore mMimeData;
        std::unique_ptr<PipeWriter> writer;

    signals:
        void dataRequested( QString mimeType, int32_t fd );
        void canceled();
};


class WQt::DataControlOffer : public QObject {
    Q_OBJECT;

    public:
        explicit DataControlOffer( zwlr_data_control_offer_v1 *offer );
        ~DataControlOffer() = default;

        // Retrieve data for a specific MIME type (blocking but GUI-friendly)
        QByteArray retrieveData( const QString& mimeType );

        // Get the list of offered MIME types
        QStringList offeredMimeTypes() const;

        // Invalidate the offer
        void invalidate();

        // Check if the offer is still valid
        bool isValid() const;

        zwlr_data_control_offer_v1 * get();

    signals:
        // Emitted to inform that this offer offers @mimeType data
        void mimeTypeOffered( const QString& mimeType );

        // Emitted when we invalidate this offer.
        // This offer should not be used after the emission of this signal.
        void invalidated();

    private:
        // Start asynchronous data retrieval for a specific MIME type
        void retrieveDataAsync( const QString& mimeType );

        // Handle the "offer" event from Wayland (static callback)
        static void handleOffer( void *data, zwlr_data_control_offer_v1 *offer, const char *mimeType );

        // Deleter for the Wayland offer object
        struct OfferDeleter {
            void operator()( zwlr_data_control_offer_v1 *offer ) const;
        };

        // Wayland offer object
        std::unique_ptr<zwlr_data_control_offer_v1, OfferDeleter> mObj;

        // PipeReader instance
        PipeReader *mPipeReader;

        // Track whether data is ready for each MIME type
        QHash<QString, bool> mDataReady;

        // Mutex to protect access to mRetrievedData and mDataReady
        mutable QMutex mMutex;

        // Condition variable to wait for data to be ready
        QWaitCondition mDataCondition;

        // List of offered MIME types
        QStringList mMimeTypes;

        // Flag to indicate if the offer is invalidated
        bool mInvalidated;

        // Listener for Wayland events
        static const zwlr_data_control_offer_v1_listener mListener;
};
