/*
 * CiderPress
 * Copyright (C) 2007, 2008 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Common code for reformatters.
 */
#include "StdAfx.h"
#include "Reformat.h"

#include "ReformatBase.h"
#include "AppleWorks.h"
#include "Asm.h"
#include "AWGS.h"
#include "Basic.h"
#include "CPMFiles.h"
#include "Directory.h"
#include "Disasm.h"
#include "DoubleHiRes.h"
#include "HiRes.h"
#include "MacPaint.h"
#include "PascalFiles.h"
#include "PrintShop.h"
#include "ResourceFork.h"
#include "Simple.h"
#include "SuperHiRes.h"
#include "Teach.h"
#include "Text8.h"

/*
 * Create an instance of the class identified by "id".
 */
/*static*/ Reformat* ReformatHolder::GetReformatInstance(ReformatID id)
{
    Reformat* pReformat = NULL;

    switch (id) {
    case kReformatTextEOL_HA:       pReformat = new ReformatEOL_HA;             break;
    case kReformatRaw:              pReformat = new ReformatRaw;                break;
    case kReformatHexDump:          pReformat = new ReformatHexDump;            break;
    case kReformatResourceFork:     pReformat = new ReformatResourceFork;       break;

    case kReformatProDOSDirectory:  pReformat = new ReformatDirectory;          break;
    case kReformatPascalText:       pReformat = new ReformatPascalText;         break;
    case kReformatPascalCode:       pReformat = new ReformatPascalCode;         break;
    case kReformatCPMText:          pReformat = new ReformatCPMText;            break;
    case kReformatApplesoft:        pReformat = new ReformatApplesoft;          break;
    case kReformatApplesoft_Hilite: pReformat = new ReformatApplesoft;          break;
    case kReformatInteger:          pReformat = new ReformatInteger;            break;
    case kReformatInteger_Hilite:   pReformat = new ReformatInteger;            break;
    case kReformatBusiness:         pReformat = new ReformatBusiness;           break;
    case kReformatBusiness_Hilite:  pReformat = new ReformatBusiness;           break;
    case kReformatSCAssem:          pReformat = new ReformatSCAssem;            break;
    case kReformatMerlin:           pReformat = new ReformatMerlin;             break;
    case kReformatLISA2:            pReformat = new ReformatLISA2;              break;
    case kReformatLISA3:            pReformat = new ReformatLISA3;              break;
    case kReformatLISA4:            pReformat = new ReformatLISA4;              break;
    case kReformatMonitor8:         pReformat = new ReformatDisasm8;            break;
    case kReformatDisasmMerlin8:    pReformat = new ReformatDisasm8;            break;
    case kReformatMonitor16Long:    pReformat = new ReformatDisasm16;           break;
    case kReformatMonitor16Short:   pReformat = new ReformatDisasm16;           break;
    case kReformatDisasmOrcam16:    pReformat = new ReformatDisasm16;           break;
    case kReformatAWGS_WP:          pReformat = new ReformatAWGS_WP;            break;
    case kReformatTeach:            pReformat = new ReformatTeach;              break;
    case kReformatGWP:              pReformat = new ReformatGWP;                break;
    case kReformatMagicWindow:      pReformat = new ReformatMagicWindow;        break;
    case kReformatAWP:              pReformat = new ReformatAWP;                break;
    case kReformatADB:              pReformat = new ReformatADB;                break;
    case kReformatASP:              pReformat = new ReformatASP;                break;
    case kReformatHiRes:            pReformat = new ReformatHiRes;              break;
    case kReformatHiRes_BW:         pReformat = new ReformatHiRes;              break;
    case kReformatDHR_Latched:      pReformat = new ReformatDHR;                break;
    case kReformatDHR_BW:           pReformat = new ReformatDHR;                break;
    case kReformatDHR_Plain140:     pReformat = new ReformatDHR;                break;
    case kReformatDHR_Window:       pReformat = new ReformatDHR;                break;
    case kReformatSHR_PIC:          pReformat = new ReformatUnpackedSHR;        break;
    case kReformatSHR_JEQ:          pReformat = new ReformatJEQSHR;             break;
    case kReformatSHR_Paintworks:   pReformat = new ReformatPaintworksSHR;      break;
    case kReformatSHR_Packed:       pReformat = new ReformatPackedSHR;          break;
    case kReformatSHR_APF:          pReformat = new ReformatAPFSHR;             break;
    case kReformatSHR_3200:         pReformat = new Reformat3200SHR;            break;
    case kReformatSHR_3201:         pReformat = new Reformat3201SHR;            break;
    case kReformatSHR_DG256:        pReformat = new ReformatDG256SHR;           break;
    case kReformatSHR_DG3200:       pReformat = new ReformatDG3200SHR;          break;
    case kReformatPrintShop:        pReformat = new ReformatPrintShop;          break;
    case kReformatMacPaint:         pReformat = new ReformatMacPaint;           break;
    case kReformatGutenberg:        pReformat = new ReformatGutenberg;          break;
    case kReformatUnknown:
    case kReformatMAX:
    default:                        assert(false);                              break;
    }

    return pReformat;
}

/*
 * Return a string describing the class identified by "id".  We need this for
 * the pop-up menu in the file viewer.
 *
 * Would have been nice to embed these in the individual classes, but that's
 * harder to maintain.
 */
/*static*/ const WCHAR* ReformatHolder::GetReformatName(ReformatID id)
{
    const WCHAR* descr = NULL;

    switch (id) {
    case kReformatTextEOL_HA:
        descr = L"Converted Text";
        break;
    case kReformatRaw:
        descr = L"Raw";
        break;
    case kReformatHexDump:
        descr = L"Hex Dump";
        break;
    case kReformatResourceFork:
        descr = L"Resource Fork";
        break;
    case kReformatProDOSDirectory:
        descr = L"ProDOS Directory";
        break;
    case kReformatPascalText:
        descr = L"Pascal Text";
        break;
    case kReformatPascalCode:
        descr = L"Pascal Code";
        break;
    case kReformatCPMText:
        descr = L"CP/M Text";
        break;
    case kReformatApplesoft:
        descr = L"Applesoft BASIC";
        break;
    case kReformatApplesoft_Hilite:
        descr = L"Applesoft BASIC w/Highlighting";
        break;
    case kReformatInteger:
        descr = L"Integer BASIC";
        break;
    case kReformatInteger_Hilite:
        descr = L"Integer BASIC w/Highlighting";
        break;
    case kReformatBusiness:
        descr = L"Apple /// Business BASIC";
        break;
    case kReformatBusiness_Hilite:
        descr = L"Apple /// Business BASIC w/Highlighting";
        break;
    case kReformatSCAssem:
        descr = L"S-C Assembler";
        break;
    case kReformatMerlin:
        descr = L"Merlin Assembler";
        break;
    case kReformatLISA2:
        descr = L"LISA Assembler (v2)";
        break;
    case kReformatLISA3:
        descr = L"LISA Assembler (v3)";
        break;
    case kReformatLISA4:
        descr = L"LISA Assembler (v4/v5)";
        break;
    case kReformatMonitor8:
        descr = L"//e monitor listing";
        break;
    case kReformatDisasmMerlin8:
        descr = L"8-bit disassembly (Merlin)";
        break;
    case kReformatMonitor16Long:
        descr = L"IIgs monitor listing (long regs)";
        break;
    case kReformatMonitor16Short:
        descr = L"IIgs monitor listing (short regs)";
        break;
    case kReformatDisasmOrcam16:
        descr = L"16-bit disassembly (Orca/M)";
        break;
    case kReformatAWGS_WP:
        descr = L"AppleWorks GS Word Processor";
        break;
    case kReformatTeach:
        descr = L"Teach Text";
        break;
    case kReformatGWP:
        descr = L"Generic IIgs text document";
        break;
    case kReformatMagicWindow:
        descr = L"Magic Window";
        break;
    case kReformatGutenberg:
        descr = L"Gutenberg Word Processor";
        break;
    case kReformatAWP:
        descr = L"AppleWorks Word Processor";
        break;
    case kReformatADB:
        descr = L"AppleWorks Database";
        break;
    case kReformatASP:
        descr = L"AppleWorks Spreadsheet";
        break;
    case kReformatHiRes:
        descr = L"Hi-Res / Color";
        break;
    case kReformatHiRes_BW:
        descr = L"Hi-Res / B&W";
        break;
    case kReformatDHR_Latched:
        descr = L"Double Hi-Res / Latched";
        break;
    case kReformatDHR_BW:
        descr = L"Double Hi-Res / B&W";
        break;
    case kReformatDHR_Plain140:
        descr = L"Double Hi-Res / Plain140";
        break;
    case kReformatDHR_Window:
        descr = L"Double Hi-Res / Windowed";
        break;
    case kReformatSHR_PIC:
        descr = L"Super Hi-Res";
        break;
    case kReformatSHR_JEQ:
        descr = L"JEQ Super Hi-Res";
        break;
    case kReformatSHR_Paintworks:
        descr = L"Paintworks Super Hi-Res";
        break;
    case kReformatSHR_Packed:
        descr = L"Packed Super Hi-Res";
        break;
    case kReformatSHR_APF:
        descr = L"APF Super Hi-Res";
        break;
    case kReformatSHR_3200:
        descr = L"3200-Color Super Hi-Res";
        break;
    case kReformatSHR_3201:
        descr = L"Packed 3200-Color Super Hi-Res";
        break;
    case kReformatSHR_DG256:
        descr = L"DreamGrafix 256-Color Super Hi-Res";
        break;
    case kReformatSHR_DG3200:
        descr = L"DreamGrafix 3200-Color Super Hi-Res";
        break;
    case kReformatPrintShop:
        descr = L"Print Shop Clip Art";
        break;
    case kReformatMacPaint:
        descr = L"MacPaint";
        break;
    case kReformatUnknown:
    case kReformatMAX:
    default:
        assert(false);
        descr = L"UNKNOWN";
        break;
    }

    return descr;
}

/*
 * Set the file attributes.  These are used by TestApplicability tests to
 * decide what it is we're looking at.
 */
void ReformatHolder::SetSourceAttributes(long fileType, long auxType,
    SourceFormat sourceFormat, const char* nameExt)
{
    fFileType = fileType;
    fAuxType = auxType;
    fSourceFormat = sourceFormat;

    assert(fNameExt == NULL);
    if (nameExt == NULL) {
        fNameExt = new char[1];
        fNameExt[0] = '\0';
    } else {
        fNameExt = strdup(nameExt);
    }
}

/*
 * Run through the set of reformatters and figure out which apply.
 *
 * Each reformatter function is handed the full set.  If it can make a
 * determination for more than one entry (e.g. Applesoft and ApplesoftHilite),
 * it can set all that apply.  There must not be any overlap between
 * reformatters -- this is here so that a single reformatter may have more
 * than one entry without having to re-process the data multiple times.
 *
 * Before calling here, the file data and file attributes (e.g. file type)
 * should be stored in "ReformatHolder".
 */
void ReformatHolder::TestApplicability(void)
{
    Reformat* pReformat;
    int i;

    for (i = 0; i < kReformatMAX; i++) {
        if (fApplies[kPartData][i] != kApplicUnknown) {
            assert(fApplies[kPartRsrc][i] != kApplicUnknown);
            assert(fApplies[kPartCmmt][i] != kApplicUnknown);
            continue;   // already set by previous test
        }
        if (!fAllow[i]) {
            if (i != 0) {
                LOGI(" NOTE: Applic %d disallowed", i);
                // did you update ConfigureReformatFromPreferences()?
            }
            fApplies[kPartData][i] = kApplicNot;
            fApplies[kPartRsrc][i] = kApplicNot;
            fApplies[kPartCmmt][i] = kApplicNot;
            continue;
        }

        /*
         * Create an instance of the object, test its applicability, and
         * then destroy the instance.  It's less efficient to do it this
         * way than some other approaches, but it's easier maintenance
         * than creating a separate table of pointers to static functions.
         */
        pReformat = GetReformatInstance((ReformatID) i);
        assert(pReformat != NULL);
        pReformat->Examine(this);
        delete pReformat;
    }

    /* don't mess with the unknown */
    assert(fApplies[kPartData][kReformatUnknown] == kApplicNot);
    assert(fApplies[kPartRsrc][kReformatUnknown] == kApplicNot);
    assert(fApplies[kPartCmmt][kReformatUnknown] == kApplicNot);
}

/*
 * Return the appropriate applicability level.
 */
ReformatHolder::ReformatApplies ReformatHolder::GetApplic(ReformatPart part,
    ReformatID id) const
{
    if (id < kReformatUnknown || id >= kReformatMAX) {
        assert(false);
        return kApplicUnknown;
    }
    if (part <= kPartUnknown || part >= kPartMAX) {
        assert(false);
        return kApplicUnknown;
    }

    return fApplies[part][id];
}

/*
 * Set the appropriate applicability level.
 */
void ReformatHolder::SetApplic(ReformatID id, ReformatApplies applyData,
    ReformatApplies applyRsrc, ReformatApplies applyCmmt)
{
    if (id <= kReformatUnknown || id >= kReformatMAX) {
        assert(false);
        return;
    }

    fApplies[kPartData][id] = applyData;
    fApplies[kPartRsrc][id] = applyRsrc;
    fApplies[kPartCmmt][id] = applyCmmt;
}

/*
 * Set the "preferred" flag on all parts that aren't "unknown" or "not"
 * for the specified id.  If "part" isn't kPartUnknown, then only that
 * part will be altered.
 *
 * The idea is to prefer one variation over another, such as highlighting
 * a BASIC program or choosing a double-hi-res algorithm.  The preference
 * indicates the default choice, but does not exclude others.
 */
void ReformatHolder::SetApplicPreferred(ReformatID id, ReformatPart part)
{
    for (int i = 0; i < kPartMAX; i++) {
        if (i == kPartUnknown)
            continue;

        if (part != kPartUnknown && i != part)
            continue;

        // Don't set "preferred" flag for "not", "probably not", "always".
        if (fApplies[i][id] >= kApplicAlways) {
            fApplies[i][id] =
                (ReformatApplies) (fApplies[i][id] | kApplicPreferred);
        }
    }
}

/*
 * Returns <0, 0, or >0 depending on whether app1 is worse, equal to,
 * or better than app2.
 */
int ReformatHolder::CompareApplies(ReformatApplies app1, ReformatApplies app2)
{
    if ((app1 & kApplicPrefMask) < (app2 & kApplicPrefMask))
        return -1;
    else if ((app1 & kApplicPrefMask) > (app2 & kApplicPrefMask))
        return 1;
    else return (app1 - app2);      // compare with "preferred" bit
}

/*
 * Find the best reformatter for the specified part of the loaded file.
 */
ReformatHolder::ReformatID ReformatHolder::FindBest(ReformatPart part)
{
    ReformatID bestID = kReformatUnknown;
    ReformatApplies bestApply = kApplicNot;
    ReformatApplies apply;
    int i;

    /* if the source couldn't be loaded, just return "raw" */
    if (fErrorBuf[part] != NULL)
        return kReformatRaw;

    /*
     * Use the best option, or an equivalent-valued option that has
     * the "preferred" flag set.
     */
    for (i = 0; i < kReformatMAX; i++) {
        apply = GetApplic(part, (ReformatID) i);
        if (CompareApplies(apply, bestApply) > 0) {
            bestApply = apply;
            bestID = (ReformatID) i;
        }
    }

    if (bestID == kReformatUnknown || bestApply == kApplicNot) {
        LOGW("Did you forget to call TestApplicability?");
        assert(false);
        return kReformatRaw;
    }

    LOGI("Best is %d at lvl=%d", bestID, bestApply);

    return bestID;
}

/*
 * Apply the requested formatter to the specified part.
 */
ReformatOutput* ReformatHolder::Apply(ReformatPart part, ReformatID id)
{
    ReformatOutput* pOutput;
    Reformat* pReformat;
    int result;

    if (id <= kReformatUnknown || id >= kReformatMAX ||
        part <= kPartUnknown || part >= kPartMAX)
    {
        LOGW("Invalid reformat request (part=%d id=%d)", part, id);
        assert(false);
        return NULL;
    }

    /* create a place for the output */
    pOutput = new ReformatOutput;
    if (pOutput == NULL) {
        assert(false);
        return NULL;     // alloc failure
    }

    /*
     * If the caller was unable to fill our source buffer, they will have
     * supplied us with an error message.  Return that instead of the data.
     */
    if (fErrorBuf[part] != NULL) {
        pOutput->SetTextBuf(fErrorBuf[part], strlen(fErrorBuf[part]), false);
        pOutput->SetOutputKind(ReformatOutput::kOutputErrorMsg);
        pOutput->SetFormatDescr(L"Error Message");
        return pOutput;
    }

    /*
     * Set the format description here, based on the id.  If the reformatter
     * fails we will need to change this, but it allows the reformatter to
     * override our label with its own in case it wants to indicate some
     * sort of sub-variant.
     */
    pOutput->SetFormatDescr(GetReformatName(id));

    /* create an instance of a reformatter */
    pReformat = GetReformatInstance(id);
    assert(pReformat != NULL);
    result = pReformat->Process(this, id, part, pOutput);
    delete pReformat;

    /*
     * If it fails, return a pointer to the source buffer.
     *
     * This commonly happens on zero-length files.  The chosen reformatter
     * rejects it, returns -1, and we return the source buffer.  Since even
     * zero-length files are guaranteed to have some sort of allocated
     * buffer, "pOutput" never points at NULL.  Unless, of course, a text
     * reformatter produces no output but still returns "success".
     */
    if (result < 0) {
        pOutput->SetTextBuf((char*)fSourceBuf[part], fSourceLen[part], false);
        pOutput->SetOutputKind(ReformatOutput::kOutputRaw);
        pOutput->SetFormatDescr(GetReformatName(kReformatRaw));
    }

    return pOutput;
}

/*
 * Get the appropriate input buffer.
 */
const uint8_t* ReformatHolder::GetSourceBuf(ReformatPart part) const
{
    if (part <= kPartUnknown || part >= kPartMAX) {
        assert(false);
        return NULL;
    }

    return fSourceBuf[part];
}

/*
 * Get the length of the appropriate input buffer.
 */
long ReformatHolder::GetSourceLen(ReformatPart part) const
{
    if (part <= kPartUnknown || part >= kPartMAX) {
        assert(false);
        return NULL;
    }

    return fSourceLen[part];
}

/*
 * Set the input buffer.
 *
 * A buffer is required, even for empty input.  This makes the overall
 * housekeeping simpler.
 *
 * The ReformatHolder "owns" the buffer afterward, so the caller should
 * discard its pointer.
 */
void ReformatHolder::SetSourceBuf(ReformatPart part, uint8_t* buf, long len)
{
    if (part <= kPartUnknown || part >= kPartMAX) {
        assert(false);
        return;
    }
    assert(buf != NULL);
    assert(len >= 0);

    fSourceBuf[part] = buf;
    fSourceLen[part] = len;
}

/*
 * Specify an error message to return instead of reformatted text for a
 * given part.
 */
void ReformatHolder::SetErrorMsg(ReformatPart part, const char* msg)
{
    assert(msg != NULL && *msg != '\0');
    assert(part > kPartUnknown && part < kPartMAX);
    assert(fErrorBuf[part] == NULL);

    fErrorBuf[part] = strdup(msg);
    LOGI("+++ set error message for part %d to '%hs'", part, msg);
}

void ReformatHolder::SetErrorMsg(ReformatPart part, const CString& str)
{
    CStringA stra(str);
    SetErrorMsg(part, (LPCSTR) stra);
}
