// SPDX-FileCopyrightText: 2011 - 2022 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later
#define _POSIX_C_SOURCE 200112L
#define _XOPEN_SOURCE 700

#include "waylandclipboardprotocol.h"
#include "log.h"

#include <stdlib.h>
#include <wayland-client-protocol.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>

long long getCurrentTime()
{
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);                                          // 获取当前系统时间（毫秒）
    long long milliseconds = (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; // 将时间转换为毫秒
    return milliseconds;
}

// 定义文件头结构体
struct datafile_header
{
    int magic;   // 魔数，用于标识文件类型
    int version; // 版本号，用于标识文件的格式或内容
    int width;
    int height;
    uint32_t size; // 文件大小，不包括文件头
};
// 定义一个函数，用于创建并写入文件头
FILE *create_file_header(const char *filename, int version)
{
    struct datafile_header header;
    header.magic = 0x12345678;         // 假设魔数为0x12345678
    header.version = version;          //
    header.width = 0;                  //
    header.height = 0;                 //
    header.size = 0;                   // 文件大小暂时为0，稍后更新
    FILE *fp = fopen(filename, "wb+"); // 以二进制读写模式打开文件
    if (fp == NULL)
    {
        log_error("Failed to open the file\n");
        return NULL;
    }
    fwrite(&header, sizeof(struct datafile_header), 1, fp); // 写入文件头
    return fp;                                              // 返回文件指针
}

// 定义一个函数，用于写入数据
void write_file_data(FILE *fp, char *data, int length)
{
    fwrite(data, sizeof(char), length, fp); // 写入数据
}

// 定义一个函数，用于更新文件头中的文件大小
void update_file_size(FILE *fp, int size)
{
    fseek(fp, sizeof(int) * 4, SEEK_SET); // 定位到文件头中的size字段
    fwrite(&size, sizeof(int), 1, fp);    // 写入文件大小
}

// 定义一个函数，用于关闭文件
void close_file(FILE *fp)
{
    fclose(fp); // 关闭文件
}

// 定义一个函数，用于读取文件头
FILE *read_file_header(const char *filename, struct datafile_header *header)
{

    // 重新打开文件，假设文件名为test.bin
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL)
    {
        log_error("Failed to open the file\n");
        return NULL;
    }
    // 读取文件头
    fread(header, sizeof(struct datafile_header), 1, fp); // 读取文件头
    // 验证文件头的魔数和版本号，假设版本号为1
    if (verify_file_header(header, 1))
    {
        log_error("The header of the file is verified\n");
    }
    else
    {
        log_error("File header validation failed\n");
        close_file(fp); // 关闭文件
        return NULL;
    }
    return fp;
}

// 定义一个函数，用于读取数据
// void read_file_data(FILE *fp, char *data, int length)
// {
//     fread(data, sizeof(char), length, fp); // 读取数据
// }

uint32_t read_file_data(FILE *fp, char *data)
{
    return fread(data, sizeof(char), WIRTE_SIZE, fp); // 读取数据
}

// 定义一个函数，用于验证文件头的魔数和版本号
int verify_file_header(struct datafile_header *header, int version)
{
    return header->magic == 0x12345678 && header->version == version; // 返回验证结果
}
void getClipboardDataFileName(WaylandClipboardProtocol *clip, char *filepath, int index)
{
    // 定义一个字符数组，用于存储文件名
    strcat(filepath, clip->clipDatapath);
    char vFilePath[50];
    memset(vFilePath, 0, 50);
    sprintf(vFilePath, "/clipboard/Data/%lld_%d.bin", getCurrentTime(), index); // 用sprintf函数将当前时间转换为字符串，并拼接到文件名中
    strcat(filepath, vFilePath);
}

void *Xmalloc(size_t size)
{
    void *ptr = malloc(size);
    if (!ptr)
    {
        return NULL;
    }
    return ptr;
}

void *Xrealloc(void *ptr, size_t size)
{
    ptr = realloc(ptr, size);
    if (!ptr)
    {
        return NULL;
    }
    return ptr;
}

char *Xstrdup(const char *s)
{
    char *duplicate = strdup(s);
    if (!duplicate)
    {
        return NULL;
    }
    return duplicate;
}

WaylandClipboardProtocol *WaylandClip_Init(void)
{
    WaylandClipboardProtocol *ret = Xmalloc(sizeof(WaylandClipboardProtocol));

    ret->selection_o = Offer_Init();
    ret->selection_s = Source_Init();

    return ret;
}

void WaylandClip_Destroy(WaylandClipboardProtocol *clip)
{
    if (clip->selection_s)
    {
        pthread_mutex_lock(&clip->cond_lock);
        Source_Destroy(clip->selection_s);
        pthread_mutex_unlock(&clip->cond_lock);
    }
    if (clip->selection_o)
    {
        Offer_Destroy(clip->selection_o);
    }
    zwlr_data_control_device_v1_destroy(clip->dmng);
    zwlr_data_control_manager_v1_destroy(clip->cmng);
    wl_seat_destroy(clip->seat);
    wl_display_disconnect(clip->display);
    free(clip);
}

void WaylandClip_ForkDestroy(WaylandClipboardProtocol *clip)
{
    if (clip->selection_s)
    {
        pthread_mutex_lock(&clip->cond_lock);
        wayland_clip_clear_selection(clip);
        Source_Destroy(clip->selection_s);
        wl_display_flush(clip->display);
        pthread_mutex_unlock(&clip->cond_lock);
    }
    if (clip->selection_o)
    {
        Offer_Destroy(clip->selection_o);
    }
    zwlr_data_control_device_v1_destroy(clip->dmng);
    zwlr_data_control_manager_v1_destroy(clip->cmng);
    wl_seat_destroy(clip->seat);
    wl_display_disconnect(clip->display);
    free(clip);
}

void WaylandClip_Sync_Buffers(WaylandClipboardProtocol *clip)
{
    source_buffer *src = clip->selection_s;
    offer_buffer *ofr = clip->selection_o;

    Source_Clear(src);
    for (int i = 0; i < ofr->num_types; i++)
    {
        if (!ofr->invalid_data[i])
        {
            if (ofr->data[i])
            {
                src->data[src->num_types] = Xmalloc(ofr->len[i]);
                memcpy(src->data[src->num_types], ofr->data[i],
                       ofr->len[i]);
            }
            memcpy(src->types[src->num_types].filepath, ofr->types[i].filepath, FILIE_PATH);

            src->len[src->num_types] = ofr->len[i];
            src->types[src->num_types].type =
                Xstrdup(ofr->types[i].type);
            src->num_types++;
        }
    }
}

static void
data_control_offer_mime_handler(void *data,
                                struct zwlr_data_control_offer_v1 *data_offer,
                                const char *mime_type)
{
    WaylandClipboardProtocol *clip = (WaylandClipboardProtocol *)data;
    offer_buffer *ofr = clip->selection_o;

    if (ofr->num_types < (MAX_MIME_TYPES - 1))
    {
        uint8_t index = ofr->num_types;
        ofr->types[index].type = Xstrdup(mime_type);
        ofr->types[index].pos = index;
        ofr->num_types++;
    }
    else
    {
        log_error("Failed to copy mime type: %s\n", mime_type);
    }
}

const struct zwlr_data_control_offer_v1_listener
    zwlr_data_control_offer_v1_listener =
        {
            .offer = data_control_offer_mime_handler};

static void data_control_device_selection_handler(
    void *data, struct zwlr_data_control_device_v1 *control_device,
    struct zwlr_data_control_offer_v1 *data_offer)
{
    WaylandClipboardProtocol *clip = (WaylandClipboardProtocol *)data;
    clip->selection_o->buf = SELECTION;
}

static void data_control_device_primary_selection_handler(
    void *data, struct zwlr_data_control_device_v1 *control_device,
    struct zwlr_data_control_offer_v1 *data_offer)
{
    WaylandClipboardProtocol *clip = (WaylandClipboardProtocol *)data;
    clip->selection_o->buf = PRIMARY;
}

static void data_control_device_finished_handler(
    void *data, struct zwlr_data_control_device_v1 *control_device)
{
    zwlr_data_control_device_v1_destroy(control_device);
}

static void data_control_device_data_offer_handler(
    void *data, struct zwlr_data_control_device_v1 *control_device,
    struct zwlr_data_control_offer_v1 *data_offer)
{
    WaylandClipboardProtocol *clip = (WaylandClipboardProtocol *)data;
    Offer_Clear(clip->selection_o);
    clip->selection_o->offer = data_offer;
    zwlr_data_control_offer_v1_add_listener(
        data_offer, &zwlr_data_control_offer_v1_listener, data);
}

const struct zwlr_data_control_device_v1_listener
    zwlr_data_control_device_v1_listener =
        {
            .data_offer = data_control_device_data_offer_handler,
            .selection = data_control_device_selection_handler,
            .primary_selection = data_control_device_primary_selection_handler,
            .finished = data_control_device_finished_handler};

void WaylandClip_Watch(WaylandClipboardProtocol *clip)
{
    zwlr_data_control_device_v1_add_listener(
        clip->dmng, &zwlr_data_control_device_v1_listener, clip);
}

void WaylandClip_Get_Selection(WaylandClipboardProtocol *clip)
{
    offer_buffer *ofr = clip->selection_o;
    for (int i = 0; i < ofr->num_types; i++)
    {
        int fds[2];
        if (pipe(fds) == -1)
        {
            log_error("Failed to create pipe\n");
        }

        fcntl(fds[0], F_SETFD, FD_CLOEXEC);
        fcntl(fds[0], F_SETFL, O_SYNC);
        fcntl(fds[1], F_SETFD, FD_CLOEXEC);
        fcntl(fds[1], F_SETFL, O_SYNC);

        struct pollfd watch_for_data = {.fd = fds[0], .events = POLLIN};

        /* Events need to be dispatched and flushed so the other client
         * can recieve the fd */
        zwlr_data_control_offer_v1_receive(ofr->offer, ofr->types[i].type,
                                           fds[1]);
        wl_display_dispatch_pending(clip->display);
        wl_display_flush(clip->display);

        /* Allocate max size for simplicity's sake */
        ofr->data[i] = Xmalloc(MAX_DATA_SIZE);
        memset(ofr->types[i].filepath, 0, FILIE_PATH);
        getClipboardDataFileName(clip, ofr->types[i].filepath, i);

        FILE *fp = create_file_header(ofr->types[i].filepath, 1);

        if (fp == NULL)
        {
            log_error("Failed to open data file\n");
            continue;
        }

        int wait_time;
        if (!strncmp("image/png", ofr->types[i].type, strlen("image/png")) ||
            !strncmp("image/jpeg", ofr->types[i].type, strlen("image/jpeg")) ||
            !strncmp("image/bmp", ofr->types[i].type, strlen("image/bmp")))
        {
            wait_time = WAIT_TIME_LONG;
        }
        else
        {
            wait_time = WAIT_TIME_SHORT;
        }

        bool textZeroflag =false;
        if (!strncmp("text/plain", ofr->types[i].type, strlen("text/plain")))
        {
            textZeroflag =true;
        }

        void *buffer_array = malloc(READ_SIZE);

        while (poll(&watch_for_data, 1, wait_time) > 0)
        {
            memset(buffer_array, 0, READ_SIZE);

            int bytes_read = read(fds[0], buffer_array, READ_SIZE);

            if (ofr->len[i] < (MAX_DATA_SIZE - READ_SIZE))
            {
                void *sub_array = ofr->data[i] + ofr->len[i];
                memcpy(sub_array, buffer_array, bytes_read); // 拷贝数据类型
            }

            if(textZeroflag && ((char *)buffer_array)[0] == '\0' && bytes_read  > 1)
            {
                memcpy(buffer_array,buffer_array+1,bytes_read-1);
                ((char *)buffer_array)[bytes_read-1] = '\0';
                bytes_read -= 1;
                log_error("buffer_array  is 0, %d\n",bytes_read);
            }

            /* If we get an error (-1) dont change anything */
            wait_time = (bytes_read > 0) ? WAIT_TIME_LONGEST : 0;
            ofr->len[i] += (bytes_read > 0) ? bytes_read : 0;

            write_file_data(fp, (char *)buffer_array, bytes_read);

            if (ofr->len[i] >= (MAX_FILE_DATA_SIZE - (READ_SIZE * 2)))
            {
                log_error("Source is too large to copy\n");
                ofr->invalid_data[i] = true;
                break;
            }

            if (bytes_read < READ_SIZE)
            {
                break;
            }
        }

        update_file_size(fp, ofr->len[i]);
        close_file(fp);

        close(fds[0]);
        close(fds[1]);
        free(buffer_array);
        buffer_array = NULL;

        if (ofr->len[i] == 0)
        {
            ofr->invalid_data[i] = true;
        }
        else if (ofr->len[i] < (MAX_DATA_SIZE - READ_SIZE))
        {
            ofr->data[i] = Xrealloc(ofr->data[i], ofr->len[i]);
        }
        else
        {
            free(ofr->data[i]);
            ofr->data[i] = NULL;
        }
    }
}

offer_buffer *Offer_Init(void)
{
    offer_buffer *ofr = Xmalloc(sizeof(offer_buffer));
    memset(ofr, 0, sizeof(offer_buffer));
    for (int i = 0; i < MAX_MIME_TYPES; i++)
    {
        ofr->len[i] = 0;
        ofr->data[i] = NULL;
        ofr->invalid_data[i] = false;
    }
    return ofr;
}

void Offer_Destroy(offer_buffer *ofr)
{
    for (int i = 0; i < ofr->num_types; i++)
    {
        if (ofr->data[i])
        {
            free(ofr->data[i]);
            ofr->data[i] = NULL;
        }
        free(ofr->types[i].type);
    }
    free(ofr);
}

void Offer_Clear(offer_buffer *ofr)
{
    for (int i = 0; i < ofr->num_types; i++)
    {
        /* src->data isn't guaranteed to exist as get_selection may not have
           been called thus we set it to NULL to be able to tell */
        if (ofr->data[i])
        {
            free(ofr->data[i]);
            ofr->data[i] = NULL;
        }
        free(ofr->types[i].type);
        ofr->types[i].pos = 0;
        ofr->len[i] = 0;
        ofr->invalid_data[i] = false;
    }
    ofr->num_types = 0;
    ofr->offer = NULL;
}

static void
data_control_source_send_handler(void *data,
                                 struct zwlr_data_control_source_v1 *data_src,
                                 const char *mime_type, int fd)
{
    WaylandClipboardProtocol *clip = (WaylandClipboardProtocol *)data;
    source_buffer *src = clip->selection_s;

    for (int i = 0; i < src->num_types; i++)
    {
        if (!strcmp(mime_type, src->types[i].type))
        {
            if (src->data[i] == NULL)
            {
                struct datafile_header header;
                FILE *fp = read_file_header(src->types[i].filepath, &header);

                if (fp == NULL)
                {
                    log_error("Failed to open data file\n");
                    continue;
                }
                char bufferData[WIRTE_SIZE];
                for (size_t i = 0; i < header.size; i = i + WIRTE_SIZE)
                {
                    memset(bufferData, 0, WIRTE_SIZE);
                    /* code */
                    uint32_t readbyte = read_file_data(fp, bufferData);

                    write(fd, bufferData, readbyte);
                }
                close(fd);
                close_file(fp);
            }
            else
            {
                write(fd, src->data[i], src->len[i]);
                close(fd);
            }
        }
    }
}

static void data_control_source_cancelled_handler(
    void *data, struct zwlr_data_control_source_v1 *data_src)
{
    WaylandClipboardProtocol *clip = (WaylandClipboardProtocol *)data;
    clip->selection_s->expired = true;
    zwlr_data_control_source_v1_destroy(data_src);
}

void wayland_clip_clear_selection(WaylandClipboardProtocol *clip)
{
    zwlr_data_control_device_v1_set_selection(clip->dmng, NULL);
}

const struct zwlr_data_control_source_v1_listener
    zwlr_data_control_source_v1_listener =
        {
            .send = data_control_source_send_handler,
            .cancelled = data_control_source_cancelled_handler};

void WaylandClip_Set_Selection(WaylandClipboardProtocol *clip)
{
    source_buffer *src = clip->selection_s;
    struct zwlr_data_control_source_v1 *data_src =
        zwlr_data_control_manager_v1_create_data_source(clip->cmng);
    src->source = data_src;

    zwlr_data_control_source_v1_add_listener(
        data_src, &zwlr_data_control_source_v1_listener, clip);

    for (int i = 0; i < src->num_types; i++)
    {
        zwlr_data_control_source_v1_offer(data_src, src->types[i].type);
    }

    zwlr_data_control_device_v1_set_selection(clip->dmng, data_src);
}

source_buffer *Source_Init(void)
{
    source_buffer *src = Xmalloc(sizeof(source_buffer));
    memset(src, 0, sizeof(source_buffer));
    src->expired = false;
    src->num_types = 0;
    return src;
}

void Source_Destroy(source_buffer *src)
{
    for (int i = 0; i < src->num_types; i++)
    {
        if (src->data[i])
        {
            free(src->data[i]);
            src->data[i] = NULL;
        }
        free(src->types[i].type);
    }
    if (src->source)
    {
        zwlr_data_control_source_v1_destroy(src->source);
    }
    free(src);
}

void Source_Clear(source_buffer *src)
{
    for (int i = 0; i < src->num_types; i++)
    {
        if (src->data[i])
        {
            free(src->data[i]);
            src->data[i] = NULL;
        }
        free(src->types[i].type);
        src->len[i] = 0;
    }
    src->num_types = 0;
    src->expired = false;
}
