#include "sam_io.h"

#include <htslib/sam.h>
#include <libdeflate.h>
#include <stdint.h>
#include <stdio.h>
#include <zlib.h>
#include <spdlog/spdlog.h>

#include "const_val.h"

namespace nsgv {
extern bool gIsBigEndian;
};

int bgzfUncompress(uint8_t *dst, size_t *dlen, const uint8_t *src, size_t slen, uint32_t expected_crc) {
    struct libdeflate_decompressor *z = libdeflate_alloc_decompressor();
    if (!z) {
        hts_log_error("Call to libdeflate_alloc_decompressor failed");
        return -1;
    }

    int ret = libdeflate_deflate_decompress(z, src, slen, dst, *dlen, dlen);
    libdeflate_free_decompressor(z);

    if (ret != LIBDEFLATE_SUCCESS) {
        hts_log_error("Inflate operation failed: %d", ret);
        return -1;
    }

    uint32_t crc = libdeflate_crc32(0, (unsigned char *)dst, *dlen);
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    // Pretend the CRC was OK so the fuzzer doesn't have to get it right
    crc = expected_crc;
#endif
    if (crc != expected_crc) {
        hts_log_error("CRC32 checksum mismatch");
        return -2;
    }

    return 0;
}

// 读取并解压一个gz block
size_t readUncompressOneBlock(FILE *fpr, uint8_t *fBuf, DataBuffer *uDataPtr) {
    DataBuffer &uData = *uDataPtr;
    size_t readState = 0;
    int blockLen = 0;                 // 每个gz block的大小
    size_t dlen = SINGLE_BLOCK_SIZE;  // gz block解压之后的真正大小
    readState = fread(fBuf, 1, BLOCK_HEADER_LENGTH, fpr);  // 先读取一个gz block的头部
    blockLen = unpackInt16(&fBuf[16]) + 1;                // block的字节数
    readState = fread(&fBuf[BLOCK_HEADER_LENGTH], 1, blockLen - BLOCK_HEADER_LENGTH, fpr);
    // 解压gz block
    uint32_t crc = le_to_u32(fBuf + blockLen - 8);
    size_t newDataSize = uData.maxLen;
    while (uData.curLen + SINGLE_BLOCK_SIZE > newDataSize) newDataSize *= 2;
    uData.reAllocMem(newDataSize);  // 需要重新开辟空间
    int ret = bgzfUncompress(&uData.data[uData.curLen], &dlen, (Bytef *)fBuf + BLOCK_HEADER_LENGTH,
                         blockLen - BLOCK_HEADER_LENGTH, crc);
    if (ret < 0) {
        spdlog::error("Error decompressing gz block");
        exit(0);
    }
    uData.curLen += dlen;
    spdlog::info("gz block size: {}, uncompressed size: {}", blockLen, dlen);
    return readState;
}

// 解析sam头部信息
void parseSamHeader(FILE *fpr, HeaderBuf &hdrBuf) {
    const int kMaxBlockSize = SINGLE_BLOCK_SIZE;
    sam_hdr_t *header = NULL;
    uint8_t *fBuf = (uint8_t*)malloc(kMaxBlockSize);
    DataBuffer uData; // 用来放解压缩后的数据，待解析
    int magicLen;
    int32_t i, nameLen, numNames = 0;

    header = sam_hdr_init();       // 初始化header
    uData.allocMem(kMaxBlockSize); // 初始化解压数据的buffer
    readUncompressOneBlock(fpr, fBuf, &uData); // 读取第一个gz block

    // 解析header
    magicLen = 4;
    if (memcmp(uData.data, "BAM\1", magicLen)) {
        spdlog::error("Invalid BAM binary header");
        return;
    }
    uData.readPos += magicLen; // 在解压数据中读取了magicLen个字节
    header->l_text = le_to_u32(uData.data + uData.readPos); uData.readPos += 4; // 解析l_text的长度
    header->text = (char *)malloc(header->l_text + 1);
    header->text[header->l_text] = 0; // 在text末尾添加'\0'

    while (uData.readPos + header->l_text > uData.curLen) { // header内容超过了一个block，继续读
        readUncompressOneBlock(fpr, fBuf, &uData);          // 读取包含header内容的gz block
    }

    memcpy(header->text, &uData.data[uData.readPos], header->l_text);
    uData.readPos += header->l_text;

    memcpy(&header->n_targets, &uData.data[uData.readPos], 4); // n_targets
    uData.readPos += 4;
    spdlog::info("num target: {}", header->n_targets);
    if (nsgv::gIsBigEndian) ed_swap_4p(&header->n_targets);
    if (header->n_targets > 0) {
        header->target_name = (char **)calloc(header->n_targets, sizeof(char *));
        header->target_len = (uint32_t *)calloc(header->n_targets, sizeof(uint32_t));
    } else {
        header->target_name = NULL;
        header->target_len = NULL;
    }

    for (int i = 0; i != header->n_targets; ++i) {
        while (uData.readPos + 4 > uData.curLen) readUncompressOneBlock(fpr, fBuf, &uData);
        memcpy(&nameLen, &uData.data[uData.readPos], 4);
        uData.readPos += 4;
        if (nsgv::gIsBigEndian) ed_swap_4p(&nameLen);
        header->target_name[i] = (char *)malloc(nameLen);
        ++numNames;
        while (uData.readPos + nameLen > uData.curLen) readUncompressOneBlock(fpr, fBuf, &uData);
        memcpy(header->target_name[i], &uData.data[uData.readPos], nameLen);
        uData.readPos += nameLen;
        if (header->target_name[i][nameLen - 1] != '\0') {
            /* Fix missing NUL-termination.  Is this being too nice?
               We could alternatively bail out with an error. */
            char *newName = (char*)realloc(header->target_name[i], nameLen + 1);
            header->target_name[i] = newName;
            header->target_name[i][nameLen] = '\0';
        }
        while (uData.readPos + 4 > uData.curLen) readUncompressOneBlock(fpr, fBuf, &uData);
        memcpy(&header->target_len[i], &uData.data[uData.readPos], 4);
        uData.readPos += 4;
        if (nsgv::gIsBigEndian) ed_swap_4p(&header->target_len[i]);

        // spdlog::info("nameLen {}, {}, {}", nameLen, header->target_len[i], header->target_name[i]);
    }
    // spdlog::info("test res: {} {} {} {}", header->l_text, dlen, header->n_targets, header->text);

    spdlog::info("udata: readPos: {}, curLen: {}, maxLen: {}", uData.readPos, uData.curLen, uData.maxLen);

    hdrBuf.header = header;
    if (uData.readPos < uData.curLen) { // 如果还有数据，则将其保存在hdrBuf中，用以后续的bam解析
        hdrBuf.data = (uint8_t *)malloc(uData.curLen - uData.readPos);
        memcpy(hdrBuf.data, &uData.data[uData.readPos], uData.curLen - uData.readPos);
        hdrBuf.dataLen = uData.curLen - uData.readPos;
    }

    free(fBuf);
}