#ifndef MODULECOMMUNICATION_H
#define MODULECOMMUNICATION_H

#include "DataStructure.h"
#include <QObject>
#include <QQmlEngine>
#include <QSerialPort>
#include <memory>

class ModuleCommunication : public QObject {
    Q_OBJECT
    QML_ELEMENT
    QML_UNCREATABLE("Only created in C++...")
    static constexpr uint32_t UsernameSize = 32;
    static constexpr uint32_t VersionSize = 32;
    static constexpr const char *Separator = "----------";
    Q_PROPERTY(MessageId currentMessageId READ currentMessageId NOTIFY currentMessageIdChanged)
    Q_PROPERTY(QString verison MEMBER m_verison NOTIFY verisonChanged)
    Q_PROPERTY(int otaVerison MEMBER m_otaVerison NOTIFY verisonChanged)

public:
    constexpr static uint16_t VendorIdentifier = 0x3346;
    constexpr static uint16_t ProductIdentifier = 0x0001;
    constexpr static uint16_t MaxUserCount = 4000;
    constexpr static uint16_t InvalidUserId = std::numeric_limits<uint16_t>::max();
    enum MessageId : uint8_t {
        Reply = 0,
        Note = 0x01,
        Reset = 0x10,
        GetCurrentStatus = 0x11,
        Verify = 0x12,
        VerifyExtended = 0x16,
        EnrollSingle = 0x1D,
        EnrollExtended = 0x1E,
        GetImage = 0x1F, // 获取图片数据，通过VerifyExtended或EnrollExtended保存的
        DeleteUser = 0x20,
        DeleteAll = 0x21,
        GetVersion = 0x30,
        StartOta = 0x40, // 模组进入boot模式进行ota升级
        EnableDebug = 0x82,
        GetUniqueID = 0xAC,
        UploadImageInfo = 0xF6,
        UploadImageData = 0xF7,
        Idle = 0xFF,
    };
    Q_ENUM(MessageId)

    enum class NoteId : uint8_t {
        Ready = 0x00,
        PalmState = 0x01,
        UnknownError = 0x02,
        DebugInfo = 0x55,
        NoAliveImage = 0x56,

        DeviceError = 0xF0, // 系统设备出现的错误，例如 QSerialPort
        InteractWarning,
    };

    enum PalmState {
        NoPalm = 1,
        TooFar = 6,
        NeedMoveToCenter = 15,
        ManyPalm = 101, // 太多掌静脉
        NoAlive = 122,
    };

    enum MessageStatus : uint8_t {
        Success = 0,
        Rejected = 1,
        Aborted = 2,
        Failed4Camera = 4,
        Failed4UnknownReason = 5,
        Failed4InvalidParam = 6,
        Failed4NoMemory = 7,
        Failed4UnknownUser = 8,
        Failed4MaxUser = 9,
        Failed4PalmEnrolled = 10,
        Failed4LivenessCheck = 12,
        Failed4Timeout = 13,
        Failed4Authorization = 14,
        Failed4ReadFile = 19,
        Failed4WriteFile = 20,
        Failed4NoEncrypt = 21,
        PalmNotDetected = 23,
        Needmore = 25,
    };

#pragma pack(1)
    struct VerifyRequest {
        uint8_t save_image;
        uint8_t timeout; // timeout, unit second, default 10s
    };

    struct PalmStateNote {
        uint16_t state;
        // position
        uint16_t left; // in pixel
        uint16_t top;
        uint16_t right;
        uint16_t bottom;
    };

    struct EnrollRequest {
        uint8_t username[32];
        uint8_t strictMode = 0;
        uint8_t excludeMode = 0; // 掌静脉库里面是否已经存在 0:不进行验证 1:只进行比对 2:如果存在，则现在不予录入
        uint8_t skipSave = 0;
        uint8_t timeout;
        uint8_t reserved[4];
    };

    struct EnrollReply {
        uint16_t userid;
        uint8_t operation;
    };

    struct PalmVeinInformation {
        uint16_t x1;
        uint16_t y1;
        uint16_t x2;
        uint16_t y2;
        uint16_t detectionProbability;
        uint16_t quality;
    };

    struct EnrollExtendedReply {
        uint16_t userid;
        uint16_t imageWidth;
        uint16_t imageHeight;
        uint8_t imageFormat; // 0: 只有Y分量,灰度图
        uint8_t md5[16];
        uint8_t enrolledUsername[32];
        uint16_t enrolledId; // 掌静脉库里已经存在的id
        PalmVeinInformation palmVeinInformation;
        uint8_t reserved[4];
    };

    struct ImageSliceRequest {
        uint32_t offset;
        uint32_t size;
    };

    struct ImageSliceReply {
        uint32_t size;
        uint8_t data[0];
    };

    struct VerifyReply {
        uint16_t userid;
        uint8_t username[UsernameSize]; // 32Bytes
        uint16_t elapsed; // 此时识别耗时时间
    };

    struct VerifyExtendReply : public VerifyReply {
        uint16_t image_width;
        uint16_t image_height;
        uint8_t image_format; // 0: 只有Y分量,灰度图
        uint8_t md5[16];
        PalmVeinInformation palmVeinInformation;
    };

    struct UploadImageInformation {
        uint8_t operation; // 0:图片录入掌静脉
        uint8_t format;    // 0: 灰度图(纯Y分量)
        uint16_t width;
        uint16_t height;
        uint32_t size;
        uint8_t md5[16]; // 图片内容 md5 值
        char username[32];
    };

    struct UploadImageDataSlice {
        uint32_t offset;
        uint32_t size;
        uint8_t reserved[4]; // 预留
        uint8_t data[0];
    };

    struct UploadImageReply : public EnrollReply {};
    struct UploadImageVerifyReply : public UploadImageReply {
        VerifyReply result;
    };

    struct ModuleVersion {
        char version[VersionSize];
        uint32_t otaVersion;
    };

    struct ModuleId {
        char id[32];
        uint8_t userids[512]; // bit位
    };

#pragma pack()
    explicit ModuleCommunication(QObject *parent = nullptr);
    bool open(const QString &portName, int baudRate);
    void verify(uint8_t timeout);
    void verifyExtended(bool captureImage, uint8_t timeout);
    Q_INVOKABLE void reset();

    void enroll(const std::string &username, bool strictMode, uint8_t excludeMode, bool persistence, uint8_t timeout);
    void enrollExtended(const std::string &username, bool strictMode, uint8_t excludeMode, bool persistence,
                        uint8_t timeout);
    Q_INVOKABLE void deleteUser(uint16_t userid);
    Q_INVOKABLE void deleteAll();
    Q_INVOKABLE void requestUniqueId();
    Q_INVOKABLE void requestVersion();
    void requestEnrolledImage(uint32_t offset, uint32_t size);

    void uploadImageInfo(const UploadImageInformation &info);
    void uploadImageData(uint32_t offset, const uint8_t *data, uint32_t size);
    Q_INVOKABLE void requestCurrentStatus();
    Q_INVOKABLE void setDebugEnabled(bool enabled);
    void startOta();

    MessageId currentMessageId() const;
    static std::string protocolDataFormatString(const uint8_t *data, int size);
signals:
    /**
     * @brief newVerifyResult
     * @param userid
     * @param username
     * @param elapsed ms毫秒
     */
    void newVerifyResult(uint16_t userid, const QString &username, uint16_t elapsed);
    void newEnrollResult(uint16_t userid);
    void newPalmFeature(const PalmFeature &feature);
    void newImageInfo(MessageId id, uint32_t size, const uint8_t *md5);
    void newImageSliceData(const std::vector<uint8_t> &data);
    void errorOccurred(NoteId note, const QString &error, const QString &detailMessage = "");
    void commandStarted(ModuleCommunication::MessageId messageId);
    void commandFinished(MessageId messageId, MessageStatus status);
    void currentMessageIdChanged();
    void verisonChanged();

protected:
    void processPackage(const uint8_t *data, uint16_t size);
    void onReadyRead();
    void onErrorOccurred(QSerialPort::SerialPortError error);
    std::pair<uint8_t *, uint32_t> generateFrame(MessageId command, const uint8_t *data = nullptr, uint16_t size = 0);
    void setCurrentMessageIdStatus(ModuleCommunication::MessageId messageId);

private:
    std::shared_ptr<QSerialPort> m_serialPort;
    QByteArray m_receivedBuffer;
    MessageId m_currentMessageId = ModuleCommunication::Idle;
    QString m_verison;
    int m_otaVerison;
};

namespace std {
std::ostream &operator<<(std::ostream &stream, const ModuleCommunication::PalmState &element);
} // namespace std

#endif // MODULECOMMUNICATION_H
