#ifndef CLIENTCORE_H
#define CLIENTCORE_H

#include <InfoClient.h>
#include <InfoDirFile.h>
#include <InfoMsg.h>
#include <InfoPack.hpp>
#include <LocalFile.h>
#include <Protocol.h>
#include <QDataStream>
#include <QHostAddress>
#include <QMutex>
#include <QMutexLocker>
#include <QQueue>
#include <QReadWriteLock>
#include <QThread>
#include <array>
#include <asio.hpp>
#include <queue>

class ClientCore : public QObject
{
    Q_OBJECT

public:
    ClientCore(QObject* parent, asio::io_context& ioContext);
    ~ClientCore();

public slots:
    void Disconnect();

public slots:
    void DoConnect(const QString& ip, quint16 port);

public:
    void Instance();
    bool Connect(const QString& ip, quint16 port);
    bool IsConnect();
    const asio::any_io_executor& get_executor();
    template <typename T> asio::awaitable<bool> Send(const T& info, FrameBufferType type, const QString& tid)
    {
        auto f = GetBuffer<T>(info, type, tid);
        co_return co_await Send(f);
    }
    template <typename T> QSharedPointer<FrameBuffer> GetBuffer(const T& info, FrameBufferType type, const QString& tid)
    {
        auto f = QSharedPointer<FrameBuffer>::create();
        f->tid = tid;
        f->data = infoPack<T>(info);
        f->type = type;
        return f;
    }
    static bool syncInvoke(ClientCore* context, QSharedPointer<FrameBuffer> frame)
    {
        bool result = false;
        try {
            std::future<bool> fut = asio::co_spawn(
                context->get_executor(), [context, frame]() -> asio::awaitable<bool> { co_return co_await context->Send(frame); },
                asio::use_future);
            result = fut.get();
        } catch (const std::exception& ex) {
            // std::cerr << e.what() << '\n';
        }

        // bool success = QMetaObject::invokeMethod(context, "SendFrame", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool,
        // result),
        //                                          Q_ARG(QSharedPointer<FrameBuffer>, frame));
        // if (!success) {
        //     return false;
        // }
        return result;
    }

signals:
    void sigDisconnect();
    void sigPath(const QString& path, const QVector<QString>& drivers);
    void sigClients(const InfoClientVec& clients);
    void sigFiles(const DirFileInfoVec& files);
    void sigReqSend(QSharedPointer<FrameBuffer> frame);
    void sigReqDown(QSharedPointer<FrameBuffer> frame);
    void sigTransDone(QSharedPointer<FrameBuffer> frame);
    void sigCanSend(QSharedPointer<FrameBuffer> frame);
    void sigCanotSend(QSharedPointer<FrameBuffer> frame);
    void sigCanotDown(QSharedPointer<FrameBuffer> frame);
    void sigCanDown(QSharedPointer<FrameBuffer> frame);
    void sigFileBuffer(QSharedPointer<FrameBuffer> frame);
    void sigTransFailed(QSharedPointer<FrameBuffer> frame);
    void sigFileInfo(QSharedPointer<FrameBuffer> frame);
    void sigOffline(QSharedPointer<FrameBuffer> frame);
    void sigYourId(QSharedPointer<FrameBuffer> frame);
    void sigMsgAsk(QSharedPointer<FrameBuffer> frame);
    void sigMsgAnswer(QSharedPointer<FrameBuffer> frame);
    void sigFlowBack(QSharedPointer<FrameBuffer> frame);
    void sigTransInterrupt(QSharedPointer<FrameBuffer> frame);

signals:
    void conSuccess();
    void connecting();
    void conFailed();

private:
    void onReadyRead();
    void onDisconnected();
    asio::awaitable<void> handleAsk(QSharedPointer<FrameBuffer> frame);

private:
    asio::awaitable<void> UseFrame(QSharedPointer<FrameBuffer> frame);

public:
    void SetRemoteID(const QString& id);
    QString GetRemoteID();
    QString GetOwnID();
    void pushID(const QString& id);
    void popID(const QString& id);

public:
    QMutex conMutex_;
    QString ownID_;

    // 这是被动发送时，对方ID。
    QReadWriteLock rwIDLock_;
    QVector<QString> remoteIDs_;

    // 这是主动通信时的对方ID。
    QString remoteID_;

    QMutex sockMut_;
    QByteArray recvBuffer_;

    bool connected_{false};
    LocalFile localFile_;

    // asio变更
private:
    asio::io_context& ioContext_;
    asio::ip::tcp::socket mSocket_;
    std::array<char, CHUNK_BUF_SIZE> mBuffer_;
    std::mutex frameMutex_;
    std::queue<QSharedPointer<FrameBuffer>> frameQueue_;

private:
    asio::awaitable<bool> Send(QSharedPointer<FrameBuffer> frame);
    asio::awaitable<bool> Send(const char* data, size_t size);
    asio::awaitable<void> recvData();
};

// Socket Worker Thread
class SocketWorker : public QThread
{
    Q_OBJECT

public:
    SocketWorker(ClientCore* core, QObject* parent = nullptr);
    ~SocketWorker();

protected:
    void run() override;

private:
    ClientCore* core_{};
};

// HeatBeat to Server
class HeatBeat : public QThread
{
    Q_OBJECT

public:
    HeatBeat(ClientCore* core, QObject* parent = nullptr);
    ~HeatBeat() override;

public:
    void Stop();

protected:
    void run() override;

private:
    bool isRun_{false};
    ClientCore* core_{};
};

class WaitThread : public QThread
{
    Q_OBJECT

public:
    WaitThread(QObject* parent = nullptr);

public:
    void SetClient(ClientCore* cli);
    bool IsQuit() const;

Q_SIGNALS:
    void sigCheckOver();

public Q_SLOTS:
    virtual void interrupCheck();
    virtual void recvFrame(QSharedPointer<FrameBuffer> frame) = 0;

protected:
    bool isRun_;
    bool isAlreadyInter_;
    ClientCore* cli_{};
};

#endif   // CLIENTCORE_H