#include <stdint.h>
#include <string.h>

#include "fs_private.h"

pack_t *FS_LoadZipFile(const char *zipfile, const char *basename)
{
    coduo_zip_global_info_t globalInfo;
    struct coduo_unz_s *zipFile;
    int zipStatus;
    uint32_t namesLength;
    uint32_t index;
    fileInPack_t *packFiles;
    char *nameCursor;
    uint32_t *checksums;
    uint32_t checksumCount;
    int32_t hashSize;
    pack_t *pack;
    fileInPack_t **hashTable;
    char filename[CODUO_FS_PACK_NAME_SIZE];
    coduo_zip_file_info_t fileInfo;
    uint32_t hash;
    int32_t basenameIndex;
    size_t basenameLength;

    checksumCount = 0;
    zipFile = Unzip_Open(zipfile);
    zipStatus = Unzip_GetGlobalInfo(zipFile, &globalInfo);
    if (zipStatus != 0) {
        return NULL;
    }

    fs_packFiles += globalInfo.number_entry;
    namesLength = 0;
    Unzip_GoToFirstFile(zipFile);
    index = 0;
    while (index < globalInfo.number_entry) {
        zipStatus =
            Unzip_GetCurrentFileInfo(zipFile, &fileInfo, filename,
                                     CODUO_FS_PACK_NAME_SIZE, NULL, 0, NULL,
                                     0);
        /* ORIGINAL_BINARY_BUG: stock breaks here but retains the archive
         * header's full entry count; see
         * original-bugs/coduomp-pak-metadata-failure-count.md. */
        if (zipStatus != 0) {
            break;
        }
        namesLength += (uint32_t)strlen(filename) + 1;
        Unzip_GoToNextFile(zipFile);
        index++;
    }

    packFiles = Z_Malloc((size_t)globalInfo.number_entry *
                             sizeof(*packFiles) +
                         (size_t)namesLength);
    nameCursor = (char *)(packFiles + globalInfo.number_entry);
    checksums =
        Z_Malloc((size_t)globalInfo.number_entry * sizeof(*checksums));

    hashSize = 1;
    while (hashSize < 0x401 && (uint32_t)hashSize <= globalInfo.number_entry) {
        hashSize <<= 1;
    }

    pack = Z_Malloc(sizeof(*pack) +
                    (size_t)hashSize * sizeof(*hashTable));
    pack->hashSize = hashSize;
    pack->hashTable = (fileInPack_t **)(pack + 1);
    hashTable = pack->hashTable;
    for (index = 0; (int32_t)index < pack->hashSize; index++) {
        hashTable[index] = NULL;
    }

    Q_strncpyz(pack->pakFilename, zipfile, CODUO_FS_PACK_NAME_SIZE);
    Q_strncpyz(pack->pakBasename, basename, CODUO_FS_PACK_NAME_SIZE);
    basenameLength = strlen(pack->pakBasename);
    if (basenameLength > 4 &&
        Q_stricmp(pack->pakBasename + basenameLength - 4, ".pk3") == 0) {
        pack->pakBasename[basenameLength - 4] = '\0';
    }

    pack->zipFile = zipFile;
    /* ORIGINAL_BINARY_BUG: this publishes the header count even if either
     * metadata pass stops early; see
     * original-bugs/coduomp-pak-metadata-failure-count.md. */
    pack->numFiles = (int32_t)globalInfo.number_entry;
    Unzip_GoToFirstFile(zipFile);
    index = 0;
    while (index < globalInfo.number_entry) {
        zipStatus =
            Unzip_GetCurrentFileInfo(zipFile, &fileInfo, filename,
                                     CODUO_FS_PACK_NAME_SIZE, NULL, 0, NULL,
                                     0);
        /* ORIGINAL_BINARY_BUG: a failed row leaves the remaining full-count
         * file-list records unpublished; see
         * original-bugs/coduomp-pak-metadata-failure-count.md. */
        if (zipStatus != 0) {
            break;
        }

        if (fileInfo.uncompressed_size != 0) {
            checksums[checksumCount] = FS_LittleLong(fileInfo.crc);
            checksumCount++;
        }

        Q_strlwr(filename);
        hash = FS_HashFileName(filename, pack->hashSize);
        packFiles[index].name = nameCursor;
        strcpy(packFiles[index].name, filename);

        basenameIndex = (int32_t)strlen(nameCursor) - 1;
        while (basenameIndex >= 0 && nameCursor[basenameIndex] != '/' &&
               nameCursor[basenameIndex] != '\\') {
            basenameIndex--;
        }
        packFiles[index].basename = nameCursor + basenameIndex + 1;

        nameCursor += strlen(filename) + 1;
        Unzip_GetCurrentFilePosition(zipFile, &packFiles[index]);

        packFiles[index].next = hashTable[hash];
        hashTable[hash] = &packFiles[index];

        Unzip_GoToNextFile(zipFile);
        index++;
    }

    pack->checksum =
        Com_BlockChecksum(checksums, (int32_t)(checksumCount *
                                               sizeof(*checksums)));
    pack->pureChecksum =
        Com_BlockChecksumKey(checksums,
                             (int32_t)(checksumCount * sizeof(*checksums)),
                             (int)FS_LittleLong((uint32_t)fs_checksumFeed));
    pack->checksum = (int32_t)FS_LittleLong((uint32_t)pack->checksum);
    pack->pureChecksum = (int32_t)FS_LittleLong((uint32_t)pack->pureChecksum);
    Z_Free(checksums);
    pack->fileList = packFiles;

    return pack;
}
