/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Process UCSD Pascal text and code files.  Files with this format, but
 * usually containing 6502 assembly rather than p-code, can appear on
 * Apple /// SOS disks.
 */
#include "StdAfx.h"
#include "PascalFiles.h"

/*
 * ===========================================================================
 *      Pascal Code
 * ===========================================================================
 */

/*
 * Decide whether or not we want to handle this file.
 */
void ReformatPascalCode::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;

    if (pHolder->GetFileType() == kTypePCD)
        applies = ReformatHolder::kApplicProbably;

    pHolder->SetApplic(ReformatHolder::kReformatPascalCode, applies,
        ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}

/*
 * Break a Pascal code file down into its separate components.
 *
 * The file format is described on page 266 of the "Apple Pascal Operating
 * System Reference Manual".  The first 512-byte block has a header; following
 * that are a series of up to 16 segments with Stuff in them.
 *
 * Rather than dump the header and follow it with bits and pieces, we gather
 * up all the header data and present it with the contents of the block.
 */
int ReformatPascalCode::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    const uint8_t* srcBuf = pHolder->GetSourceBuf(part);
    long srcLen = pHolder->GetSourceLen(part);
    fUseRTF = false;
    int retval = -1;
    PCDSegment segments[kNumSegments];
    uint32_t intrinsSegs;
    int i;

    if (srcLen < kSegmentHeaderLen) {
        LOGI("  PCD truncated?");
        goto bail;
    }

    RTFBegin();

    /*
     * Pull the data fields out of srcBuf.
     */
    for (i = 0; i < kNumSegments; i++) {
        uint16_t segInfo;

        segments[i].codeAddr = Get16LE(srcBuf + 0x00 + i*4);
        segments[i].codeLeng = Get16LE(srcBuf + 0x02 + i*4);
        memcpy(segments[i].name, srcBuf + 0x40 + i*kSegmentNameLen, kSegmentNameLen);
        segments[i].name[kSegmentNameLen] = '\0';
        segments[i].segmentKind = (SegmentKind) Get16LE(srcBuf + 0xc0 + i*2);
        segments[i].textAddr = Get16LE(srcBuf + 0xe0 + i*2);
        segInfo = Get16LE(srcBuf + 0x100 + i*2);
        segments[i].segInfo.segNum = segInfo & 0xff;
        segments[i].segInfo.mType = (MachineType) ((segInfo >> 8) & 0x0f);
        segments[i].segInfo.unused = (segInfo >> 12) & 0x01;
        segments[i].segInfo.version = (segInfo >> 13) & 0x07;
    }
    intrinsSegs = Get32LE(srcBuf + 0x120);

    int numSegments;
    numSegments = 0;
    for (i = 0; i < kNumSegments; i++) {
        if (segments[i].codeAddr != 0 ||
            segments[i].codeLeng != 0)
                numSegments++;
    }

    /*
     * Print the header.
     */
    BufPrintf("Pascal code file has %d segment%s\r\n", numSegments,
        numSegments == 1 ? "" : "s");
    BufPrintf("Intrinsic units required:");
    if (intrinsSegs == 0)
        BufPrintf(" none");
    else {
        for (i = 0; i < 32; i++) {
            if ((intrinsSegs & 0x01) != 0)
                BufPrintf(" %d", i);
            intrinsSegs >>= 1;
        }
    }
    BufPrintf("\r\n");

#if 0       // region is undefined; see the Pilot disk for weird examples
    /*
     * Look for a string in the header.
     */
    for (i = 0x124; i < 512; i++) {
        int strLen = srcBuf[i];
        if (strLen != 0 && (512 - (i+strLen+1)) > 0) {
            char* tmpBuf = new char[strLen+1];
            memcpy(tmpBuf, srcBuf + i +1, strLen);
            tmpBuf[strLen] = '\0';
            BufPrintf("Header string found: '%s'\r\n", tmpBuf);
            delete[] tmpBuf;

            i += strLen;
        }
    }
#endif

    //BufPrintf("Leftover stuff in segment dictionary block:\r\n");
    //BufHexDump(srcBuf + 0x124, 512 - 0x124);

    for (i = 0; i < kNumSegments; i++)
        PrintSegment(&segments[i], i, srcBuf, srcLen);

    RTFEnd();

    SetResultBuffer(pOutput);
    retval = 0;

bail:
    return retval;
}

/*
 * Print information about and the contents of one segment.
 */
void ReformatPascalCode::PrintSegment(PCDSegment* pSegment, int segNum,
    const uint8_t* srcBuf, long srcLen)
{
    const char* segKindStr;
    const char* mTypeStr;

    if (pSegment->codeAddr == 0 && pSegment->codeLeng == 0)
        return;

    switch (pSegment->segmentKind) {
    case kSegmentLinked:            segKindStr = "LINKED";      break;
    case kSegmentHostseg:           segKindStr = "HOSTSEG";     break;
    case kSegmentSegproc:           segKindStr = "SEGPROC";     break;
    case kSegmentUnitseg:           segKindStr = "UNITSEG";     break;
    case kSegmentSeprtseg:          segKindStr = "SEPRTSEG";    break;
    case kSegmentUnlinkedIntrins:   segKindStr = "UNLINKED_INTRINS";    break;
    case kSegmentLinkedIntrins:     segKindStr = "LINKED_INTRINS";      break;
    case kSegmentDataseg:           segKindStr = "DATASEG";     break;
    default:                        segKindStr = "UNKNOWN";     break;
    };
    switch (pSegment->segInfo.mType) {
    case kMTUnidentified:       mTypeStr = "unidentified";          break;
    case kMTPCodeMSB:           mTypeStr = "P-Code (MSB first)";    break;
    case kMTPCodeLSB:           mTypeStr = "P-Code (LSB first)";    break;
    case kMTPAsm3:              mTypeStr = "Machine code (type 3)"; break;
    case kMTPAsm4:              mTypeStr = "Machine code (type 4)"; break;
    case kMTPAsm5:              mTypeStr = "Machine code (type 5)"; break;
    case kMTPAsm6:              mTypeStr = "Machine code (type 6)"; break;
    case kMTPAsmApple6502:      mTypeStr = "Apple II 6502 machine code";    break;
    case kMTPAsm8:              mTypeStr = "Machine code (type 8)"; break;
    case kMTPAsm9:              mTypeStr = "Machine code (type 9)"; break;
    default:                    mTypeStr = "unknown";               break;
    };

    BufPrintf("\r\n");
    BufPrintf("Segment %d: '%s' (%s)\r\n", segNum, pSegment->name, segKindStr);
    BufPrintf("  Segment start block: %d\r\n", pSegment->codeAddr);
    BufPrintf("  Segment length: %d\r\n", pSegment->codeLeng);
    BufPrintf("  Text address: %d\r\n", pSegment->textAddr);
    BufPrintf("  Segment info: segNum=%d version=%d mType=%s\n",
        pSegment->segInfo.segNum, pSegment->segInfo.version, mTypeStr);
    BufPrintf("\r\n");

    if (pSegment->codeAddr == 0) {
        if (pSegment->segmentKind == kSegmentDataseg) {
            BufPrintf("(no data for DATASEG segments)\r\n");
        } else {
            BufPrintf("Segment start block of zero not expected.\r\n");
        }
    } else {
        if (pSegment->codeAddr * 512 + pSegment->codeLeng > srcLen) {
            BufPrintf("INVALID DATA POINTER\r\n");
        } else {
            BufHexDump(srcBuf + pSegment->codeAddr * 512, pSegment->codeLeng);
        }
    }
}


/*
 * ===========================================================================
 *      Pascal Text
 * ===========================================================================
 */

/*
 * Decide whether or not we want to handle this file.
 */
void ReformatPascalText::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;

    if (pHolder->GetFileType() == kTypePTX)
        applies = ReformatHolder::kApplicProbably;

    pHolder->SetApplic(ReformatHolder::kReformatPascalText, applies,
        ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}

/*
 * Convert Pascal text to plain text.
 *
 * The file format is documented on page 266 of the classic "Apple Pascal
 * Operating System Reference Manual".  Basically it's set in 1024-byte
 * blocks, where each line can start with an optional DLE (0x10) indicating
 * that the next value is 32+indent.  Lines end with a CR.  If the next line
 * won't fit in the block, the remainder of the block is filled with zeroes.
 */
int ReformatPascalText::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    const uint8_t* srcBuf = pHolder->GetSourceBuf(part);
    long srcLen = pHolder->GetSourceLen(part);
    long length = srcLen;
    fUseRTF = false;
    int retval = -1;

    if (srcLen < kPTXBlockSize) {
        LOGI("  PTX truncated?");
        goto bail;
    }

    RTFBegin();

    /* the first block is filled with editor storage */
    srcBuf += kPTXBlockSize;
    length -= kPTXBlockSize;

    while (length) {
        int blockLen = length > kPTXBlockSize ? kPTXBlockSize : length;

        ProcessBlock(srcBuf, blockLen);

        srcBuf += blockLen;
        length -= blockLen;
    }

    RTFEnd();

    SetResultBuffer(pOutput);
    retval = 0;

bail:
    return retval;
}

/*
 * Process up to 1024 bytes of text.
 *
 * I'm not sure if the format mandates full 1024-byte blocks -- it
 * appears to -- but I'm not going to assume it.
 */
void ReformatPascalText::ProcessBlock(const uint8_t* srcBuf, long length)
{
    ASSERT(srcBuf != NULL);
    ASSERT(length > 0 && length <= kPTXBlockSize);
    
    char lineBuf[kPTXBlockSize+1];
    char* linePtr;
    int indent;

    while (length) {
        if (*srcBuf == 0x00) {
            /* we've reached the end of the data for this block */
            LOGI(" PTX end of useful block with %d remaining", length);

            /* be paranoid */
            bool first = true;
            while (length--) {
                if (*srcBuf != 0x00) {
                    if (first) {
                        BufPrintf("EXTRA: ");
                        first = false;
                    }
                    BufPrintf("%c", *srcBuf);
                }
                srcBuf++;
            }
            if (!first) {
                RTFNewPara();
            }
            goto bail;
        }
        if (*srcBuf == kDLE) {
            srcBuf++;
            length--;
            if (!length) {
                LOGI(" PTX end of block inside DLE");
                goto bail;
            }
            indent = *srcBuf - kIndentSub;
            if (indent < 0) {
                LOGI(" PTX odd indent (raw value %d)", *srcBuf);
                indent = 0;     /* fix it */
            }
            srcBuf++;
            length--;

            /* print the #of spaces indicated */
            linePtr = lineBuf;
            while (indent--)
                *linePtr++ = ' ';
            *linePtr = '\0';
            BufPrintf("%s", lineBuf);

            if (!length)
                goto bail;
        }

        ASSERT(length > 0);

        /*
         * Accumulate the line into a buffer and then spit it out all
         * at once.
         */
        linePtr = lineBuf;
        while (*srcBuf != 0x0d && length) {
            if (*srcBuf == 0x00) {
                LOGI(" PTX a null leaked into a line??");
                /* keep going */
            }

            *linePtr++ = *srcBuf++;
            length--;
        }
        if (length && *srcBuf == 0x0d) {
            srcBuf++;
            length--;
        }
        *linePtr = '\0';

        BufPrintf("%s", lineBuf);
        RTFNewPara();
    }

bail:
    return;
}
