/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Various super-hi-res conversions, including 3200 color images.
 */
#include "StdAfx.h"
#include "SuperHiRes.h"

/*
 * ==========================================================================
 *      ReformatSHR (base class)
 * ==========================================================================
 */

/*
 * The basic Super Hi-Res "PIC" file format ($c1/0000) is just a dump of
 * memory from $2000-$9fff.
 *
 *  $2000-9cff: pixel data, 160 bytes per line
 *  $9d00-9dc7: SCB bytes (8 bits each)
 *      0-3 : color table number (0 to 15)
 *      4   : reserved, must be 0
 *      5   : fill mode (1=on, 0=off)
 *      6   : interrupts (1=generated, 0=not)
 *      7   : number of pixels (0=320, 1=640)
 *  $9e00-9fff: color tables (16 sets of 2 bytes)
 *      0-3 : blue intensity
 *      4-7 : green intensity
 *      8-11: red intensity
 *      12-15: reserved, must be 0
 *
 * Packed images ($c0/0001) are just the above packed with PackBytes.  The
 * "Apple Preferred" $c0/0002 format is funky, and has arbitrary resolution.
 *
 * 3200 color images do away with the SCB (they're always 320 mode and don't
 * support fill mode) and instead follow the pixel data with 200 sets of
 * color tables.  According to the FTN for $c1/0002 ("Brooks" format), the
 * color table is reversed, with color #15 appearing first in each table.
 */

MyDIBitmap* ReformatSHR::SHRScreenToBitmap8(const SHRScreen* pScreen)
{
    return SHRDataToBitmap8(pScreen->pixels, pScreen->scb,
                pScreen->colorTable, kPixelBytesPerLine, kNumLines,
                kOutputWidth, kOutputHeight);
}

MyDIBitmap* ReformatSHR::SHRDataToBitmap8(const uint8_t* pPixels,
    const uint8_t* pSCB, const uint8_t* pColorTable,
    unsigned int bytesPerLine, unsigned int numScanLines,
    unsigned int outputWidthPix, unsigned int outputHeightPix)
{
    MyDIBitmap* pDib = new MyDIBitmap;
    if (pDib == NULL)
        goto bail;

    // We can have an odd number of pixels, which results in 1-3 empty
    // spaces in the last byte.  We double the 4bpp pixels, so we're
    // always outputting 4 pixels per byte.
    if (outputWidthPix > bytesPerLine * 4 ||
            outputWidthPix < bytesPerLine * 4 - 3) {
        LOGW(" Invalid params: outputWidthPix=%u bytesPerLine=%u",
            outputWidthPix, bytesPerLine);
        ASSERT(false);
        goto bail;
    }

    const uint8_t* pSCBStart = pSCB;  // sanity check only

    /*
     * Set up a DIB to hold the data.  "Create" returns a pointer to the
     * pixel storage.
     */
    uint8_t* outBuf = (uint8_t*) pDib->Create(outputWidthPix, outputHeightPix,
                            8, kNumColorTables * kNumEntriesPerColorTable);
    if (outBuf == NULL) {
        delete pDib;
        pDib = NULL;
        goto bail;
    }
    int outputStride = pDib->GetPitch();

    /*
     * Convert color palette.
     */
    const uint16_t* pClrTable;
    pClrTable = (const uint16_t*) pColorTable;
    for (int table = 0; table < kNumColorTables; table++) {
        for (int entry = 0; entry < kNumEntriesPerColorTable; entry++) {
            GSColor(*pClrTable++, &fColorTables[table][entry]);
        }
    }
    pDib->SetColorTable((RGBQUAD*)fColorTables);

    if (false) {
        LOGI(" SHR color table 0");
        for (int ii = 0; ii < kNumEntriesPerColorTable; ii++) {
            LOGI("  %2d: 0x%02x %02x %02x",
                ii,
                fColorTables[0][ii].rgbRed,
                fColorTables[0][ii].rgbGreen,
                fColorTables[0][ii].rgbBlue);
        }
    }

    /*
     * Set the pixels to palette indices.
     */
    unsigned int line;
    for (line = 0; line < numScanLines; line++) {
        bool mode640, fillMode;
        int colorTableOffset;
        unsigned int byteCount;
        uint8_t pixelVal;
        uint8_t colorIndex;
        int x = 0;

        mode640 = (*pSCB & kSCBNumPixels) != 0;
        fillMode = (*pSCB & kSCBFillMode) != 0;
        colorTableOffset = (*pSCB & kSCBColorTableMask) * kNumEntriesPerColorTable;

        if (!line) {
            LOGI(" SHR line 0 mode640=%d", mode640);
        }
        if (fillMode) {
            /* I doubt anyone uses this in still images */
            LOGI(" SHR FILL MODE!!");
            DebugBreak();
        }

#define SetPix(x, y, colridx) \
        outBuf[((outputHeightPix-1) - (y)) * outputStride + (x)] = colridx

        if (mode640) {
            /* 640 mode, one byte becomes four non-doubled pixels (1:4) */
            unsigned int fullBytesPerLine = outputWidthPix / 4;
            ASSERT(fullBytesPerLine <= bytesPerLine);

            for (byteCount = 0; byteCount < fullBytesPerLine; byteCount++) {
                uint8_t pixelByte = *pPixels++;

                pixelVal = (pixelByte >> 6) & 0x03;
                colorIndex = colorTableOffset + pixelVal + 8;
                SetPix(x, line*2, colorIndex);
                SetPix(x++, line*2+1, colorIndex);

                pixelVal = (pixelByte >> 4) & 0x03;
                colorIndex = colorTableOffset + pixelVal + 12;
                SetPix(x, line*2, colorIndex);
                SetPix(x++, line*2+1, colorIndex);

                pixelVal = (pixelByte >> 2) & 0x03;
                colorIndex = colorTableOffset + pixelVal + 0;
                SetPix(x, line*2, colorIndex);
                SetPix(x++, line*2+1, colorIndex);

                pixelVal = pixelByte & 0x03;
                //rgbval = fColorLookup[colorTable][pixelVal];
                colorIndex = colorTableOffset + pixelVal + 4;
                SetPix(x, line*2, colorIndex);
                SetPix(x++, line*2+1, colorIndex);
            }
            if (byteCount != bytesPerLine) {
                // 1-3 pixels in last byte
                uint8_t pixelByte = *pPixels++;
                int rem = outputWidthPix - fullBytesPerLine * 4;
                if (rem >= 3) {
                    pixelVal = (pixelByte >> 6) & 0x03;
                    colorIndex = colorTableOffset + pixelVal + 8;
                    SetPix(x, line * 2, colorIndex);
                    SetPix(x++, line * 2 + 1, colorIndex);
                }
                if (rem >= 2) {
                    pixelVal = (pixelByte >> 4) & 0x03;
                    colorIndex = colorTableOffset + pixelVal + 12;
                    SetPix(x, line * 2, colorIndex);
                    SetPix(x++, line * 2 + 1, colorIndex);
                }
                if (rem >= 1) {
                    pixelVal = (pixelByte >> 2) & 0x03;
                    colorIndex = colorTableOffset + pixelVal + 0;
                    SetPix(x, line * 2, colorIndex);
                    SetPix(x++, line * 2 + 1, colorIndex);
                }
            }
        } else {
            /* 320 mode, one byte becomes two doubled pixels (1:4) */
            unsigned int fullBytesPerLine = outputWidthPix / 4;
            ASSERT(fullBytesPerLine <= bytesPerLine);

            for (byteCount = 0; byteCount < fullBytesPerLine; byteCount++) {
                uint8_t pixelByte = *pPixels++;

                pixelVal = (pixelByte >> 4) & 0x0f;
                colorIndex = colorTableOffset + pixelVal;
                SetPix(x, line*2, colorIndex);
                SetPix(x++, line*2+1, colorIndex);
                SetPix(x, line*2, colorIndex);
                SetPix(x++, line*2+1, colorIndex);

                pixelVal = pixelByte & 0x0f;
                colorIndex = colorTableOffset + pixelVal;
                SetPix(x, line*2, colorIndex);
                SetPix(x++, line*2+1, colorIndex);
                SetPix(x, line*2, colorIndex);
                SetPix(x++, line*2+1, colorIndex);
            }
            if (byteCount != bytesPerLine) {
                // 1 pixel in last byte
                uint8_t pixelByte = *pPixels++;
                pixelVal = (pixelByte >> 4) & 0x0f;
                colorIndex = colorTableOffset + pixelVal;
                SetPix(x, line * 2, colorIndex);
                SetPix(x++, line * 2 + 1, colorIndex);
                SetPix(x, line * 2, colorIndex);
                SetPix(x++, line * 2 + 1, colorIndex);
            }
        }
#undef SetPix

        ASSERT(x == outputWidthPix);

        pSCB++;
    }

    ASSERT(line == outputHeightPix/2);
    ASSERT(pSCB == pSCBStart + numScanLines);

bail:
    return pDib;
}


/*
 * ==========================================================================
 *      ReformatUnpackedSHR
 * ==========================================================================
 */

/*
 * Decide whether or not we want to handle this file.
 *
 * Occasionally somebody slaps the wrong aux type on, so if we're in
 * "relaxed" mode we accept just about anything that's the right size.
 */
void ReformatUnpackedSHR::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
    long fileType = pHolder->GetFileType();
    long auxType = pHolder->GetAuxType();
    long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
    const char* nameExt = pHolder->GetNameExt();
    bool relaxed;

    relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;

    /* uncompressed $c1/0000, but many variations on aux type */
    if ((fileType == kTypePIC && auxType == 0x0000 && fileLen == 32768) ||
        (relaxed &&
            (
            (fileType == kTypePIC && fileLen == 32768) ||
            (fileType == kTypeBIN && fileLen == 32768 &&
                (stricmp(nameExt, ".PIC") == 0 || stricmp(nameExt, ".SHR") == 0))
            )
        ))
    {
        applies = ReformatHolder::kApplicProbably;
    }

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

/*
 * Convert an unpacked Super Hi-Res image.
 */
int ReformatUnpackedSHR::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    MyDIBitmap* pDib;
    int retval = -1;

    if (pHolder->GetSourceLen(part) != kTotalSize) {
        LOGI(" SHR file is not %d bytes long!", kTotalSize);
        goto bail;
    }

    ASSERT(sizeof(SHRScreen) == kTotalSize);

    memcpy(&fScreen, pHolder->GetSourceBuf(part), sizeof(fScreen));

    pDib = SHRScreenToBitmap8(&fScreen);
    if (pDib == NULL)
        goto bail;

    SetResultBuffer(pOutput, pDib);
    retval = 0;

bail:
    return retval;
}


/*
 * ==========================================================================
 *      ReformatAppSHR
 * ==========================================================================
 */

/*
 * Decide whether or not we want to handle this file.
 *
 * This file type seems exclusive to the IIgs version of "John Elway
 * Quarterback".
 */
void ReformatJEQSHR::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
    long fileType = pHolder->GetFileType();
    long auxType = pHolder->GetAuxType();
    long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
    const char* nameExt = pHolder->GetNameExt();
    bool relaxed;

    relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;

    /* uncompressed $c1/0000, but many variations on aux type */
    if (fileType == kTypeBIN && auxType == 0x0000 &&
        fileLen == kExpectedLen && stricmp(nameExt, ".APP") == 0)
    {
        applies = ReformatHolder::kApplicProbably;
    }

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

/*
 * Convert one of these odd-format images.  It appears to be a dump of the
 * SHR screen minus the "reserved" section between the SCB and color table,
 * and only one color table is stored.  Total savings of 480 bytes -- less
 * than a ProDOS block.  Sorta dumb.
 */
int ReformatJEQSHR::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    MyDIBitmap* pDib;
    const uint8_t* srcBuf = pHolder->GetSourceBuf(part);
    long srcLen = pHolder->GetSourceLen(part);
    int retval = -1;

    if (pHolder->GetSourceLen(part) != kExpectedLen) {
        LOGI(" SHR file is not %d bytes long!", kTotalSize);
        goto bail;
    }

    ASSERT(sizeof(SHRScreen) == kTotalSize);

    memcpy(&fScreen.pixels, srcBuf, 32000);
    memcpy(&fScreen.scb, srcBuf + 0x7d00, 256);
    memcpy(&fScreen.colorTable, srcBuf + 0x7e00, 32);

    pDib = SHRScreenToBitmap8(&fScreen);
    if (pDib == NULL)
        goto bail;

    SetResultBuffer(pOutput, pDib);
    retval = 0;

bail:
    return retval;
}


/*
 * ==========================================================================
 *      ReformatPaintworksSHR
 * ==========================================================================
 */

/*
 * Decide whether or not we want to handle this file.
 */
void ReformatPaintworksSHR::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
    long fileType = pHolder->GetFileType();
    long auxType = pHolder->GetAuxType();
    //long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
    //const char* nameExt = pHolder->GetNameExt();
    //bool relaxed;

    //relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;

    /* PNT $c0/0000 */
    if (fileType == kTypePNT && auxType == 0x0000)
        applies = ReformatHolder::kApplicProbably;

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

/*
 * Convert a PaintWorks format Super Hi-Res image.
 *
 * The format is loosely documented in the file type note for $C0/0000:
 *  +$000 to +$01F: Bytes
 *    Super Hi-Res Palette
 *  +$020 to +$021: Word
 *    Background color
 *  +$022 to +$221: Bytes
 *    Patterns. 16 QuickDraw II patterns, each 32 bytes in length.
 *  +$222 to EOF: Bytes
 *    Packed graphics data. Note that the unpacked data could be longer
 *    than one Super Hi-Res screen (Paintworks allows full-page sized
 *    documents). 
 *
 * Sometimes it runs a few bytes over.  If it runs significantly under, it's
 * probably a generic packed image (PNT/0001) with the wrong auxtype.
 */
int ReformatPaintworksSHR::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    MyDIBitmap* pDib;
    const int kPWOutputLines = 396;
    const int kPWOutputSize = kPWOutputLines * kPixelBytesPerLine;
    const int kPWDataOffset = 0x222;
    uint8_t scb[kPWOutputLines];
    uint8_t colorTable[kNumEntriesPerColorTable * kColorTableEntrySize];
    uint8_t* unpackBuf = NULL;
    int retval = -1;
    int i, result;

    if (pHolder->GetSourceLen(part) < kMinSize) {
        LOGW(" SHR file too short (%ld)", pHolder->GetSourceLen(part));
        goto bail;
    }

    /*
     * Copy the color table out.
     */
    memcpy(colorTable, pHolder->GetSourceBuf(part), sizeof(colorTable));

    /*
     * Unpack the packed data.
     *
     * For some reason it's fairly common to have exactly 9 bytes left over
     * in the input.  It appears that these files have 400 lines of graphics,
     * and the first 8 bytes are PackBytes for the 4 lines.  The very last
     * byte is a zero, for no apparent reason.  Best guess is something other
     * than PaintWorks wrote these, because SuperConvert expects them to
     * have 396 lines and only recognizes that many.
     */
    unpackBuf = new uint8_t[kPWOutputSize];
    if (unpackBuf == NULL)
        goto bail;
    memset(unpackBuf, 0, sizeof(unpackBuf));        // in case we fall short

    result = UnpackBytes(unpackBuf,
                pHolder->GetSourceBuf(part) + kPWDataOffset,
                kPWOutputSize,
                pHolder->GetSourceLen(part) - kPWDataOffset);
    if (result != kPWOutputSize) {
        LOGI("WARNING: UnpackBytes wasn't happy");

#if 0
        /* thd282.shk (rev76.2) has a large collection of these */
        if (UnpackBytes((uint8_t*) &fScreen,
            pHolder->GetSourceBuf(part),
            kTotalSize,
            pHolder->GetSourceLen(part)) == 0)
        {
            pDib = SHRScreenToBitmap8(&fScreen);
            if (pDib == NULL)
                goto bail;
            goto gotit;
        }
#endif
    }

    for (i = 0; i < kPWOutputLines; i++)
        scb[i] = 0;     // 320 mode
    pDib = SHRDataToBitmap8(unpackBuf, scb, colorTable, kPixelBytesPerLine,
            kPWOutputLines, kOutputWidth, kPWOutputLines * 2);
    if (pDib == NULL)
        goto bail;

//gotit:
    SetResultBuffer(pOutput, pDib);
    retval = 0;

bail:
    delete[] unpackBuf;
    return retval;
}


/*
 * ==========================================================================
 *      ReformatPackedSHR
 * ==========================================================================
 */

/*
 * Decide whether or not we want to handle this file.
 */
void ReformatPackedSHR::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
    long fileType = pHolder->GetFileType();
    long auxType = pHolder->GetAuxType();
    //long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
    //const char* nameExt = pHolder->GetNameExt();
    //bool relaxed;

    //relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;

    /* compressed $c0/0001 */
    if (fileType == kTypePNT && auxType == 0x0001)
        applies = ReformatHolder::kApplicProbably;

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

/*
 * Convert a packed Super Hi-Res image (PNT/$0001).
 */
int ReformatPackedSHR::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    MyDIBitmap* pDib;
    int retval = -1;

    if (pHolder->GetSourceLen(part) < 4) {
        LOGW(" SHR file too short (%ld)", pHolder->GetSourceLen(part));
        goto bail;
    }

    ASSERT(sizeof(SHRScreen) == kTotalSize);

    if (UnpackBytes((uint8_t*) &fScreen,
        pHolder->GetSourceBuf(part), kTotalSize,
        pHolder->GetSourceLen(part)) != kTotalSize)
    {
        LOGW(" SHR UnpackBytes failed");
        goto bail;
    }

    pDib = SHRScreenToBitmap8(&fScreen);
    if (pDib == NULL)
        goto bail;

    SetResultBuffer(pOutput, pDib);
    retval = 0;

bail:
    return retval;
}


/*
 * ==========================================================================
 *      ReformatAPFSHR
 * ==========================================================================
 */

/*
 * Decide whether or not we want to handle this file.
 */
void ReformatAPFSHR::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
    long fileType = pHolder->GetFileType();
    long auxType = pHolder->GetAuxType();
    //long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
    //const char* nameExt = pHolder->GetNameExt();
    //bool relaxed;

    //relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;

    /* compressed $c0/0002 */
    if (fileType == kTypePNT && auxType == 0x0002)
        applies = ReformatHolder::kApplicProbably;

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

/*
 * Convert a Super Hi-Res image in APF format (PNT/$0002).
 *
 * Blocks are a 32-bit length followed by a pascal string describing the
 * contents.  Common blocks are "MAIN", "PATS", and "SCIB", but could be
 * anything (e.g. "Platinum Paint").
 *
 * All we're really interested in is the "MAIN" block, though we need
 * to handle MULTIPAL as well for 3200-color images.
 */
int ReformatAPFSHR::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    MyDIBitmap* pDib;
    const uint8_t* srcPtr = pHolder->GetSourceBuf(part);
    long srcLen = pHolder->GetSourceLen(part);
    uint8_t multiPal[kNumLines *
                        kNumEntriesPerColorTable * kColorTableEntrySize];
    bool is3200 = false;
    bool haveGraphics = false;
    int retval = -1;

    if (srcLen < 4) {
        LOGW(" SHR file too short (%ld)", srcLen);
        goto bail;
    }

    ASSERT(sizeof(SHRScreen) == kTotalSize);

    while (srcLen > 0) {
        CString blockName;
        const uint8_t* nextBlock;
        long blockLen, dataLen, nextLen;
        int nameLen;

        blockLen = Read32(&srcPtr, &srcLen);
        if (blockLen > srcLen + 4) {
            LOGI(" APFSHR WARNING: found blockLen=%ld, remaining len=%ld",
                blockLen, srcLen);
            break;
            //goto bail;
        }
        nextBlock = srcPtr + (blockLen-4);
        nextLen = srcLen - (blockLen-4);

        nameLen = ::GetPascalString(srcPtr, srcLen, &blockName);
        if (nameLen < 0) {
            LOGI(" APFSHR failed getting pascal name, bailing");
            goto bail;
        }

        srcPtr += nameLen+1;
        srcLen -= nameLen+1;

        dataLen = blockLen - (nameLen+1 + 4);

        LOGI(" APFSHR block='%ls' blockLen=%ld (dataLen=%ld) start=0x%p",
            (LPCWSTR) blockName, blockLen, dataLen, srcPtr);

        if (blockName == "MAIN") {
            if (UnpackMain(srcPtr, dataLen) != 0)
                goto bail;
            haveGraphics = true;
        } else if (blockName == "MULTIPAL") {
            if (UnpackMultipal(multiPal, srcPtr, dataLen) == 0)
                is3200 = true;
        } else if (blockName == "NOTE") {
            UnpackNote(srcPtr, dataLen);
        } else {
            LOGI(" APFSHR  (ignoring segment '%ls')", (LPCWSTR) blockName);
        }

        srcPtr = nextBlock;
        srcLen = nextLen;
    }

    if (!haveGraphics)
        goto bail;

    // TODO: we don't currently handle MULTIPAL with anything other than
    //  a 320x200 image.  Fortunately this is rare.
    if (is3200 && !fNonStandard) {
        Reformat3200SHR ref3200(&fScreen, multiPal);

        pDib = ref3200.SHR3200ToBitmap24();
    } else if (fNonStandard) {
        pDib = SHRDataToBitmap8(fPixelStore, fSCBStore,
                fScreen.colorTable, fPixelBytesPerLine, fNumScanLines,
                fOutputWidth, fOutputHeight);

    } else {
        pDib = SHRScreenToBitmap8(&fScreen);
    }
    if (pDib == NULL)
        goto bail;

    SetResultBuffer(pOutput, pDib);
    retval = 0;

bail:
    return retval;
}

/*
 * Unpack the "MAIN" segment of an APF paint file into fScreen.
 *
 *  +$00    MasterMode: 16-bit ModeWord value for MasterSCB.  A ModeWord
 *            with a high byte of zero has the SCB in the low byte.
 *  +$02    PixelsPerScanLine: 16-bit value, usually 320 or 640 but may
 *            be something different.
 *  +$04    NumColorTables: 16-bit value, should be between 0 and 15
 *  +$06    ColorTableArray: [0..NumColorTables-1] of ColorTable, where
 *            a ColorTable is 16 sets of 16-bit $0RGB values.
 *  +$xx    NumScanLines: 16-bit count of scan lines, often 200.
 *  +$xx+2  ScanLineDirectory: [0..NumScanLines-1] of DirEntry.  Each
 *            DirEntry has two parts, a 16-bit count of packed-data-length
 *            for each line, followed by a 16-bit ModeWord value.
 *  +$yy    PackedScanLines: [0..NumScanLines-1] of packed data.  Each
 *            scan line is individually packed with PackBytes.
 *
 * Returns 0 on success, -1 on failure.
 *
 * Question: if the image is 640x200, is it in 640 mode?  Can we depend on
 * the MasterMode word to tell us if it's a 320-mode 4-bit-per-pixel image
 * that happens to be 640 pixels wide?  Did all //gs applications set this
 * correctly?  Can the format be different on every line?
 */
int ReformatAPFSHR::UnpackMain(const uint8_t* srcPtr, long srcLen)
{
    const int kColorTableSize = kNumEntriesPerColorTable * kColorTableEntrySize;
    uint16_t masterMode;
    unsigned int numColorTables;
    uint16_t* packedDataLen = NULL;
    int retval = -1;

    if (srcLen < 64) {
        /* can't possibly be this small */
        LOGI(" APFSHR unlikely srcLen %d", srcLen);
        goto bail;
    }

    masterMode = Read16(&srcPtr, &srcLen);
    fPixelsPerScanLine = Read16(&srcPtr, &srcLen);
    numColorTables = Read16(&srcPtr, &srcLen);
    if (fPixelsPerScanLine == 0 || fPixelsPerScanLine > kMaxPixelsPerScan) {
        LOGI(" APFSHR unsupported pixelsPerScanLine %d",
            fPixelsPerScanLine);
        goto bail;
    }

    LOGD(" APFSHR  masterMode=0x%04x, ppsl=%d, nct=%d",
        masterMode, fPixelsPerScanLine, numColorTables);
    if (numColorTables == 0 || numColorTables > kNumColorTables) {
        // Zero is valid per the spec, but we don't know how to render that.
        // Rather than showing an all-black bitmap, just fail.
        LOGI(" APFSHR unsupported numColorTables %d", numColorTables);
        goto bail;
    }

    /*
     * Load the color tables.
     */
    memset(fScreen.colorTable, 0, sizeof(fScreen.colorTable));
    for (unsigned int i = 0; i < numColorTables; i++) {
        if (srcLen < kColorTableSize) {
            LOGI(" APFSHR underrun while copying color tables");
            goto bail;
        }

        memcpy(&fScreen.colorTable[i * kColorTableSize], srcPtr,
            kColorTableSize);
        srcPtr += kColorTableSize;
        srcLen -= kColorTableSize;
    }

    /*
     * Get the scanline count.
     *
     * fOutputHeight is twice the #of scan lines because we use pixel
     * doubling so we can handle both 320 mode and 640 mode.
     */
    fNumScanLines = Read16(&srcPtr, &srcLen);
    if (fNumScanLines == 0 || fNumScanLines > kMaxScanLines) {
        LOGI(" APFSHR unsupported numScanLines %d", fNumScanLines);
        goto bail;
    }
    if ((fPixelsPerScanLine == 320 || fPixelsPerScanLine == 640) &&
        fNumScanLines == kNumLines)
    {
        /* standard-sized image, use fScreen */
        ASSERT(!fNonStandard);
        LOGD("  Assuming this is a standard, full-width SHR image");
        fPixelBytesPerLine = kPixelBytesPerLine;        // 160
        fPixelStore = fScreen.pixels;
        fSCBStore = fScreen.scb;
        // These don't matter if "fNonStandard" is false, but set them anyway
        // so the debug output looks okay.
        fOutputWidth = 640;
        fOutputHeight = kNumLines * 2;
    } else {
        /* non-standard image, allocate storage */
        fNonStandard = true;
        LOGI("  NOTE: non-standard image size %dx%d, 2-bit-mode=%d",
            fPixelsPerScanLine, fNumScanLines,
            (masterMode & kSCBNumPixels) != 0);

        fOutputHeight = fNumScanLines * 2;
        if ((masterMode & kSCBNumPixels) == 0) {
            fOutputWidth = fPixelsPerScanLine * 2;
            fPixelBytesPerLine = (fPixelsPerScanLine + 1) / 2;  // "320 mode"
        } else {
            fOutputWidth = fPixelsPerScanLine;
            fPixelBytesPerLine = (fPixelsPerScanLine + 3) / 4;  // "640 mode"
        }
        fPixelStore = new uint8_t[fPixelBytesPerLine * fNumScanLines];
        if (fPixelStore == NULL) {
            LOGI(" APFSHR ERROR: alloc of %d bytes fPixelStore failed",
                fPixelBytesPerLine * fNumScanLines);
            goto bail;
        }
        fSCBStore = new uint8_t[fNumScanLines];
        if (fSCBStore == NULL) {
            LOGI(" APFSHR ERROR: alloc of %d bytes fSCBStore failed",
                fNumScanLines);
            goto bail;
        }
    }
    LOGD(" APFSHR  numScanLines=%d, outputWidth=%d, pixelBytesPerLine=%d",
        fNumScanLines, fOutputWidth, fPixelBytesPerLine);

    /*
     * Get the per-scanline data.
     */
    packedDataLen = new uint16_t[fNumScanLines];
    if (packedDataLen == NULL)
        goto bail;
    for (unsigned int i = 0; i < fNumScanLines; i++) {
        packedDataLen[i] = Read16(&srcPtr, &srcLen);
        if (packedDataLen[i] > fPixelsPerScanLine) {
            /* each pixel is 2 or 4 bits, so this is a 2-4x expansion */
            LOGI(" APFSHR got funky packed len %d for line %d",
                packedDataLen[i], i);
            goto bail;
        }

        uint16_t mode = Read16(&srcPtr, &srcLen);
        if (mode >> 8 == 0)
            fSCBStore[i] = (uint8_t) mode;
        else {
            LOGI(" APFSHR odd mode 0x%04x on line %d", mode, i);
            fSCBStore[i] = 0;   // ?
        }
    }

    /*
     * Unpack the scan lines into the pixel storage.  Some APF files
     * use PackBytes in a not-quite-safe way, e.g. a 28x30 image with
     * 14 bytes per line uses the 32-bit pattern repeat mode (0x3fff),
     * yielding 16 bytes.  We need to unpack to a temp buffer.
     *
     * I've found a few from 816/Paint that appear to have the wrong width
     * value; they claim 320 but look like they're actually 640 (the image
     * seems to be cut in half, and we unpack 2x as much data as we expect).
     */
    for (unsigned int i = 0; i < fNumScanLines; i++) {
        // PackBytes can generate 256 bytes from a single pair of bytes.
        uint8_t tmpBuf[ReformatSHR::kMaxPixelsPerScan];

        if (srcLen <= 0) {
            LOGI(" APFSHR ran out of data while unpacking pixels");
            goto bail;
        }
        int actual = UnpackBytes(tmpBuf, srcPtr, sizeof(tmpBuf), packedDataLen[i]);
        if (actual < (int) fPixelBytesPerLine) {  // includes actual < 0
            LOGI(" APFSHR UnpackBytes failed on line %d (pbpl=%d actual=%d)",
                i, fPixelBytesPerLine, actual);
            goto bail;
        }
        //LOGD("+++ unpackbytes %d pbpl=%d pdl=%d --> %d",
        //    i, fPixelBytesPerLine, packedDataLen[i], actual);
        memcpy(&fPixelStore[i * fPixelBytesPerLine], tmpBuf, fPixelBytesPerLine);

        srcPtr += packedDataLen[i];
        srcLen -= packedDataLen[i];
    }

    retval = 0;

bail:
    delete[] packedDataLen;
    return retval;
}

/*
 * Unpack the "multipal" data.
 *
 * Unfortunately the guy who wrote "SuperView" decided to un-swap the
 * Brooks format by stuffing them into the file the wrong way.  We could
 * fix it here, but we can't reliably detect the files.
 */
int ReformatAPFSHR::UnpackMultipal(uint8_t* dstPtr,
    const uint8_t* srcPtr, long srcLen)
{
    const int kMultipalSize = kNumLines *
                        kNumEntriesPerColorTable * kColorTableEntrySize;
    int numColorTables;

    /* check the size; use (size+2) to factor in 16-bit count */
    if (srcLen < kMultipalSize+2) {
        LOGI("  APFSHR got too-small multipal size %ld", srcLen);
        return -1;
    } else if (srcLen > kMultipalSize+2) {
        LOGI("  APFSHR WARNING: oversized multipal (%ld, expected %ld)",
            srcLen, kMultipalSize);
        /* keep going */
    }

    /* get the #of color tables; should always be 200 */
    numColorTables = Read16(&srcPtr, &srcLen);
    if (numColorTables != kNumLines) {
        /* expecting one palette per line */
        LOGI("  APFSHR ignoring multipal with %d color tables",
            numColorTables);
        return -1;
    }

    if (1) {
        memcpy(dstPtr, srcPtr, kMultipalSize);
    } else {
#if 0
        /* swap entries */
        const uint16_t* pSrcTable;
        uint16_t* pDstTable = (uint16_t*) dst;
        int table, entry;
        for (table = 0; table < kNumLines; table++) {
            pSrcTable = (const uint16_t*)src +
                        (kNumLines - table) * kNumEntriesPerColorTable;
            for (entry = 0; entry < kNumEntriesPerColorTable; entry++) {
                pDstTable[entry] = *pSrcTable++;
            }
            pDstTable += kNumEntriesPerColorTable;
        }
#endif
    }

    return 0;
}

/*
 * Unpack a "NOTE" chunk.  This seems to be a 16-bit count followed by
 * ASCII data.
 */
void ReformatAPFSHR::UnpackNote(const uint8_t* srcPtr, long srcLen)
{
    int numChars;

    numChars = Read16(&srcPtr, &srcLen);

    if (numChars != srcLen) {
        LOGI("  APFSHR note chunk has numChars=%d but dataLen=%ld, bailing",
            numChars, srcLen);
        return;
    }

    CString str;
    while (srcLen--) {
        str += *srcPtr++;
    }

    LOGI(" APFSHR   note: '%ls'", (LPCWSTR) str);
}


/*
 * ==========================================================================
 *      Reformat3200SHR
 * ==========================================================================
 */

/*
 * Decide whether or not we want to handle this file.
 */
void Reformat3200SHR::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
    long fileType = pHolder->GetFileType();
    long auxType = pHolder->GetAuxType();
    long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
    const char* nameExt = pHolder->GetNameExt();
    bool relaxed;

    relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;

    /* should be $c1/0002, but there are many variations */
    if ((fileType == kTypePIC && auxType == 0x0002 && fileLen == 38400) ||
        (fileType == kTypeBIN && fileLen == 38400 &&
                stricmp(nameExt, ".3200") == 0) ||
        (relaxed &&
            (
            (fileType == kTypePIC && fileLen == 38400)
            )
        ))
    {
        applies = ReformatHolder::kApplicProbably;
    }

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

/*
 * Convert a 3200-color Super Hi-Res Image.
 */
int Reformat3200SHR::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    MyDIBitmap* pDib;
    int retval = -1;

    if (pHolder->GetSourceLen(part) != kExtTotalSize) {
        LOGI(" SHR file is not %d bytes long!", kExtTotalSize);
        return retval;
    }

    ASSERT(sizeof(fScreen.pixels) + sizeof(fExtColorTable) == kExtTotalSize);

    ASSERT(sizeof(fScreen.pixels) == 32000);
    ASSERT(sizeof(fExtColorTable) == 32 * 200);

    memcpy(fScreen.pixels, pHolder->GetSourceBuf(part), sizeof(fScreen.pixels));
    //memcpy(fExtColorTable, (*ppBuf) + sizeof(fScreen.pixels),
    //  sizeof(fExtColorTable));

    /* "Brooks" format color tables are stored in reverse order */
    const uint16_t* pSrcTable = (const uint16_t*)
                        (pHolder->GetSourceBuf(part) + sizeof(fScreen.pixels));
    uint16_t* pDstTable = (uint16_t*) fExtColorTable;
    for (int table = 0; table < kExtNumColorTables; table++) {
        int entry;
        for (entry = 0; entry < kNumEntriesPerColorTable; entry++) {
            pDstTable[(kNumEntriesPerColorTable-1) - entry] = *pSrcTable++;
        }
        pDstTable += kNumEntriesPerColorTable;
    }

    pDib = SHR3200ToBitmap24();
    if (pDib == NULL)
        goto bail;

    SetResultBuffer(pOutput, pDib);
    retval = 0;

bail:
    return retval;
}

/*
 * Convert a SHRScreen struct to a 24-bit DIB, using a different
 * color palette for each line.
 *
 * This is shared among the 3200-color SHR formats.
 */
MyDIBitmap* Reformat3200SHR::SHR3200ToBitmap24(void)
{
    MyDIBitmap* pDib = new MyDIBitmap;
    RGBTRIPLE colorLookup[kExtNumColorTables][kNumEntriesPerColorTable];
    RGBTRIPLE* rgbBuf;
    const uint8_t* pPixels = fScreen.pixels;

    if (pDib == NULL)
        goto bail;

    /*
     * Set up a DIB to hold the data.
     */
    rgbBuf = (RGBTRIPLE*) pDib->Create(kOutputWidth, kOutputHeight, 24, 0);
    if (rgbBuf == NULL) {
        delete pDib;
        pDib = NULL;
        goto bail;
    }

    /*
     * Convert color palette data to RGBTRIPLE form.
     */
    const uint16_t* pClrTable;
    pClrTable = (const uint16_t*) fExtColorTable;
    for (int table = 0; table < kExtNumColorTables; table++) {
        for (int entry = 0; entry < kNumEntriesPerColorTable; entry++) {
            GSColor(*pClrTable++, &colorLookup[table][entry]);
            //if (!table) {
            //  LOGI(" table %2d entry %2d value=0x%04x",
            //      table, entry, *pClrTable);
            //}
        }
    }

    /*
     * Set the pixels in our private RGB buffer.
     */
    int line;
    for (line = 0; line < kNumLines; line++) {
        int byteCount, pixelByte;
        uint8_t pixelVal;
        RGBTRIPLE rgbval;
        int x = 0;

#define SetPix(x, y, rgbval) \
        rgbBuf[((kOutputHeight-1) - (y)) * kOutputWidth + (x)] = rgbval

        for (byteCount = 0; byteCount < kPixelBytesPerLine; byteCount++) {
            pixelByte = *pPixels++;

            pixelVal = (pixelByte >> 4) & 0x0f;
            rgbval = colorLookup[line][pixelVal];
            SetPix(x, line*2, rgbval);
            SetPix(x++, line*2+1, rgbval);
            SetPix(x, line*2, rgbval);
            SetPix(x++, line*2+1, rgbval);

            pixelVal = pixelByte & 0x0f;
            rgbval = colorLookup[line][pixelVal];
            SetPix(x, line*2, rgbval);
            SetPix(x++, line*2+1, rgbval);
            SetPix(x, line*2, rgbval);
            SetPix(x++, line*2+1, rgbval);
        }
#undef SetPix

        ASSERT(x == kOutputWidth);
    }
    ASSERT(line == kOutputHeight/2);

bail:
    return pDib;
}


/*
 * ==========================================================================
 *      Reformat3201SHR
 * ==========================================================================
 */

/*
 * Decide whether or not we want to handle this file.
 *
 * This *might* also be $c0/0004, but there's no file type note
 * for that.
 */
void Reformat3201SHR::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
    long fileType = pHolder->GetFileType();
    long auxType = pHolder->GetAuxType();
    //long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
    const char* nameExt = pHolder->GetNameExt();
    //bool relaxed;

    //relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;

    /* only identified by name */
    if (stricmp(nameExt, ".3201") == 0)
        applies = ReformatHolder::kApplicProbably;

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

/*
 * Convert a 3201-format packed 3200-color Super Hi-Res Image.
 */
int Reformat3201SHR::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    MyDIBitmap* pDib;
    const uint8_t* srcBuf = pHolder->GetSourceBuf(part);
    long srcLen = pHolder->GetSourceLen(part);
    long length = srcLen;
    uint8_t* tmpBuf = NULL;
    int retval = -1;

    if (srcLen < 16 || srcLen > kExtTotalSize) {
        LOGI(" SHR3201 file funky length (%d)", srcLen);
        return retval;
    }
    const long* pMagic = (const long*) pHolder->GetSourceBuf(part);
    if (*pMagic != 0x00d0d0c1) {    // "APP\0"
        LOGI(" SHR3201 didn't find magic 'APP'");
        return retval;
    }
    srcBuf += 4;
    length -= 4;

    ASSERT(sizeof(fScreen.pixels) == 32000);
    ASSERT(sizeof(fExtColorTable) == 32 * 200);
    ASSERT(sizeof(fScreen.pixels) + sizeof(fExtColorTable) == kExtTotalSize);

    /* "Brooks" format color tables are stored in reverse order */
    const uint16_t* pSrcTable;
    uint16_t* pDstTable;
    pSrcTable = (const uint16_t*) srcBuf;
    pDstTable = (uint16_t*) fExtColorTable;
    int table;
    for (table = 0; table < kExtNumColorTables; table++) {
        int entry;
        for (entry = 0; entry < kNumEntriesPerColorTable; entry++) {
            pDstTable[(kNumEntriesPerColorTable-1) - entry] = *pSrcTable++;
        }
        pDstTable += kNumEntriesPerColorTable;
    }

    srcBuf = (const uint8_t*) pSrcTable;
    length -= srcBuf - (pHolder->GetSourceBuf(part) +4);

    /* now unpack the PackBytes-format pixels */
    if (UnpackBytes(fScreen.pixels, srcBuf, sizeof(fScreen.pixels), length)
            != sizeof(fScreen.pixels)) {
        goto bail;
    }

    pDib = SHR3200ToBitmap24();
    if (pDib == NULL)
        goto bail;

    SetResultBuffer(pOutput, pDib);
    retval = 0;

bail:
    delete[] tmpBuf;
    return retval;
}
