/*
 * NuFX archive manipulation library
 * Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
 * This is free software; you can redistribute it and/or modify it under the
 * terms of the BSD License, see the file COPYING-LIB.
 *
 * Debugging functions.  These are omitted from the non-debug build.
 */
#include "NufxLibPriv.h"

#if defined(DEBUG_MSGS)


/* pull a string out of one of the static arrays */
#define GetStaticString(index, staticArray) ( \
            (index) >= NELEM(staticArray) ? "<unknown>" : staticArray[index] \
        )

/* thread's thread_class */
static const char* gThreadClassNames[] = {
    "message_thread",
    "control_thread",
    "data_thread",
    "filename_thread",
};

/* thread's thread_format */
static const char* gThreadFormatNames[] = {
    "uncompressed",
    "Huffman Squeeze",
    "dynamic LZW/1",
    "dynamic LZW/2",
    "12-bit LZC",
    "16-bit LZC",
    "deflate",
    "bzip2"
};

/* days of the week */
static const char* gDayNames[] = {
    "[ null ]",
    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
/* months of the year */
static const char* gMonths[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

#define kNuDateOutputLen    64

/* file_sys_id values */
static const char* gFileSysIDs[] = {
    "Reserved/unknown ($00)", "ProDOS/SOS", "DOS 3.3", "DOS 3.2",
    "Apple II Pascal", "Macintosh (HFS)", "Macintosh (MFS)",
    "LISA file system", "Apple CP/M", "Reserved 0x09", "MS-DOS",
    "High-Sierra", "ISO 9660", "AppleShare"
};


/*
 * Convert a DateTime structure into something printable.
 *
 * The buffer passed in must hold at least kNuDateOutputLen bytes.
 *
 * Returns "buffer" for the benefit of printf() calls.
 */
static char* Nu_DebugDumpDate(const NuDateTime* pDateTime, char* buffer)
{
    char* cp;

    /* is it valid? */
    if (pDateTime->day > 30 || pDateTime->month > 11 || pDateTime->hour > 24 ||
        pDateTime->minute > 59)
    {
        strcpy(buffer, "   <invalid>   ");
        goto bail;
    }

    /* is it empty? */
    if ((pDateTime->second | pDateTime->minute | pDateTime->hour |
        pDateTime->year | pDateTime->day | pDateTime->month |
        pDateTime->extra | pDateTime->weekDay) == 0)
    {
        strcpy(buffer, "   [No Date]   ");
        goto bail;
    }

    cp = buffer;

    /* only print weekDay if one was stored */
    if (pDateTime->weekDay) {
        if (pDateTime->weekDay < NELEM(gDayNames))
            sprintf(cp, "%s, ", gDayNames[pDateTime->weekDay]);
        else
            sprintf(cp, "??%d, ", pDateTime->weekDay);
        cp += strlen(cp);
    }

    sprintf(cp, "%02d-%s-%04d  %02d:%02d:%02d",
        pDateTime->day+1, gMonths[pDateTime->month],
        pDateTime->year < 40 ? pDateTime->year + 2000 : pDateTime->year + 1900,
        pDateTime->hour, pDateTime->minute, pDateTime->second);

bail:
    sprintf(buffer + strlen(buffer), "  [s%d m%d h%d Y%d D%d M%d x%d w%d]",
        pDateTime->second, pDateTime->minute, pDateTime->hour,
        pDateTime->year, pDateTime->day, pDateTime->month, pDateTime->extra,
        pDateTime->weekDay);

    return buffer;
}


/*
 * Convert a buffer into a hexadecimal character string.
 *
 * The result will be 2x the size of the original, +1 for a null byte.
 */
static void ConvertToHexStr(const uint8_t* inBuf, int inLen, char* outBuf)
{
    while (inLen--) {
        *outBuf++ = HexConv((*inBuf >> 4) & 0x0f);
        *outBuf++ = HexConv(*inBuf & 0x0f);
        inBuf++;
    }
    *outBuf = '\0';
}


/*
 * Dump everything we know about pThread.
 */
void Nu_DebugDumpThread(const NuThread* pThread)
{
    static const char* kInd = "      ";
    NuThreadID threadID;
    const char* descr;

    Assert(pThread != NULL);

    printf("%sThreadClass:  0x%04x (%s)\n", kInd,
        pThread->thThreadClass,
        GetStaticString(pThread->thThreadClass, gThreadClassNames));
    printf("%sThreadFormat: 0x%04x (%s)\n", kInd,
        pThread->thThreadFormat,
        GetStaticString(pThread->thThreadFormat, gThreadFormatNames));

    threadID = NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind);
    switch (threadID) {
    case kNuThreadIDOldComment: descr = "old comment";  break;
    case kNuThreadIDComment:    descr = "comment";      break;
    case kNuThreadIDIcon:       descr = "icon";         break;
    case kNuThreadIDMkdir:      descr = "mkdir";        break;
    case kNuThreadIDDataFork:   descr = "data fork";    break;
    case kNuThreadIDDiskImage:  descr = "disk image";   break;
    case kNuThreadIDRsrcFork:   descr = "rsrc fork";    break;
    case kNuThreadIDFilename:   descr = "filename";     break;
    default:                    descr = "<unknown>";    break;
    }
    printf("%sThreadKind:   0x%04x (%s)\n", kInd,
        pThread->thThreadKind, descr);

    printf("%sThreadCRC: 0x%04x  ThreadEOF: %u  CompThreadEOF: %u\n", kInd,
        pThread->thThreadCRC, pThread->thThreadEOF, pThread->thCompThreadEOF);
    printf("%s*File data offset: %ld  actualThreadEOF: %d\n", kInd,
        pThread->fileOffset, pThread->actualThreadEOF);
}

/*
 * Dump everything we know about pRecord, including its threads and ThreadMods.
 *
 * Changes to existing records are made to the "copy" set, not the "orig"
 * set.  Pass in the "orig" copy in "pRecord", and optionally pass in the
 * "copy" set in "pXrefRecord" to glean data from both.
 */
static void Nu_DebugDumpRecord(NuArchive* pArchive, const NuRecord* pRecord,
    const NuRecord* pXrefRecord, Boolean isDeleted)
{
    NuError err;    /* dummy */
    static const char* kInd = "    ";
    char dateBuf[kNuDateOutputLen];
    const NuThreadMod* pThreadMod;
    const NuThread* pThread;
    uint32_t idx;

    Assert(pRecord != NULL);

    /*printf("PTR: pRecord=0x%08lx pXrefRecord=0x%08lx\n", (long) pRecord,
        (long) pXrefRecord);*/

    UNICHAR* filenameUNI = Nu_CopyMORToUNI(pRecord->filenameMOR);
    printf("%s%s%sFilename: '%s' (idx=%u)\n", kInd,
        isDeleted ? "[DEL] " : "",
        pXrefRecord != NULL && pXrefRecord->pThreadMods != NULL ? "[MOD] " : "",
        filenameUNI == NULL ? "<not specified>" : filenameUNI,
        pRecord->recordIdx);
    free(filenameUNI);
    printf("%sHeaderID: '%.4s'  VersionNumber: 0x%04x  HeaderCRC: 0x%04x\n",
        kInd,
        pRecord->recNufxID, pRecord->recVersionNumber, pRecord->recHeaderCRC);
    printf("%sAttribCount: %u  TotalThreads: %u\n", kInd,
        pRecord->recAttribCount, pRecord->recTotalThreads);
    printf("%sFileSysID: %u (%s)  FileSysInfo: 0x%04x ('%c')\n", kInd,
        pRecord->recFileSysID,
        GetStaticString(pRecord->recFileSysID, gFileSysIDs),
        pRecord->recFileSysInfo,
        NuGetSepFromSysInfo(pRecord->recFileSysInfo));
    /* do something fancy for ProDOS? */
    printf("%sFileType: 0x%08x  ExtraType: 0x%08x  Access: 0x%08x\n", kInd,
        pRecord->recFileType, pRecord->recExtraType, pRecord->recAccess);
    printf("%sCreateWhen:  %s\n", kInd,
        Nu_DebugDumpDate(&pRecord->recCreateWhen, dateBuf));
    printf("%sModWhen:     %s\n", kInd,
        Nu_DebugDumpDate(&pRecord->recModWhen, dateBuf));
    printf("%sArchiveWhen: %s\n", kInd,
        Nu_DebugDumpDate(&pRecord->recArchiveWhen, dateBuf));
    printf("%sStorageType: %u  OptionSize: %u  FilenameLength: %u\n", kInd,
        pRecord->recStorageType, pRecord->recOptionSize,
        pRecord->recFilenameLength);
    if (pRecord->recOptionSize) {
        char* outBuf = Nu_Malloc(pArchive, pRecord->recOptionSize * 2 +1);
        BailAlloc(outBuf);
        Assert(pRecord->recOptionList != NULL);
        ConvertToHexStr(pRecord->recOptionList, pRecord->recOptionSize, outBuf);
        printf("%sOptionList: [%s]\n", kInd, outBuf);
        Nu_Free(pArchive, outBuf);
    }

    printf("%s*ExtraCount: %d  RecFileOffset: %ld  RecHeaderLength: %d\n",
        kInd,
        pRecord->extraCount, pRecord->fileOffset, pRecord->recHeaderLength);

    for (idx = 0; idx < pRecord->recTotalThreads; idx++) {
        Boolean isFake;

        isFake = (idx >= pRecord->recTotalThreads - pRecord->fakeThreads);
        pThread = Nu_GetThread(pRecord, idx);
        Assert(pThread != NULL);

        printf("%s--Thread #%u (idx=%u)%s\n", kInd, idx, pThread->threadIdx,
            isFake ? " [FAKE]" : "");
        Nu_DebugDumpThread(pThread);
    }

    if (pXrefRecord != NULL)
        pThreadMod = pXrefRecord->pThreadMods;
    else
        pThreadMod = pRecord->pThreadMods;  /* probably empty */

    if (pThreadMod != NULL)
        printf("%s*ThreadMods -----\n", kInd);
    while (pThreadMod != NULL) {
        switch (pThreadMod->entry.kind) {
        case kNuThreadModAdd:
            printf("%s  *-ThreadMod ADD 0x%08x 0x%04x (sourceType=%d)\n", kInd,
                pThreadMod->entry.add.threadID,
                pThreadMod->entry.add.threadFormat,
                Nu_DataSourceGetType(pThreadMod->entry.add.pDataSource));
            break;
        case kNuThreadModUpdate:
            printf("%s  *-ThreadMod UPDATE %6d\n", kInd,
                pThreadMod->entry.update.threadIdx);
            break;
        case kNuThreadModDelete:
            printf("%s  *-ThreadMod DELETE %6d\n", kInd,
                pThreadMod->entry.delete.threadIdx);
            break;
        case kNuThreadModUnknown:
        default:
            Assert(0);
            printf("%s++ThreadMod UNKNOWN\n", kInd);
            break;
        }

        pThreadMod = pThreadMod->pNext;
    }

    /*printf("%s*TotalLength: %ld  TotalCompLength: %ld\n",
        kInd, pRecord->totalLength, pRecord->totalCompLength);*/
    printf("%s*TotalCompLength: %u\n", kInd, pRecord->totalCompLength);
    printf("\n");

bail:
    return;
}

/*
 * Dump the records in a RecordSet.
 */
static void Nu_DebugDumpRecordSet(NuArchive* pArchive,
    const NuRecordSet* pRecordSet, const NuRecordSet* pXrefSet)
{
    const NuRecord* pRecord;
    const NuRecord* pXrefRecord;
    Boolean doXref;
    long count;

    doXref = false;
    pXrefRecord = NULL;
    if (pXrefSet != NULL && Nu_RecordSet_GetLoaded(pXrefSet)) {
        pXrefRecord = Nu_RecordSet_GetListHead(pXrefSet);
        doXref = true;
    }

    /* dump every record, if we've loaded them */
    count = Nu_RecordSet_GetNumRecords(pRecordSet);
    pRecord = Nu_RecordSet_GetListHead(pRecordSet);
    if (pRecord != NULL) {
        Assert(count != 0);
        while (count--) {
            Assert(pRecord != NULL);

            if (pXrefRecord != NULL &&
                pRecord->recordIdx == pXrefRecord->recordIdx)
            {
                Nu_DebugDumpRecord(pArchive, pRecord, pXrefRecord, false);
                pXrefRecord = pXrefRecord->pNext;
            } else {
                Nu_DebugDumpRecord(pArchive, pRecord, NULL, doXref);
            }
            pRecord = pRecord->pNext;
        }
    } else {
        Assert(count == 0);
    }
}

/*
 * Dump the master header block.
 */
static void Nu_DebugDumpMH(const NuMasterHeader* pMasterHeader)
{
    static const char* kInd = "  ";
    char dateBuf1[kNuDateOutputLen];

    Assert(pMasterHeader != NULL);

    printf("%sNufileID: '%.6s'  MasterCRC: 0x%04x  TotalRecords: %u\n", kInd,
        pMasterHeader->mhNufileID, pMasterHeader->mhMasterCRC,
        pMasterHeader->mhTotalRecords);
    printf("%sArchiveCreateWhen: %s\n", kInd,
        Nu_DebugDumpDate(&pMasterHeader->mhArchiveCreateWhen, dateBuf1));
    printf("%sArchiveModWhen:    %s\n", kInd,
        Nu_DebugDumpDate(&pMasterHeader->mhArchiveModWhen, dateBuf1));
    printf("%sMasterVersion: %u  MasterEOF: %u\n", kInd,
        pMasterHeader->mhMasterVersion, pMasterHeader->mhMasterEOF);
}

/*
 * Dump everything we know about pArchive.
 *
 * This will only print the records that we have seen so far.  If the
 * application hasn't caused us to scan through all of the records in
 * the archive, then this won't be very interesting.  This will never
 * show any records for streaming-mode archives.
 */
void Nu_DebugDumpAll(NuArchive* pArchive)
{
    Assert(pArchive != NULL);

    printf("*Archive pathname: '%s'\n", pArchive->archivePathnameUNI);
    printf("*Archive type: %d\n", pArchive->archiveType);
    printf("*Header offset: %ld (junk offset=%ld)\n",
        pArchive->headerOffset, pArchive->junkOffset);
    printf("*Num records: %u orig, %u copy, %u new\n",
        Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet),
        Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet),
        Nu_RecordSet_GetNumRecords(&pArchive->newRecordSet));
    printf("*NuRecordIdx seed: %u  NuRecordIdx next: %u\n",
        pArchive->recordIdxSeed, pArchive->nextRecordIdx);

    /* master header */
    Nu_DebugDumpMH(&pArchive->masterHeader);

    printf("  *ORIG record set (x-ref with COPY):\n");
    Nu_DebugDumpRecordSet(pArchive, &pArchive->origRecordSet,
        &pArchive->copyRecordSet);
    printf("  *NEW record set:\n");
    Nu_DebugDumpRecordSet(pArchive, &pArchive->newRecordSet, NULL);

    if (!Nu_RecordSet_GetLoaded(&pArchive->origRecordSet) &&
        !Nu_RecordSet_GetLoaded(&pArchive->newRecordSet))
    {
        printf("*** DEBUG: original records not loaded yet? ***\n");
    }

}

#endif /*DEBUG_MSGS*/
