/*
 * DMLib
 * -- PACK-file additional utility routines
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2011 Tecnic Software productions (TNSP)
 */
#include "dmpackutil.h"
#include <zlib.h>
#include "dmfile.h"


DMPackEntry *dmPackEntryCopy(const DMPackEntry *src)
{
    DMPackEntry *node = dmPackEntryNew();
    if (node == NULL)
        return NULL;

    strncpy(node->filename, src->filename, sizeof(node->filename));
    node->filename[sizeof(node->filename) - 1] = 0;

    node->size     = src->size;
    node->offset   = src->offset;
    node->length   = src->length;
    node->flags    = src->flags;

    return node;
}


/*
 * CLOSE/WRITE the packfile
 */
int dmPackWrite(DMPackFile * pack)
{
    DMPackEntry *node;
    DMPackFileHeader hdr;

    if (pack == NULL)
        return DMERR_OK;

    if (pack->file == NULL)
        return DMERR_FOPEN;

    // Compute directory offset and number of entries
    memcpy(&hdr.ident, DPACK_IDENT, sizeof(hdr.ident));
    hdr.version = DPACK_VERSION;
    hdr.dirEntries = 0;
    hdr.dirOffset =
        sizeof(hdr.ident) + sizeof(hdr.version) +
        sizeof(hdr.dirEntries) + sizeof(hdr.dirOffset);

    node = pack->entries;
    while (node != NULL)
    {
        hdr.dirEntries++;
        hdr.dirOffset += node->length;
        node = node->next;
    }

    // Write PACK header
    if (fseek(pack->file, 0L, SEEK_SET) != 0)
        return DMERR_FSEEK;

    if (!dm_fwrite_str(pack->file, (Uint8 *) & hdr.ident, sizeof(hdr.ident)) ||
        !dm_fwrite_le16(pack->file, hdr.version) ||
        !dm_fwrite_le32(pack->file, hdr.dirEntries) ||
        !dm_fwrite_le32(pack->file, hdr.dirOffset))
        return DMERR_FWRITE;

    // Write the directory
    if (fseek(pack->file, hdr.dirOffset, SEEK_SET) != 0)
        return DMERR_FSEEK;

    node = pack->entries;
    while (node != NULL)
    {
        // Write one entry
        if (!dm_fwrite_str(pack->file, node->filename, sizeof(node->filename)) ||
            !dm_fwrite_le32(pack->file, node->size) ||
            !dm_fwrite_le32(pack->file, node->offset) ||
            !dm_fwrite_le32(pack->file, node->length) ||
            !dm_fwrite_le32(pack->file, node->flags))
            return DMERR_FWRITE;

        node = node->next;
    }

    return DMERR_OK;
}


/*
 * CREATE a packfile, for writing
 */
int dmPackCreate(const char *filename, DMPackFile ** pack)
{
    // Allocate packfile-structure
    *pack = (DMPackFile *) dmCalloc(1, sizeof(DMPackFile));
    if (*pack == NULL)
        return DMERR_MALLOC;

    // Open the file
    (*pack)->file = fopen(filename, "wb");
    if ((*pack)->file == NULL)
    {
        dmFree(*pack);
        return DMERR_FOPEN;
    }

    (*pack)->filename = dm_strdup(filename);

    // Set the result
    return DMERR_OK;
}


/*
 * ADD a file into the PACK
 */
int dmPackAddFile(DMPackFile * pack, const char *filename,
    BOOL doCompress, const Uint32 flags, DMPackEntry ** ppEntry)
{
    z_stream zstr;
    off_t startOffs;
    unsigned int zstrSize;
    FILE *inFile;
    Uint8 *inBuffer, *outBuffer;
    DMPackEntry entry, *node;
    int result;

    *ppEntry = NULL;

    if (pack == NULL)
        return DMERR_OK;

    if (pack->file == NULL)
        return DMERR_FOPEN;

    // Compute starting offset
    startOffs = sizeof(DMPackFileHeader);
    node = pack->entries;
    while (node != NULL)
    {
        startOffs += node->length;
        node = node->next;
    }

    // Seek to the position
    if (fseek(pack->file, startOffs, SEEK_SET) != 0)
        return DMERR_INVALID;

    // Read file data
    if ((inFile = fopen(filename, "rb")) == NULL)
        return -1;

    // Allocate temporary buffer
    inBuffer = (Uint8 *) dmMalloc(DPACK_TMPSIZE);
    if (!inBuffer)
    {
        fclose(inFile);
        return DMERR_MALLOC;
    }

    outBuffer = (Uint8 *) dmMalloc(DPACK_TMPSIZE);
    if (!outBuffer)
    {
        dmFree(inBuffer);
        fclose(inFile);
        return DMERR_MALLOC;
    }

    // Read (and possibly compress) the data
    zstrSize = 0;
    zstr.zalloc = (alloc_func) Z_NULL;
    zstr.zfree = (free_func) Z_NULL;
    zstr.opaque = (voidpf) Z_NULL;
    result = deflateInit(&zstr, (doCompress) ? Z_DEFAULT_COMPRESSION : 0);
    if (result != Z_OK)
    {
        dmFree(inBuffer);
        dmFree(outBuffer);
        fclose(inFile);
        return DMERR_COMPRESSION;
    }

    // Initialize compression streams
    result = Z_OK;
    while (!feof(inFile) && result == Z_OK)
    {
        zstr.avail_in = fread(inBuffer, sizeof(Uint8), DPACK_TMPSIZE, inFile);
        zstr.next_in = inBuffer;
        zstr.next_out = outBuffer;
        zstr.avail_out = DPACK_TMPSIZE;
        zstr.total_out = 0;
        result = deflate(&zstr, Z_FULL_FLUSH);

        if (result == Z_OK && zstr.total_out > 0)
        {
            zstrSize += zstr.total_out;
            fwrite(outBuffer, sizeof(Uint8), zstr.total_out, pack->file);
        }
    }

    // Create directory entry
    strncpy(entry.filename, filename, sizeof(entry.filename));
    entry.filename[sizeof(entry.filename) - 1] = 0;
    entry.size   = zstr.total_in;
    entry.offset = startOffs;
    entry.length = zstrSize;
    entry.flags  = flags;

    // Cleanup
    deflateEnd(&zstr);
    dmFree(inBuffer);
    dmFree(outBuffer);
    fclose(inFile);

    // Add directory entry
    if ((*ppEntry = dmPackEntryCopy(&entry)) == NULL)
        return DMERR_MALLOC;

    dmPackEntryInsert(&pack->entries, *ppEntry);

    return DMERR_OK;
}


/*
 * EXTRACT a file from the PACK
 */
int dmPackExtractFile(DMPackFile *pack, DMPackEntry * entry)
{
    z_stream zstr;
    FILE *outFile;
    Uint8 *inBuffer, *outBuffer;
    size_t inDataLeft;
    int ret;

    if (pack == NULL)
        return DMERR_OK;

    if (pack->file == NULL)
        return DMERR_FOPEN;

    // Seek to the position
    if (fseek(pack->file, entry->offset, SEEK_SET) != 0)
        return DMERR_INVALID;

    // Open destination file
    if ((outFile = fopen(entry->filename, "wb")) == NULL)
        return -1;

    // Allocate temporary buffer
    if ((inBuffer = (Uint8 *) dmMalloc(DPACK_TMPSIZE)) == NULL)
    {
        fclose(outFile);
        return DMERR_MALLOC;
    }

    if ((outBuffer = (Uint8 *) dmMalloc(DPACK_TMPSIZE)) == NULL)
    {
        dmFree(inBuffer);
        fclose(outFile);
        return DMERR_MALLOC;
    }

    // Read and uncompress the data
    zstr.zalloc = (alloc_func) Z_NULL;
    zstr.zfree = (free_func) Z_NULL;
    zstr.opaque = (voidpf) Z_NULL;
    ret = inflateInit(&zstr);
    if (ret != Z_OK)
    {
        dmFree(inBuffer);
        dmFree(outBuffer);
        fclose(outFile);
        return DMERR_COMPRESSION;
    }

    // Initialize compression streams
    inDataLeft = entry->length;
    ret = Z_OK;
    while (inDataLeft > 0 && ret == Z_OK)
    {
        if (inDataLeft >= DPACK_TMPSIZE)
            zstr.avail_in = fread(inBuffer, sizeof(Uint8), DPACK_TMPSIZE, pack->file);
        else
            zstr.avail_in = fread(inBuffer, sizeof(Uint8), inDataLeft, pack->file);

        inDataLeft -= zstr.avail_in;
        zstr.next_in = inBuffer;

        while (zstr.avail_in > 0 && ret == Z_OK)
        {
            zstr.next_out = outBuffer;
            zstr.avail_out = DPACK_TMPSIZE;
            zstr.total_out = 0;
            ret = inflate(&zstr, Z_FULL_FLUSH);
            if (zstr.total_out > 0)
            {
                fwrite(outBuffer, sizeof(Uint8), zstr.total_out, outFile);
            }
        }
    }

    // Cleanup
    inflateEnd(&zstr);
    dmFree(inBuffer);
    dmFree(outBuffer);
    fclose(outFile);

    return DMERR_OK;
}
