/*
 * PACKed - PACKfile EDitor
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2011 Tecnic Software productions (TNSP)
 */
#include "dmlib.h"
#include "dmargs.h"
#include "dmpack.h"
#include "dmpackutil.h"
#include "dmres.h"
#include "dmmutex.h"
#include <errno.h>

#define	SET_MAX_FILES	  (4096)
#define SET_DEFAULT_PACK  "data.pak"

enum
{
    CMD_NONE = 0,
    CMD_CREATE,
    CMD_ADD,
    CMD_LIST,
    CMD_EXTRACT
} DCOMMAND;

enum
{
    PACK_EXTRACTED = 0x0001,
};

int    nsrcFilenames = 0, nexcFilenames = 0;
char * srcFilenames[SET_MAX_FILES];
char * excFilenames[SET_MAX_FILES];

char * optPackFilename = NULL;
BOOL   optCompress = TRUE;
int    optCommand = CMD_NONE;
int    optDefResFlags = 0;


static DMOptArg optList[] =
{
    { 0, '?', "help",        "Show this help", OPT_NONE },
    { 1, 'p', "pack",        "Set pack filename (default: " SET_DEFAULT_PACK ")", OPT_ARGREQ },
    { 2, 'c', "create",      "Create and add files to PACK", OPT_NONE },
    { 3, 'a', "add",         "Add files to PACK", OPT_NONE },
    { 4, 'l', "list",        "List files in PACK", OPT_NONE },
    { 5, 'e', "extract",     "Extract files from PACK", OPT_NONE },
    { 6, 'n', "nocompress",  "No compression", OPT_NONE },
    { 7, 'v', "verbose",     "Increase verbosity", OPT_NONE },
    { 8, 'f', "resflags",    "Set default resource flags (-f 0xff)", OPT_ARGREQ },
    { 9, 'x', "exclude",     "Exclude name/glob pattern from add", OPT_ARGREQ },
};

static const int optListN = sizeof(optList) / sizeof(optList[0]);


void argShowHelp()
{
    dmPrintBanner(stdout, dmProgName, "[options] [-p <packfilename>] [filename[s]]");
    dmArgsPrintHelp(stdout, optList, optListN);
    fprintf(stdout,
    "\n"
    "Examples:\n"
    "$ %s -p test.pak -l         -- list files in test.pak\n"
    "$ %s -a foobar.jpg          -- add foobar.jpg in " SET_DEFAULT_PACK "\n"
    "$ %s -e foobar.jpg          -- extract foobar.jpg from " SET_DEFAULT_PACK "\n",
    dmProgName, dmProgName, dmProgName);
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    (void) optArg;
    switch (optN)
    {
        case 0:
            argShowHelp();
            exit(0);
            break;

        case 1:
            optPackFilename = optArg;
            break;
        case 2:
            optCommand = CMD_CREATE;
            break;
        case 3:
            optCommand = CMD_ADD;
            break;
        case 4:
            optCommand = CMD_LIST;
            break;
        case 5:
            optCommand = CMD_EXTRACT;
            break;

        case 6:
            optCompress = FALSE;
            break;

        case 7:
            dmVerbosity++;
            break;

        case 8:
            {
                int i;
                if (!dmGetIntVal(optArg, &i))
                {
                    dmError("Invalid flags value '%s'.\n", optArg);
                    return FALSE;
                }
                optDefResFlags = i;
            }
            break;
        
        case 9:
            if (nexcFilenames < SET_MAX_FILES)
            {
                excFilenames[nsrcFilenames] = currArg;
                nexcFilenames++;
            }
            else
            {
                dmError("Maximum number of exclusion patterns (%d) exceeded!\n",
                      SET_MAX_FILES);
                return FALSE;
            }
            break;

        default:
            dmError("Unknown argument '%s'.\n", currArg);
            return FALSE;
    }

    return TRUE;
}


BOOL argHandleFile(char *currArg)
{
    if (nsrcFilenames < SET_MAX_FILES)
    {
        srcFilenames[nsrcFilenames] = currArg;
        nsrcFilenames++;
    }
    else
    {
        dmError("Maximum number of input files (%d) exceeded!\n",
              SET_MAX_FILES);
        return FALSE;
    }
    return TRUE;
}


/* Compare a string to a pattern. Case-SENSITIVE version.
 * The matching pattern can consist of any normal characters plus
 * wildcards ? and *. "?" matches any character and "*" matches
 * any number of characters.
 */
BOOL dm_strmatch(const char *str, const char *pattern)
{
    BOOL didMatch = TRUE, isAnyMode = FALSE, isEnd = FALSE;
    const char *tmpPattern = NULL;

    // Check given pattern and string
    if (str == NULL || pattern == NULL)
        return FALSE;

    // Start comparision
    do {
        didMatch = FALSE;
        switch (*pattern)
        {
        case '?':
            // Any single character matches
            if (*str)
            {
                didMatch = TRUE;
                pattern++;
                str++;
            }
            break;

        case '*':
            didMatch = TRUE;
            pattern++;
            if (!*pattern)
                isEnd = TRUE;
            isAnyMode = TRUE;
            tmpPattern = pattern;
            break;

        case 0:
            if (isAnyMode)
            {
                if (*str)
                    str++;
                else
                    isEnd = TRUE;
            }
            else
            {
                if (*str)
                {
                    if (tmpPattern)
                    {
                        isAnyMode = TRUE;
                        pattern = tmpPattern;
                    }
                    else
                        didMatch = FALSE;
                }
                else
                    isEnd = TRUE;
            }
            break;
        default:
            if (isAnyMode)
            {
                if (*pattern == *str)
                {
                    isAnyMode = FALSE;
                    didMatch = TRUE;
                }
                else
                {
                    if (*str)
                    {
                        didMatch = TRUE;
                        str++;
                    }
                }
            }
            else
            {
                if (*pattern == *str)
                {
                    didMatch = TRUE;
                    if (*pattern)
                        pattern++;
                    if (*str)
                        str++;
                }
                else
                {
                    if (tmpPattern)
                    {
                        didMatch = TRUE;
                        isAnyMode = TRUE;
                        pattern = tmpPattern;
                    }
                }
            }

            if (!*str && !*pattern)
                isEnd = TRUE;
            break;

        }        // switch

    } while (didMatch && !isEnd);

    return didMatch;
}


BOOL dmCheckExcluded(const char *filename)
{
    int i;
    for (i = 0; i < nexcFilenames; i++)
    {
        if (dm_strmatch(filename, excFilenames[i]))
            return TRUE;
    }
    return FALSE;
}


int main(int argc, char *argv[])
{
    int i, res = 0;
    DMPackFile *pack = NULL;

#ifndef __WIN32
    stderr = stdout;
#endif

    // Parse arguments
    dmInitProg("packed", "Pack File Editor", "0.5", NULL, NULL);
    dmVerbosity = 1;
    
    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, argHandleFile, TRUE))
        exit(1);

    // Check PACK filename
    if (optPackFilename == NULL)
        optPackFilename = SET_DEFAULT_PACK;

    if (optCommand == CMD_NONE)
    {
        argShowHelp();
        dmError("Nothing to do.\n");
        exit(0);
        return 0;
    }
    
    dmMsg(1, "Processing %s ...\n", optPackFilename);

    // Execute command
    switch (optCommand)
    {
    case CMD_CREATE:
    case CMD_ADD:
        switch (optCommand)
        {
        case CMD_CREATE:
            dmMsg(1, "Creating new PACK\n");
            res = dmPackCreate(optPackFilename, &pack);
            break;

        case CMD_ADD:
            dmMsg(1, "Opening existing PACK\n");
            res = dmPackOpen(optPackFilename, &pack, FALSE);
            break;
        }

        // Add files into PACK
        if (res == DMERR_OK)
        {
            dmMsg(1, "Adding files...\n");

            for (i = 0; i < nsrcFilenames; i++)
            if (!dmCheckExcluded(srcFilenames[i]))
            {
                DMPackEntry *node = NULL;
                int res = dmPackAddFile(pack, srcFilenames[i], optCompress, optDefResFlags, &node);

                if (res != DMERR_OK)
                {
                    dmPrint(1, "%-32s [ERROR:%d]\n",
                        srcFilenames[i], res);
                }
                else
                {
                    dmPrint(1, "%-32s ['%s', s=%d, c=%d, o=%ld, f=0x%04x]\n",
                        srcFilenames[i], node->filename,
                        node->size, node->length, node->offset, node->flags);
                }
            }

            dmMsg(1, "w=%d\n", dmPackWrite(pack));
            dmMsg(1, "c=%d\n", dmPackClose(pack));
        }
        else
        {
            dmError("Could not open packfile, error #%d: %s\n", res,
                  dmErrorStr(res));
        }
        break;

    case CMD_LIST:
        // List files in PACK
        res = dmPackOpen(optPackFilename, &pack, TRUE);
        if (res == DMERR_OK)
        {
            DMPackEntry *node;
            for (i = 0, node = pack->entries; node; i++)
                node = node->next;
            dmMsg(1, "%d files total\n", i);

            dmPrint(0, "%-32s | %8s | %8s | %8s | %s\n",
                "Name", "Size", "CSize", "Offset", "ResFlags");

            for (node = pack->entries; node != NULL; node = node->next)
            {
                BOOL match;
                
                // Check for matches
                if (nsrcFilenames > 0)
                {
                    match = FALSE;
                    for (i = 0; i < nsrcFilenames && !match; i++)
                    {
                        match = dm_strmatch(node->filename, srcFilenames[i]);
                    }
                }
                else
                    match = TRUE;

                if (match)
                {
                    dmPrint(0, "%-32s | %8d | %8d | %08x | %04x\n",
                        node->filename, node->size, node->length,
                        node->offset, node->flags);
                }
            }

            dmMsg(1, "c=%d\n", dmPackClose(pack));
        }
        else
            dmError("Could not open packfile, error #%d: %s\n", res,
                  dmErrorStr(res));
        break;

    case CMD_EXTRACT:
        // Extract files from PACK
        res = dmPackOpen(optPackFilename, &pack, TRUE);
        if (res == DMERR_OK)
        {
            DMPackEntry *node;
            FILE *resFile = fopen(DMRES_RES_FILE, "w");
            if (resFile == NULL)
            {
                dmError("Could not create resource output file '%s' #%d: %s\n",
                    DMRES_RES_FILE, errno, strerror(errno));
            }

            for (node = pack->entries; node != NULL; node = node->next)
            {
                BOOL match;
                
                // Check for matches
                if (nsrcFilenames > 0)
                {
                    match = FALSE;
                    for (i = 0; (i < nsrcFilenames) && !match; i++)
                    {
                        match = dm_strmatch(node->filename, srcFilenames[i]);
                    }
                }
                else
                    match = TRUE;

                if (match && (node->privFlags & PACK_EXTRACTED) == 0)
                {
                    // Mark as done
                    node->privFlags |= PACK_EXTRACTED;

                    // Print one entry
                    dmPrint(0, "Extracting: %-32s [siz=%d, cmp=%d, offs=0x%08x, flags=0x%04x]\n",
                            node->filename, node->size, node->length,
                            node->offset, node->flags);

                    dmPackExtractFile(pack, node);
                }
            }

            dmMsg(1, "c=%d\n", dmPackClose(pack));
            
            if (resFile != NULL)
                fclose(resFile);
        }
        else
            dmError("Could not open packfile, error #%d: %s\n", res,
                  dmErrorStr(res));
        break;

    }

    return 0;
}
