/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Reformat "generic" GWP and Teach files.
 */
#include "StdAfx.h"
#include "Teach.h"
#include "ResourceFork.h"


/*
 * ==========================================================================
 *      ReformatGWP
 * ==========================================================================
 */

/*
 * Decide whether or not we want to treat this file as a "generic" IIgs
 * document.  Possibly useful for converting the special characters.  We'll
 * activate it for any GWP file other than the two known types.
 */
void ReformatGWP::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;

    if (pHolder->GetFileType() == kTypeGWP &&
        (pHolder->GetAuxType() != 0x5445 && pHolder->GetAuxType() != 0x8010))
    {
        applies = ReformatHolder::kApplicProbably;
    }

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

/*
 * Convert GWP into formatted text.
 */
int ReformatGWP::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;

    Charset::CheckGSCharConv();
    RTFBegin();

    /* convert EOL markers and IIgs characters */
    uint8_t ch;
    while (srcLen) {
        ch = *srcBuf++;
        srcLen--;

        if (ch == '\r') {
            /* got CR, check for CRLF -- not really expected on IIgs */
            if (srcLen != 0 && *srcBuf == '\n') {
                srcBuf++;
                srcLen--;
            }
            BufPrintf("\r\n");
        } else if (ch == '\n') {
            BufPrintf("\r\n");
        } else {
            // RTF is always off, so just use BufPrintf
            BufPrintf("%c", Charset::ConvertMacRomanTo1252(ch));
        }
    }

    RTFEnd();

    SetResultBuffer(pOutput);
    return 0;
}


/*
 * ==========================================================================
 *      ReformatTeach
 * ==========================================================================
 */

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

    if (pHolder->GetFileType() == kTypeGWP && pHolder->GetAuxType() == 0x5445)
        applies = ReformatHolder::kApplicYes;

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

/*
 * Convert Teach Text into formatted text.
 *
 * The text is in the data fork and the formatting is in the resource fork.
 */
int ReformatTeach::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    const uint8_t* dataBuf;
    const uint8_t* rsrcBuf;
    long dataLen, rsrcLen;
    const uint8_t* styleBlock;
    long styleLen;

    if (part != ReformatHolder::kPartData)
        return -1;

    dataBuf = pHolder->GetSourceBuf(ReformatHolder::kPartData);
    dataLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
    rsrcBuf = pHolder->GetSourceBuf(ReformatHolder::kPartRsrc);
    rsrcLen = pHolder->GetSourceLen(ReformatHolder::kPartRsrc);
    if (dataBuf == NULL || rsrcBuf == NULL || dataLen <= 0 || rsrcLen <= 0) {
        LOGI("Teach reformatter missing one fork of the file");
        return -1;
    }
    Charset::CheckGSCharConv();

    /* find the rStyleBlock */
    if (!ReformatResourceFork::GetResource(rsrcBuf, rsrcLen, 0x8012, 0x0001,
        &styleBlock, &styleLen))
    {
        LOGI("Resource fork of Teach Text file not found");
        return -1;
    }

    RStyleBlock rStyleBlock;
    if (!rStyleBlock.Create(styleBlock, styleLen)) {
        LOGI("Unable to unpack rStyleBlock");
        return -1;
    }

    fUseRTF = true;
    RTFBegin();

    /*
     * Configure ruler options.  The format allows us to have more than one
     * ruler, but doesn't provide a way to select between them, so we have
     * to assume that the first ruler is global.
     *
     * In practice, the Teach application doesn't provide a way to set
     * margins or tab stops, so there's really not much to do.  It seems to
     * create tab stops with 64-pixel widths, which translates to 10 tab
     * stops per page.  WordPad seems to default to 12, so I figure there's
     * not much point in futzing with it since the fonts are all different
     * anyway.
     */
    RStyleBlock::TERuler* pRuler = rStyleBlock.GetRuler(0);
    assert(pRuler != NULL);
    if (pRuler->GetJustification() != RStyleBlock::TERuler::kJustLeft) {
        LOGI("WARNING: not using left justified Teach text");
        /* ignore it */
    }

    for (int style = 0; style < rStyleBlock.GetNumStyleItems(); style++) {
        RStyleBlock::StyleItem* pStyleItem;
        RStyleBlock::TEStyle* pStyle;
        int numBytes;

        /* set up the style */
        pStyleItem = rStyleBlock.GetStyleItem(style);
        assert(pStyleItem != NULL);
        if (pStyleItem->GetLength() == RStyleBlock::StyleItem::kUnusedItem)
            continue;
        numBytes = pStyleItem->GetLength();
        pStyle = rStyleBlock.GetStyle(pStyleItem->GetStyleIndex());

        /*
         * Set the font first; that defines the point size multiplier.  Set
         * the size second, because the point size determines whether we
         * show underline.  Set the style last.
         */
        RTFSetGSFont(pStyle->GetFontFamily());
        RTFSetGSFontSize(pStyle->GetFontSize());
        RTFSetGSFontStyle(pStyle->GetTextStyle());

        /*
         * The Teach app doesn't let you set text colors, but it appears
         * there's a way to do it, since the SpyHunterGS Read.Me file has
         * a bit of red in it.
         */
        if (pStyle->GetTextColor() != 0) {
            // do something with color, someday
        }

        /* output the characters */
        uint8_t uch;
        while (numBytes--) {
            if (!dataLen) {
                LOGI("WARNING: Teach underrun (%ld wanted)", numBytes);
                break;
            }
            uch = *dataBuf;
            if (uch == '\r') {
                RTFNewPara();
            } else if (uch == '\t') {
                RTFTab();
            } else {
                RTFPrintUTF16Char(Charset::ConvertMacRomanToUTF16(uch));
            }
            dataBuf++;
            dataLen--;
        }
    }
    if (dataLen) {
        LOGI("WARNING: Teach overrun (%ld remain)", dataLen);
        /* no big deal */
    }


    RTFEnd();

    SetResultBuffer(pOutput, true);
    return 0;
}


/*
 * ==========================================================================
 *      RStyleBlock functions
 * ==========================================================================
 */

/*
 * Unpack an rStyleBlock resource.
 */
bool RStyleBlock::Create(const uint8_t* buf, long len)
{
    uint16_t version;
    uint32_t partLen;
    int i;

    assert(buf != NULL);
    if (len < kMinLen) {
        LOGI("Too short to be rStyleBlock (%d)", len);
        return false;
    }

    version = Reformat::Read16(&buf, &len);
    if (version != kExpectedVersion) {
        LOGI("Bad rStyleBlock version (%d)", version);
        return false;
    }

    /* extract ruler(s) */
    partLen = Reformat::Read32(&buf, &len);
    if (partLen > (uint32_t) (len+8)) {
        /* not enough to satisfy data + two more counts */
        LOGI("Invalid part1 length (%d vs %d)", partLen, len);
        return false;
    }

    fNumRulers = 1;
    fpRulers = new TERuler[fNumRulers];
    if (fpRulers == NULL)
        return false;
    if (fpRulers->Create(buf, partLen) <= 0)
        return false;

    buf += partLen;
    len -= partLen;

    /* extract TEStyles */
    partLen = Reformat::Read32(&buf, &len);
    if (partLen > (uint32_t) (len + 4)) {
        LOGI("Invalid part2 length (%d vs %d)", partLen, len);
        return false;
    }
    if ((partLen % TEStyle::kDataLen) != 0) {
        LOGI("Invalid part2 length (%d mod %d)",
            partLen, TEStyle::kDataLen);
        return false;
    }

    fNumStyles = partLen / TEStyle::kDataLen;
    fpStyles = new TEStyle[fNumStyles];
    if (fpStyles == NULL)
        return false;
    for (i = 0; i < fNumStyles; i++) {
        fpStyles[i].Create(buf);
        buf += TEStyle::kDataLen;
        len -= TEStyle::kDataLen;
    }

    /* extract StyleItems */
    fNumStyleItems = (int) Reformat::Read32(&buf, &len);
    partLen = fNumStyleItems * StyleItem::kItemDataLen;
    if (partLen > (uint32_t) len) {
        LOGI("Invalid part3 length (%d vs %d)", partLen, len);
        return false;
    }

    fpStyleItems = new StyleItem[fNumStyleItems];
    if (fpStyleItems == NULL)
        return false;
    for (i = 0; i < fNumStyleItems; i++) {
        fpStyleItems[i].Create(buf);
        if ((fpStyleItems[i].GetOffset() % TEStyle::kDataLen) != 0) {
            LOGI("Invalid offset %d (mod %d)",
                fpStyleItems[i].GetOffset(), TEStyle::kDataLen);
            return false;
        }
        buf += StyleItem::kItemDataLen;
        len -= StyleItem::kItemDataLen;
    }

    if (len != 0) {
        LOGI("WARNING: at end of rStyleBlock, len is %ld", len);
    }

    return true;
}

/*
 * Construct a TERuler from a chunk of data.
 *
 * Returns the #of bytes consumed, or -1 on failure.
 */
int RStyleBlock::TERuler::Create(const uint8_t* buf, long len)
{
    long origLen = len;

    if (len < kMinLen)
        return -1;

    fLeftMargin = Reformat::Get16LE(buf);
    fLeftIndent = Reformat::Get16LE(buf + 2);
    fRightMargin = Reformat::Get16LE(buf + 4);
    fJust = Reformat::Get16LE(buf + 6);
    fExtraLS = Reformat::Get16LE(buf + 8);
    fFlags = Reformat::Get16LE(buf + 10);
    fUserData = Reformat::Get32LE(buf + 12);
    fTabType = Reformat::Get16LE(buf + 16);
    buf += 18;
    len -= 18;

    /*
     * What we do now depends on the value of fTabType:
     *  0: no tabs are set, there's nothing else to read.
     *  1: regularly-spaced tabs; spacing defined by fTabTerminator field.
     *  2: irregular tabs, defined by array terminated by 0xffff in the
     *     fTabTerminator field.
     *
     * At present we're just throwing the tab array out.
     */
    switch (fTabType) {
    case 0:
        fTabTerminator = 0;
        break;
    case 1:
        fTabTerminator = Reformat::Get16LE(buf);
        buf += 2;
        len -= 2;
        break;
    case 2:
        while (len >= 2) {
            TabItem tabItem;
            tabItem.tabKind = Reformat::Get16LE(buf);
            buf += 2;
            len -= 2;

            if (tabItem.tabKind == kTabArrayEnd || len < 2)
                break;
            tabItem.tabData = Reformat::Get16LE(buf);
            buf += 2;
            len -= 2;
        }
        break;
    default:
        LOGI("Invalid tab type %d", fTabType);
        return -1;
    }

    LOGI("TERuler consumed %ld bytes (%ld left over)", origLen - len, len);
    return origLen - len;
}

/*
 * Extract a TEStyle object from the buffer.
 */
void RStyleBlock::TEStyle::Create(const uint8_t* buf)
{
    fFontID = Reformat::Get32LE(buf);
    fForeColor = Reformat::Get16LE(buf + 4);
    fBackColor = Reformat::Get16LE(buf + 6);
    fUserData = Reformat::Get32LE(buf + 8);

    LOGI("  TEStyle: font fam=0x%04x size=%-2d style=0x%02x  fore=0x%04x",
        GetFontFamily(), GetFontSize(), GetTextStyle(), fForeColor);
}

/*
 * Extract a StyleItem object from the buffer.
 */
void RStyleBlock::StyleItem::Create(const uint8_t* buf)
{
    fLength = Reformat::Get32LE(buf);
    fOffset = Reformat::Get32LE(buf + 4);

    LOGI("  StyleItem: len=%ld off=%ld", fLength, fOffset);
}
