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


/* our color map */
const ReformatText::TextColor kDefaultColor = ReformatText::kColorDarkGrey;
const ReformatText::TextColor kLineNumColor = ReformatText::kColorDarkGrey;
const ReformatText::TextColor kKeywordColor = ReformatText::kColorBlack;
const ReformatText::TextColor kCommentColor = ReformatText::kColorMediumGreen;
const ReformatText::TextColor kStringColor  = ReformatText::kColorMediumBlue;
const ReformatText::TextColor kColonColor   = ReformatText::kColorRed;
 //kColorMediumGrey;

/*
 * ===========================================================================
 *      Applesoft BASIC
 * ===========================================================================
 */

/*
 * Applesoft BASIC file format:
 *
 *  <16-bit file length>  [DOS 3.3 only; not visible here]
 *  <line> ...
 *  <EOF marker ($0000)>
 *
 * Each line consists of:
 *  <16-bit address of next line (relative to $800)>
 *  <16-bit line number, usually 0-63999>
 *  <tokens | characters> ...
 *  <EOL marker ($00)>
 *
 * All values are little-endian.  Numbers are stored as characters.
 */

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

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

    pHolder->SetApplic(ReformatHolder::kReformatApplesoft, applies,
        ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
    pHolder->SetApplic(ReformatHolder::kReformatApplesoft_Hilite, applies,
        ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);

    if (pHolder->GetOption(ReformatHolder::kOptHiliteBASIC) != 0)
        pHolder->SetApplicPreferred(ReformatHolder::kReformatApplesoft_Hilite);
    else
        pHolder->SetApplicPreferred(ReformatHolder::kReformatApplesoft);
}

/*
 * Values from 128 to 234 are tokens in Applesoft.  Values from 235 to 255
 * show up as error messages.  The goal here is to produce values that are
 * human-readable and/or EXECable, so no attempt has been made to display
 * the error values.
 */
static const char gApplesoftTokens[128 * ReformatApplesoft::kTokenLen] = {
    "END\0    FOR\0    NEXT\0   DATA\0   INPUT\0  DEL\0    DIM\0    READ\0   "
    "GR\0     TEXT\0   PR#\0    IN#\0    CALL\0   PLOT\0   HLIN\0   VLIN\0   "
    "HGR2\0   HGR\0    HCOLOR=\0HPLOT\0  DRAW\0   XDRAW\0  HTAB\0   HOME\0   "
    "ROT=\0   SCALE=\0 SHLOAD\0 TRACE\0  NOTRACE\0NORMAL\0 INVERSE\0FLASH\0  "
    "COLOR=\0 POP\0    VTAB\0   HIMEM:\0 LOMEM:\0 ONERR\0  RESUME\0 RECALL\0 "
    "STORE\0  SPEED=\0 LET\0    GOTO\0   RUN\0    IF\0     RESTORE\0&\0      "
    "GOSUB\0  RETURN\0 REM\0    STOP\0   ON\0     WAIT\0   LOAD\0   SAVE\0   "
    "DEF\0    POKE\0   PRINT\0  CONT\0   LIST\0   CLEAR\0  GET\0    NEW\0    "
    "TAB(\0   TO\0     FN\0     SPC(\0   THEN\0   AT\0     NOT\0    STEP\0   "
    "+\0      -\0      *\0      /\0      ^\0      AND\0    OR\0     >\0      "
    "=\0      <\0      SGN\0    INT\0    ABS\0    USR\0    FRE\0    SCRN(\0  "
    "PDL\0    POS\0    SQR\0    RND\0    LOG\0    EXP\0    COS\0    SIN\0    "
    "TAN\0    ATN\0    PEEK\0   LEN\0    STR$\0   VAL\0    ASC\0    CHR$\0   "
    "LEFT$\0  RIGHT$\0 MID$\0   ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  "
    "ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  "
    "ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0 "
};

/*
 * Make table available.
 */
/*static*/ const char* ReformatApplesoft::GetApplesoftTokens(void)
{
    return gApplesoftTokens;
}


/*
 * Reformat an Applesoft BASIC program into a text format that mimics the
 * output of the "LIST" command (with POKE 33,73 to suppress CRs).
 */
int ReformatApplesoft::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    const uint8_t* srcPtr = pHolder->GetSourceBuf(part);
    long srcLen = pHolder->GetSourceLen(part);
    long length = srcLen;
    int retval = -1;

    if (srcLen > 65536)
        fUseRTF = false;
    if (fUseRTF) {
        if (id != ReformatHolder::kReformatApplesoft_Hilite)
            fUseRTF = false;
    }

    RTFBegin(kRTFFlagColorTable);

    /*
     * Make sure there's enough here to get started.  We want to return an
     * "okay" result because we want this treated like a reformatted empty
     * BASIC program rather than a non-Applesoft file.
     */
    if (length < 2) {
        LOGI("  BAS truncated?");
        //fExpBuf.CreateWorkBuf();
        BufPrintf("\r\n");
        goto done;
    }

    while (length > 0) {
        uint16_t nextAddr;
        uint16_t lineNum;
        bool inQuote = false;
        bool inRem = false;

        nextAddr = Read16(&srcPtr, &length);
        if (nextAddr == 0) {
            /* ProDOS sticks an extra byte on the end? */
            if (length > 1) {
                LOGI("  BAS ended early; len is %d", length);
            }
            break;
        }

        /* print line number */
        RTFSetColor(kLineNumColor);
        lineNum = Read16(&srcPtr, &length);
        BufPrintf(" %u ", lineNum);

        RTFSetColor(kDefaultColor);

        assert(kTokenLen == 8);     // we do "<< 3" below, so this must hold

        /* print a line */
        while (*srcPtr != 0 && length > 0) {
            if (*srcPtr & 0x80) {
                /* token */
                //RTFBoldOn();
                RTFSetColor(kKeywordColor);
                BufPrintf(" %s ", &gApplesoftTokens[((*srcPtr) & 0x7f) << 3]);
                //RTFBoldOff();
                RTFSetColor(kDefaultColor);

                if (*srcPtr == 0xb2) {
                    // REM -- do rest of line in green
                    RTFSetColor(kCommentColor);
                    inRem = true;
                }
            } else {
                /* simple character */
                if (fUseRTF) {
                    if (*srcPtr == '"' && !inRem) {
                        if (!inQuote) {
                            RTFSetColor(kStringColor);
                            RTFPrintChar(*srcPtr);
                        } else {
                            RTFPrintChar(*srcPtr);
                            RTFSetColor(kDefaultColor);
                        }
                        inQuote = !inQuote;
                    } else if (*srcPtr == ':' && !inRem && !inQuote) {
                        RTFSetColor(kColonColor);
                        RTFPrintChar(*srcPtr);
                        RTFSetColor(kDefaultColor);
                    } else if (inRem && *srcPtr == '\r') {
                        RTFNewPara();
                    } else {
                        RTFPrintChar(*srcPtr);
                    }
                } else {
                    if (inRem && *srcPtr == '\r') {
                        BufPrintf("\r\n");
                    } else {
                        BufPrintf("%c", *srcPtr);
                    }
                }
            }

            srcPtr++;
            length--;
        }

        if (inQuote || inRem)
            RTFSetColor(kDefaultColor);
        inQuote = inRem = false;

        srcPtr++;
        length--;

        if (!length) {
            LOGI("  BAS truncated in mid-line");
            break;
        }

        RTFNewPara();
    }

done:
    RTFEnd();

    SetResultBuffer(pOutput);
    retval = 0;

//bail:
    return retval;
}


/*
 * ===========================================================================
 *      Integer BASIC
 * ===========================================================================
 */
#include "Asm.h"

/*
 * Integer BASIC file format (thanks to Paul Schlyter, pausch at saaf.se):
 *
 *  <16-bit file length>  [DOS 3.3 only; not visible here]
 *  <line> ...
 *
 * Each line consists of:
 *  <8-bit line length>
 *  <16-bit line number>
 *  <token | character | variable> ...
 *  <end-of-line token ($01)>
 *
 * Each line is a stream of bytes:
 *  $01: end of line
 *  $12-$7f: language token
 *  $b0-b9 ('0'-'9'): start of integer constant (first byte has no meaning?)
 *          next 16 bits hold the number
 *  $c1-da ('A'-'Z'): start of a variable name; ends on value <0x80
 *          next several bytes hold the name
 *
 * Most of the first $11 tokens are illegal except as part of an integer
 * constant, which just means that you can't type them into a program from
 * the keyboard.  If you POKE them in manually, things like "himem:" and
 * "del" will work.
 *
 * $ba-$c0 and $db-$ff are illegal except in a string constant.
 *
 * There is no end-of-file marker.
 */

/*
 * Tokens.
 */
static const char* const gIntegerTokens[128] = {
    "HIMEM:",   "<EOL>",    "_ ",       ":",
    "LOAD ",    "SAVE ",    "CON ",     "RUN ",
    "RUN ",     "DEL ",     ",",        "NEW ",
    "CLR ",     "AUTO ",    ",",        "MAN ",
    "HIMEM:",   "LOMEM:",   "+",        "-",
    "*",        "/",        "=",        "#",
    ">=",       ">",        "<=",       "<>",
    "<",        "AND ",     "OR ",      "MOD ",

    "^ ",       "+",        "(",        ",",
    "THEN ",    "THEN ",    ",",        ",",
    "\"",       "\"",       "(",        "!",
    "!",        "(",        "PEEK ",    "RND ",
    "SGN ",     "ABS ",     "PDL ",     "RNDX ",
    "(",        "+",        "-",        "NOT ",
    "(",        "=",        "#",        "LEN(",
    "ASC( ",    "SCRN( ",   ",",        "(",

    "$",        "$",        "(",        ",",
    ",",        ";",        ";",        ";",
    ",",        ",",        ",",        "TEXT ",
    "GR ",      "CALL ",    "DIM ",     "DIM ",
    "TAB ",     "END ",     "INPUT ",   "INPUT ",
    "INPUT ",   "FOR ",     "=",        "TO ",
    "STEP ",    "NEXT ",    ",",        "RETURN ",
    "GOSUB ",   "REM ",     "LET ",     "GOTO ",

    "IF ",      "PRINT ",   "PRINT ",   "PRINT ",
    "POKE ",    ",",        "COLOR= ",  "PLOT ",
    ",",        "HLIN ",    ",",        "AT ",
    "VLIN ",    ",",        "AT ",      "VTAB ",
    "=",        "=",        ")",        ")",
    "LIST ",    ",",        "LIST ",    "POP ",
    "NODSP ",   "NODSP ",   "NOTRACE ", "DSP ",
    "DSP ",     "TRACE ",   "PR# ",     "IN# "
};

/*
 * Decide whether or not we want to handle this file.
 */
void ReformatInteger::Examine(ReformatHolder* pHolder)
{
    ReformatHolder::ReformatApplies apply = ReformatHolder::kApplicNot;
    if (pHolder->GetFileType() == kTypeINT) {
        if (ReformatSCAssem::IsSCAssem(pHolder)) {
            /* possibly intbasic */
            apply = ReformatHolder::kApplicMaybe;
        } else if (ReformatLISA3::IsLISA(pHolder)) {
            /* possibly intbasic */
            apply = ReformatHolder::kApplicMaybe;
        } else if (ReformatLISA4::IsLISA(pHolder)) {
            /* possibly intbasic */
            apply = ReformatHolder::kApplicMaybe;
        } else {
            /* definitely intbasic */
            apply = ReformatHolder::kApplicYes;
        }
    } else {
        /* not intbasic */
        apply = ReformatHolder::kApplicNot;
    }

    //apply = ReformatHolder::kApplicMaybe;     // DEBUG DEBUG

    pHolder->SetApplic(ReformatHolder::kReformatInteger, apply,
        ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
    pHolder->SetApplic(ReformatHolder::kReformatInteger_Hilite, apply,
        ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);

    if (pHolder->GetOption(ReformatHolder::kOptHiliteBASIC) != 0)
        pHolder->SetApplicPreferred(ReformatHolder::kReformatInteger_Hilite);
    else
        pHolder->SetApplicPreferred(ReformatHolder::kReformatInteger);
}

/*
 * Reformat an Integer BASIC program into a text format that mimics the
 * output of the "LIST" command.
 */
int ReformatInteger::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    const uint8_t* srcPtr = pHolder->GetSourceBuf(part);
    long srcLen = pHolder->GetSourceLen(part);
    long length = srcLen;
    int retval = -1;

    //srcPtr += 0xff0; //0x228e;
    //srcLen -= 0xff0; //0x228e;

    if (srcLen > 65536)
        fUseRTF = false;
    if (fUseRTF) {
        if (id != ReformatHolder::kReformatInteger_Hilite)
            fUseRTF = false;
    }

    RTFBegin(kRTFFlagColorTable);

    /*
     * Make sure there's enough here to get started.  We want to return an
     * "okay" result because we want this treated like a reformatted empty
     * BASIC program rather than a non-Integer file.
     */
    if (length < 2) {
        LOGI("  INT truncated?");
        BufPrintf("\r\n");
        goto done;
    }

    while (length > 0) {
        uint8_t lineLen;
        uint16_t lineNum;
        bool trailingSpace;
        bool newTrailingSpace = false;

        /* pull the length byte, which we sanity-check */
        lineLen = *srcPtr++;
        length--;
        if (lineLen == 0) {
            LOGI("  INT found zero-length line?");
            break;
        }

        /* line number */
        RTFSetColor(kLineNumColor);
        lineNum = Read16(&srcPtr, &length);
        BufPrintf("%5u ", lineNum);
        RTFSetColor(kDefaultColor);

        trailingSpace = true;
        while (*srcPtr != 0x01 && length > 0) {
            if (*srcPtr == 0x28) {
                /* start of quoted text */
                RTFSetColor(kStringColor);
                BufPrintf("\"");
                length--;
                while (*++srcPtr != 0x29 && length > 0) {
                    /* escape chars, but let Ctrl-D and Ctrl-G through */
                    if (fUseRTF && *srcPtr != 0x84 && *srcPtr != 0x87)
                        RTFPrintChar(*srcPtr & 0x7f);
                    else
                        BufPrintf("%c", *srcPtr & 0x7f);
                    length--;
                }
                if (*srcPtr != 0x29) {
                    LOGI("  INT ended while in a string constant");
                    break;
                }
                BufPrintf("\"");
                RTFSetColor(kDefaultColor);
                srcPtr++;
                length--;
            } else if (*srcPtr == 0x5d) {
                /* start of REM statement, run to EOL */
                //RTFBoldOn();
                RTFSetColor(kKeywordColor);
                BufPrintf("%sREM ", trailingSpace ? "" : " ");
                //RTFBoldOff();
                RTFSetColor(kCommentColor);
                length--;
                while (*++srcPtr != 0x01) {
                    if (fUseRTF)
                        RTFPrintChar(*srcPtr & 0x7f);
                    else
                        BufPrintf("%c", *srcPtr & 0x7f);
                    length--;
                }
                RTFSetColor(kDefaultColor);
                if (*srcPtr != 0x01) {
                    LOGI("  INT ended while in a REM statement");
                    break;
                }
            } else if (*srcPtr >= 0xb0 && *srcPtr <= 0xb9) {
                /* start of integer constant */
                srcPtr++;
                length--;
                if (length < 2) {
                    LOGI("  INT ended while in an integer constant");
                    break;
                }
                int val;
                val = Read16(&srcPtr, &length);
                BufPrintf("%d", val);
            } else if (*srcPtr >= 0xc1 && *srcPtr <= 0xda) {
                /* start of variable name */
                while ((*srcPtr >= 0xc1 && *srcPtr <= 0xda) ||
                       (*srcPtr >= 0xb0 && *srcPtr <= 0xb9))
                {
                    /* note no RTF-escaped chars in this range */
                    BufPrintf("%c", *srcPtr & 0x7f);
                    srcPtr++;
                    length--;
                }
            } else if (*srcPtr < 0x80) {
                /* found a token; try to get the whitespace right */
                /* (maybe should've left whitespace on the ends of tokens
                    that are always followed by whitespace...?) */
                const char* token;
                token = gIntegerTokens[*srcPtr];
                //RTFBoldOn();
                if (*srcPtr == 0x03)    // colon
                    RTFSetColor(kColonColor);
                else
                    RTFSetColor(kKeywordColor);
                if (token[0] >= 0x21 && token[0] <= 0x3f || *srcPtr < 0x12) {
                    /* does not need leading space */
                    BufPrintf("%s", token);
                } else {
                    /* needs leading space; combine with prev if it exists */
                    if (trailingSpace)
                        BufPrintf("%s", token);
                    else
                        BufPrintf(" %s", token);
                }
                if (token[strlen(token)-1] == ' ')
                    newTrailingSpace = true;
                //RTFBoldOff();
                RTFSetColor(kDefaultColor);
                srcPtr++;
                length--;
            } else {
                /* should not happen */
                LOGI("  INT unexpected value 0x%02x at byte %d",
                    *srcPtr, srcPtr - pHolder->GetSourceBuf(part));

                /* skip past it and keep trying */
                srcPtr++;
                length--;
            }

            trailingSpace = newTrailingSpace;
            newTrailingSpace = false;
        } /*while line*/

        /* skip past EOL token */
        if (*srcPtr != 0x01 && length > 0) {
            LOGI("bailing");     // must've failed during processing
            goto bail;
        }
        srcPtr++;
        length--;

        RTFNewPara();
    }

done:
    RTFEnd();

    SetResultBuffer(pOutput);
    retval = 0;

bail:
    return retval;
}

/*
 * ===========================================================================
 *      Apple /// Business BASIC
 * ===========================================================================
 */

/*
 * Apple /// Business BASIC file format:
 *
 *  <16-bit file length>
 *  <line> ...
 *  <EOF marker ($0000)>
 *
 * Each line consists of:
 *  <8-bit offset to next line>
 *  <16-bit line number, usually 0-63999>
 *  <tokens | characters> ...
 *  <EOL marker ($00)>
 *
 * All values are little-endian.  Numbers are stored as characters.
 */

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

    if (pHolder->GetFileType() == kTypeBA3)
        applies = ReformatHolder::kApplicYes;

    pHolder->SetApplic(ReformatHolder::kReformatBusiness, applies,
        ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
    pHolder->SetApplic(ReformatHolder::kReformatBusiness_Hilite, applies,
        ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);

    if (pHolder->GetOption(ReformatHolder::kOptHiliteBASIC) != 0)
        pHolder->SetApplicPreferred(ReformatHolder::kReformatBusiness_Hilite);
    else
        pHolder->SetApplicPreferred(ReformatHolder::kReformatBusiness);
}

/*
 * Values from 128 to 234 are tokens in Business BASIC.  Values from 235 to 255
 * show up as error messages.  The goal here is to produce values that are
 * human-readable and/or EXECable, so no attempt has been made to display
 * the error values.
 * TODO: verify this comment -- looks like copy & paste from BAS token table
 */
static const char gBusinessTokens[128*10] = {
/* 0x80 */ "END\0      FOR\0      NEXT\0     INPUT\0    OUTPUT\0   DIM\0      READ\0     WRITE\0    "
/* 0x88 */ "OPEN\0     CLOSE\0    *error*\0  TEXT\0     *error*\0  BYE\0      *error*\0  *error*\0  "
/* 0x90 */ "*error*\0  *error*\0  *error*\0  WINDOW\0   INVOKE\0   PERFORM\0  *error*\0  *error*\0  "
/* 0x98 */ "FRE\0      HPOS\0     VPOS\0     ERRLIN\0   ERR\0      KBD\0      EOF\0      TIME$\0    "
/* 0xa0 */ "DATE$\0    PREFIX$\0  EXFN.\0    EXFN%.\0   OUTREC\0   INDENT\0   *error*\0  *error*\0  "
/* 0xa8 */ "*error*\0  *error*\0  *error*\0  *error*\0  *error*\0  POP\0      HOME\0     *error*\0  "
/* 0xb0 */ "SUB$(\0    OFF\0      TRACE\0    NOTRACE\0  NORMAL\0   INVERSE\0  SCALE(\0   RESUME\0   "
/* 0xb8 */ "*error*\0  LET\0      GOTO\0     IF\0       RESTORE\0  SWAP\0     GOSUB\0    RETURN\0   "
/* 0xc0 */ "REM\0      STOP\0     ON\0       *error*\0  LOAD\0     SAVE\0     DELETE\0   RUN\0      "
/* 0xc8 */ "RENAME\0   LOCK\0     UNLOCK\0   CREATE\0   EXEC\0     CHAIN\0    *error*\0  *error*\0  "
/* 0xd0 */ "*error*\0  CATALOG\0  *error*\0  *error*\0  DATA\0     IMAGE\0    CAT\0      DEF\0      "
/* 0xd8 */ "*error*\0  PRINT\0    DEL\0      ELSE\0     CONT\0     LIST\0     CLEAR\0    GET\0      "
/* 0xe0 */ "NEW\0      TAB\0      TO\0       SPC(\0     USING\0    THEN\0     *error*\0  MOD\0      "
/* 0xe8 */ "STEP\0     AND\0      OR\0       EXTENSION\0DIV\0      *error*\0  FN\0       NOT\0      "
/* 0xf0 */ "*error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  "
/* 0xf8 */ "*error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  tf7\0     "
};

static const char gExtendedBusinessTokens[128*10] = {
/* 0x80 */ "TAB(\0     TO\0       SPC(\0     USING\0    THEN\0     *error*\0  MOD\0      STEP\0     "
/* 0x88 */ "AND\0      OR\0       EXTENSION\0DIV\0      *error*\0  FN\0       NOT\0      *error*\0  "
/* 0x90 */ "*error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  "
/* 0x98 */ "*error*\0  *error*\0  *error*\0  *error*\0  AS\0       SGN(\0     INT(\0     ABS(\0     "
/* 0xa0 */ "*error*\0  TYP(\0     REC(\0     *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  "
/* 0xa8 */ "*error*\0  *error*\0  *error*\0  *error*\0  *error*\0  PDL(\0     BUTTON(\0  SQR(\0     "
/* 0xb0 */ "RND(\0     LOG(\0     EXP(\0     COS(\0     SIN(\0     TAN(\0     ATN(\0     *error*\0  "
/* 0xb8 */ "*error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  "
/* 0xc0 */ "*error*\0  *error*\0  *error*\0  STR$(\0    HEX$(\0    CHR$(\0    LEN(\0     VAL(\0     "
/* 0xc8 */ "ASC(\0     TEN(\0     *error*\0  *error*\0  CONV(\0    CONV&(\0   CONV$(\0   CONV%(\0   "
/* 0xd0 */ "LEFT$(\0   RIGHT$(\0  MID$(\0    INSTR(\0   *error*\0  *error*\0  *error*\0  *error*\0  "
/* 0xd8 */ "*error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  "
/* 0xe0 */ "*error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  "
/* 0xe8 */ "*error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  "
/* 0xf0 */ "*error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  "
/* 0xf8 */ "*error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0  *error*\0 "
};

/*
 * Reformat an Apple /// Business BASIC program into a text format that
 * mimics the output of the "LIST" command.
 */
int ReformatBusiness::Process(const ReformatHolder* pHolder,
    ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
    ReformatOutput* pOutput)
{
    const uint8_t* srcPtr = pHolder->GetSourceBuf(part);
    long srcLen = pHolder->GetSourceLen(part);
    long length = srcLen;
    int retval = -1;
    int nestLevels = 0;

    if (srcLen > 65536)
        fUseRTF = false;
    if (fUseRTF) {
        if (id != ReformatHolder::kReformatBusiness_Hilite)
            fUseRTF = false;
    }

    RTFBegin(kRTFFlagColorTable);

    /*
     * Make sure there's enough here to get started.  We want to return an
     * "okay" result because we want this treated like a reformatted empty
     * BASIC program rather than a non-BASIC file.
     */
    if (length < 2) {
        LOGI("  BA3 truncated?");
        //fExpBuf.CreateWorkBuf();
        BufPrintf("\r\n");
        goto done;
    }

    uint16_t fileLength;
    fileLength = Read16(&srcPtr, &length);
    LOGI("  BA3 internal file length is: %d", fileLength);

    while (length > 0) {
        uint16_t increment;
        uint16_t extendedToken;
        uint16_t lineNum;
        bool inQuote = false;
        bool inRem = false;
        bool firstData = true;
        bool literalYet = false;

        increment = Read8(&srcPtr, &length);
        LOGI("  BA3 increment to next line is: %d", increment);
        if (increment == 0) {
            /* ProDOS sticks an extra byte on the end? */
            if (length > 1) {
                LOGI("  BA3 ended early; len is %d", length);
            }
            break;
        }

        /* print line number */
        RTFSetColor(kLineNumColor);
        lineNum = Read16(&srcPtr, &length);
        LOGI("  BA3 line number: %d", lineNum);
        BufPrintf(" %u   ", lineNum);

        RTFSetColor(kDefaultColor);
        if (nestLevels > 0) {
            for (int i =0; i < nestLevels; i++)
                BufPrintf("  ");
        }

        /* print a line */
        while (*srcPtr != 0 && length > 0) {
            if (*srcPtr & 0x80) {
                /* token */
                //RTFBoldOn();
                literalYet = false;
                RTFSetColor(kKeywordColor);
                if (*srcPtr == 0x81) {
                    // Token is FOR - indent
                    nestLevels ++;
                }
                else if (*srcPtr == 0x82) {
                    // Token is NEXT - outdent
                    nestLevels --;
                }
                if (!firstData)
                    BufPrintf(" ");
                if (((*srcPtr) & 0x7f) == 0x7f)
                {
                    extendedToken = Read8(&srcPtr, &length);
                    BufPrintf("%s", &gExtendedBusinessTokens[((*srcPtr) & 0x7f) * 10]);
                    // We need to have some tokens NOT add a space after them.
                    if ((*srcPtr == 0x80) ||    // TAB(
                        (*srcPtr == 0x82) ||    // SPC(
                        (*srcPtr == 0x9d) ||    // SGN(
                        (*srcPtr == 0x9e) ||    // INT(
                        (*srcPtr == 0x9f) ||    // ABS(
                        (*srcPtr == 0xa1) ||    // TYP(
                        (*srcPtr == 0xa2) ||    // REC(
                        (*srcPtr == 0xad) ||    // PDL(
                        (*srcPtr == 0xae) ||    // BUTTON(
                        (*srcPtr == 0xaf) ||    // SQR(
                        (*srcPtr == 0xb0) ||    // RND(
                        (*srcPtr == 0xb1) ||    // LOG(
                        (*srcPtr == 0xb2) ||    // EXP(
                        (*srcPtr == 0xb3) ||    // COS(
                        (*srcPtr == 0xb4) ||    // SIN(
                        (*srcPtr == 0xb5) ||    // TAN(
                        (*srcPtr == 0xb6) ||    // ATN(
                        (*srcPtr == 0xc3) ||    // STR$(
                        (*srcPtr == 0xc4) ||    // HEX$(
                        (*srcPtr == 0xc5) ||    // CHR$(
                        (*srcPtr == 0xc6) ||    // LEN(
                        (*srcPtr == 0xc7) ||    // VAL(
                        (*srcPtr == 0xc8) ||    // ASC(
                        (*srcPtr == 0xc9) ||    // TEN(
                        (*srcPtr == 0xcc) ||    // CONV(
                        (*srcPtr == 0xcd) ||    // CONV&(
                        (*srcPtr == 0xce) ||    // CONV$(
                        (*srcPtr == 0xcf) ||    // CONV%(
                        (*srcPtr == 0xd0) ||    // LEFT$(
                        (*srcPtr == 0xd1) ||    // RIGHT$(
                        (*srcPtr == 0xd2) ||    // MID$(
                        (*srcPtr == 0xd3))      // INSTR(
                        firstData = true;
                    else
                        firstData = false;
                }
                else {
                    BufPrintf("%s", &gBusinessTokens[((*srcPtr) & 0x7f) * 10]);
                    // We need to have some tokens NOT add a space after them.
                    if ((*srcPtr == 0x99) ||    // HPOS
                        (*srcPtr == 0x9a) ||    // VPOS
                        (*srcPtr == 0x9f) ||    // TIME$
                        (*srcPtr == 0xa0) ||    // DATE$
                        (*srcPtr == 0xa1) ||    // PREFIX$
                        (*srcPtr == 0xa2) ||    // EXFN.
                        (*srcPtr == 0xa3) ||    // EXFN%.
                        (*srcPtr == 0xb0) ||    // SUB$(.
                        (*srcPtr == 0xb6) ||    // SCALE(
                        (*srcPtr == 0xc0) ||    // REM
                        (*srcPtr == 0xe3))      // SPC(
                        firstData = true;
                    else
                        firstData = false;
                }
                //RTFBoldOff();
                RTFSetColor(kDefaultColor);

                if (*srcPtr == 0xc0) {
                    // REM -- do rest of line in green
                    RTFSetColor(kCommentColor);
                    inRem = true;
                }
            } else {
                /* simple chracter */
                if (*srcPtr == ':') // Reset line if we have a colon
                    firstData = true;
                if (!firstData) {
                    if (!literalYet) {
                        BufPrintf(" ");
                        literalYet = true;
                    }
                }
                if (fUseRTF) {
                    if (*srcPtr == '"' && !inRem) {
                        if (!inQuote) {
                            RTFSetColor(kStringColor);
                            RTFPrintChar(*srcPtr);
                        } else {
                            RTFPrintChar(*srcPtr);
                            RTFSetColor(kDefaultColor);
                        }
                        inQuote = !inQuote;
                    } else if (*srcPtr == ':' && !inRem && !inQuote) {
                        RTFSetColor(kColonColor);
                        RTFPrintChar(*srcPtr);
                        RTFSetColor(kDefaultColor);
                    } else {
                        RTFPrintChar(*srcPtr);
                    }
                } else {
                    BufPrintf("%c", *srcPtr);
                }
            }

            srcPtr++;
            length--;
        }

        if (inQuote || inRem)
            RTFSetColor(kDefaultColor);
        inQuote = inRem = false;

        srcPtr++;
        length--;

        if (!length) {
            LOGI("  BA3 truncated in mid-line");
            break;
        }

        RTFNewPara();
    }

done:
    RTFEnd();

    SetResultBuffer(pOutput);
    retval = 0;

//bail:
    return retval;
}
