/*
 * MultiMail offline mail reader
 * ANSI image/text viewer

 Copyright 1998-2019 William McBrine <wmcbrine@gmail.com>
 Distributed under the GNU General Public License, version 3 or later. */

#include "interfac.h"

//------------------
// AnsiLine methods
//------------------

AnsiWindow::AnsiLine::AnsiLine(AnsiLine *parent) : prev(parent)
{
    next = 0;
    text = 0;
    length = 0;
    isasc = false;
}

AnsiWindow::AnsiLine::~AnsiLine()
{
    if (isasc)
        delete[] atext;
    else
        delete[] text;
}

AnsiWindow::AnsiLine *AnsiWindow::AnsiLine::getnext(bool isnew)
{
    if (!next)
        if (isnew)
            next = new AnsiLine(this);
    return next;
}

AnsiWindow::AnsiLine *AnsiWindow::AnsiLine::getprev()
{
    return prev;
}

int AnsiWindow::AnsiLine::unpack(chtype *tmp)
{
    int i;

    if (isasc)
        for (i = 0; i < length; i++)
            tmp[i] = att | atext[i];
    else
        if (length)
            memcpy(tmp, text, length * sizeof(chtype));

    for (i = length; i < COLS; i++)
        tmp[i] = ' ' | (C_ANSIBACK);

    return length;
}

void AnsiWindow::AnsiLine::pack(chtype *tmp, size_t newlen)
{
    size_t i;

    if (isasc)
        delete[] atext;
    else
        delete[] text;

    isasc = true;
    att = newlen ? (*tmp & ~(A_CHARTEXT)) : C_ANSIBACK;

    for (i = 0; (i < newlen) && isasc; i++)
        if ((tmp[i] & ~(A_CHARTEXT)) != att)
            isasc = false;

    if (isasc) {
        atext = newlen ? new unsigned char[newlen] : 0;
        for (i = 0; i < newlen; i++)
            atext[i] = tmp[i] & (A_CHARTEXT);
    } else {
        text = newlen ? new chtype[newlen] : 0;
        if (newlen)
            memcpy(text, tmp, newlen * sizeof(chtype));
    }

    length = newlen;
}

void AnsiWindow::AnsiLine::show(Win *win, int i)
{
    if (length) {
        if (isasc) {
            win->attrib(att);
            win->put(i, 0, (char *) atext, length);
        } else {
            win->attrib(0);
            win->put(i, 0, text, length);
        }
    }
    win->attrib(C_ANSIBACK);
    win->clreol(i, length);
}

void AnsiWindow::AnsiLine::unpacktext(char *tmp)
{
    if (isasc) {
        if (length)
            memcpy((unsigned char *) tmp, atext, length);
        tmp += length;
    } else
        for (int i = 0; i < length; i++)
            *tmp++ = (text[i] & (A_CHARTEXT));
    *tmp = '\0';
}

void AnsiWindow::AnsiLine::remapzero(chtype newatt)
{
    if (isasc) {
        if (!PAIR_NUMBER(att & (A_COLOR))) {
            att &= ~(A_COLOR);
            att |= newatt;
        }
    } else
        for (int i = 0; i < length; i++)
            if (!PAIR_NUMBER(text[i] & (A_COLOR))) {
                text[i] &= ~(A_COLOR);
                text[i] |= newatt;
            }
}

//--------------------
// StringFile methods
//--------------------

void StringFile::init(file_header *fromFileA)
{
    fromFile = fromFileA;
    srcStr = 0;
    afile = 0;

    reset();
}

void StringFile::init(letter_body *msgBodyA)
{
    fromFile = 0;
    msgBody = msgBodyFirst = msgBodyA;

    reset();
}

void StringFile::close()
{
    if (fromFile) {
        fclose(afile);
        afile = 0;
    }
}

unsigned char StringFile::nextchar()
{
    int result;

    if (fromFile) {
        result = fgetc(afile);
        if (EOF == result)
            result = 0;
    } else {
        result = *curpos++;

        if (!result && msgBody) {
            msgBody = msgBody->next;
            if (msgBody) {
                srcStr = (const unsigned char *) msgBody->getText();
                curpos = srcStr;
                result = *curpos++;
            }
        }
    }

    return result;
}

void StringFile::backup(int c)
{
    if (fromFile)
        ungetc(c, afile);
    else
        if (curpos > srcStr)
            curpos--;
}

void StringFile::reset()
{
    if (fromFile) {
        if (!afile) {
            afile = mm.workList->ftryopen(fromFile->getName());
            if (!afile)
                fatalError("Error opening ANSI file");
        }
        fseek(afile, 0, SEEK_SET);
    } else {
        if (msgBodyFirst) {
            msgBody = msgBodyFirst;
            srcStr = (const unsigned char *) msgBody->getText();
        }
        curpos = srcStr;
    }
}

bool StringFile::anyleft()
{
    return !(fromFile ? feof(afile) :
           (!*curpos && (!msgBody || !msgBody->next)));
}

//--------------------
// AnsiWindow methods
//--------------------

const int AnsiWindow::ansi_colortable[8] = {COLOR_BLACK, COLOR_RED,
    COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN,
    COLOR_WHITE};

const int AnsiWindow::pc_colortable[8] = {COLOR_BLACK, COLOR_BLUE,
    COLOR_GREEN, COLOR_CYAN, COLOR_RED, COLOR_MAGENTA, COLOR_YELLOW,
    COLOR_WHITE};

void AnsiWindow::Init()
{
#ifdef NCURSES_VERSION
    // Is the "PC" character set available?
    char smpch[] = "smpch";
    char *result = tigetstr(smpch);
    useAltCharset = (result && (result != ((char *) -1)));
#endif
    atparse = 1;
    avtparse = true;
    bsvparse = true;
}

void AnsiWindow::DestroyChain()
{
    while (NumOfLines--)
        delete linelist[NumOfLines];
    delete[] linelist;
}

int AnsiWindow::getparm()
{
    char *parm;
    int value;

    if (escparm[0]) {
        for (parm = escparm; (*parm != ';') && *parm; parm++);

        if (*parm == ';') {             // more params after
            *parm++ = '\0';
            value = (parm == escparm + 1) ? 1 : atoi(escparm);
            strcpy(escparm, parm);
        } else {                        // last parameter
            value = atoi(escparm);
            escparm[0] = '\0';
        }
    } else                              // empty last param
        value = 1;

    return value;
}

void AnsiWindow::cls()
{
    if (anim)
        animtext->Clear(C_ANSIBACK);
    else {
        cpy = NumOfLines - baseline - !cpy;
        checkpos();

        if (baseline < (NumOfLines - 1))
            baseline = NumOfLines - 1;
    }
    posreset();
}

void AnsiWindow::colreset()
{
    cfl = crv = cbr = 0;
    ccf = COLOR_WHITE;
    ccb = COLOR_BLACK;
}

chtype AnsiWindow::colorcore()
{
    // Attribute set

    // Check bold and blinking:

    chtype tmpattrib = (cbr ? A_BOLD : 0) | (cfl ? A_BLINK : 0) |

    // If animating, check for color pair 0 (assumes COLOR_BLACK == 0),
    // and for remapped black-on-black:

        ((!anim || ((ccb | ccf) || !(oldcolorx | oldcolory))) ?
        (crv ? REVERSE(ccf, ccb) : COL(ccf, ccb)) :
        (crv ? REVERSE(oldcolorx, oldcolory) : COL(oldcolorx, oldcolory)));

    // If not animating, mark color pair as used:

    if (!anim)
        colorsused[(ccf << 3) + ccb] = true;

    return tmpattrib;
}

void AnsiWindow::colorset()
{
    int tmp;

    while(escparm[0]) {
        tmp = getparm();
        switch (tmp) {
        case 0:             // reset colors
            colreset();
            break;
        case 1:             // bright
            cbr = 1;
            break;
        case 5:             // flashing
            cfl = 1;
            break;
        case 7:             // reverse
            crv = 1;
            break;
        default:
            if ((tmp > 29) && (tmp < 38))           // foreground
                ccf = ansi_colortable[tmp - 30];
            else if ((tmp > 39) && (tmp < 48))      // background
                ccb = ansi_colortable[tmp - 40];
        }
    }

    attrib = colorcore();
}

void AnsiWindow::pc_colorset(unsigned char c)
{
    cfl = !(!(c & 0x80));  // or should it be cleared for AVATAR?
    cbr = !(!(c & 8));
    crv = 0;

    ccf = pc_colortable[c & 7];
    ccb = pc_colortable[(c & 0x70) >> 4];

    attrib = colorcore();
}

void AnsiWindow::athandle()
{
    static int oldccf = -1, oldccb, oldcbr, oldcfl;
    unsigned fg = 0, bg = 0;
    int result;
    unsigned char c[2], bgc[2], fgc[2];

    bool xmode = false;

    c[0] = source.nextchar();
    c[1] = '\0';

    switch (c[0]) {
    case 'X':
        xmode = true;
        c[0] = source.nextchar();
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
        bgc[0] = c[0];
        bgc[1] = '\0';
        result = sscanf((const char *)bgc, "%x", &bg);

        if (1 == result) {
            fgc[0] = source.nextchar();
            fgc[1] = '\0';
            result = sscanf((const char *)fgc, "%x", &fg);

            if (1 == result) {
                if (!xmode) {
                    c[0] = source.nextchar();
                    result = ('@' == c[0]);
                }
                if (1 == result) {
                    if (1 == atparse) {
                        if (!fg && !bg) {
                            oldccf = ccf;
                            oldccb = ccb;
                            oldcbr = cbr;
                            oldcfl = cfl;
                        } else {
                            if ((15 == fg) && (15 == bg)) {
                                if (-1 != oldccf) {
                                    ccf = oldccf;
                                    ccb = oldccb;
                                    cbr = oldcbr;
                                    cfl = oldcfl;

                                    attrib = colorcore();
                                }
                            } else {
                                cbr = (fg > 7);
                                cfl = (bg > 7);

                                ccf = pc_colortable[fg & 7];
                                ccb = pc_colortable[bg & 7];

                                attrib = colorcore();
                            }
                        }
                    }
                } else {
                    update('@');
                    update(bgc[0]);
                    update(fgc[0]);
                    source.backup(c[0]);
                }
            } else {
                update('@');
                if (xmode)
                    update('X');
                update(bgc[0]);
                source.backup(fgc[0]);
            }
        } else {
            update('@');
            if (xmode)
                update('X');
            source.backup(c[0]);
        }
        break;
    case 'C':
        c[0] = source.nextchar();
        c[1] = source.nextchar();

        if (('L' == c[0]) && ('S' == c[1])) {
            cls();
            c[0] = source.nextchar();
        } else {
            update('@');
            update('C');
            update(c[0]);
            update(c[1]);
        }
        break;
    default:
        source.backup(c[0]);
        update('@');
    }
}

void AnsiWindow::cpylow()
{
    if (cpy < 0)
        cpy = 0;
}

void AnsiWindow::cpyhigh()
{
    if (anim && (cpy > (LINES - 2)))
        cpy = LINES - 2;
}

void AnsiWindow::cpxhigh()
{
    if (cpx > (COLS - 1))
        cpx = COLS - 1;
}

void AnsiWindow::cpxlow()
{
    if (cpx < 0)
        cpx = 0;
}

void AnsiWindow::escfig()
{
    unsigned char a[2];

    a[0] = source.nextchar();
    a[1] = '\0';

    switch (a[0]) {
    case 'A':                   // cursor up
        cpy -= getparm();
        cpylow();
        break;
    case 'B':                   // cursor down
        cpy += getparm();
        cpyhigh();
        break;
    case 'C':                   // cursor right
        cpx += getparm();
        cpxhigh();
        break;
    case 'D':                   // cursor left
        cpx -= getparm();
        cpxlow();
        break;
    case 'J':                   // clear screen
        if (getparm() == 2)
            cls();
        break;
    case 'H':                   // set cursor position
    case 'f':
        cpy = getparm() - 1;
        cpylow(); cpyhigh();
        cpx = getparm() - 1;
        cpxlow(); cpxhigh();
        break;
    case 's':                   // save position
        spx = cpx;
        spy = cpy;
        break;
    case 'u':                   // restore position
        cpx = spx;
        cpy = spy;
        break;
    case 'm':                   // attribute set (colors, etc.)
        colorset();
        break;
    case 'M':                   // eat the music (Kate Bush)
        do
            *a = source.nextchar();
        while ((14 != *a) && ('\n' != *a));
        break;

    // I know this is strange, but I think it beats the alternatives:

    default:
        if ((a[0] >= '0') && (a[0] <= '9'))

    case '=':                   // for some weird mutant codes
    case '?':

    case ';':                   // parameter separator
        {
            strcat(escparm, (const char *)a);
            escfig();
        }
    }
}

void AnsiWindow::avatar()
{
    unsigned char c = source.nextchar();

    switch (c) {
    case 1:                             // attribute set
        pc_colorset(source.nextchar());
        break;
    case 2:                             // blink on
        cfl = 1;
        break;
    case 3:                             // cursor up
        cpy--;
        cpylow();
        break;
    case 4:                             // cursor down
        cpy++;
        cpyhigh();
        break;
    case 5:                             // cursor left
        cpx--;
        cpxlow();
        break;
    case 6:                             // cursor right
        cpx++;
        cpxhigh();
        break;
    case 7:                             // clrtoeol() TODO
        break;
    case 8:                             // set cursor position
        cpy = source.nextchar();
        cpyhigh();
        cpx = source.nextchar();
        cpxhigh();
        break;
    default:
        update(22);
        source.backup(c);
    }
}

void AnsiWindow::posreset()
{
    cpx = cpy = lpy = spx = spy = 0;
}

void AnsiWindow::checkpos()
{
    if (cpy != lpy) {
        curr->pack(chtmp, tlen);

        if (cpy > lpy) {
            for (; lpy != cpy; lpy++)           // move down and
                curr = curr->getnext(true);     // allocate space
        } else
            if (cpy < lpy) {
                for (; lpy != cpy; lpy--)       // move up
                    curr = curr->getprev();
            }

        if ((cpy + 1) > (NumOfLines - baseline))
            NumOfLines = cpy + baseline + 1;

        tlen = curr->unpack(chtmp);
    }
}

void AnsiWindow::update(unsigned char c)
{
    // Characters 0 - 31
    static const chtype lowtrans[] = {
        ' ', '0', '*', 'V', ACS_DIAMOND, ACS_PLUS, ACS_PLMINUS,
        ACS_DEGREE, ACS_BLOCK, 'O', ACS_BLOCK, 'Q', 't', 'd',
        'd', '*', 187, 171, ACS_VLINE, '!', 182, 167, ACS_BULLET,
        ACS_PLMINUS, ACS_UARROW, ACS_DARROW, ACS_LARROW, ACS_RARROW,
        ACS_LLCORNER, ACS_HLINE, ACS_UARROW, ACS_DARROW
    };

    // Characters 176 - 223
    static const chtype acstrans[] = {
        ACS_CKBOARD, ACS_CKBOARD, ACS_CKBOARD, ACS_VLINE, ACS_RTEE,
        ACS_RTEE, ACS_RTEE, ACS_URCORNER, ACS_URCORNER, ACS_RTEE,
        ACS_VLINE, ACS_URCORNER, ACS_LRCORNER, ACS_LRCORNER,
        ACS_LRCORNER, ACS_URCORNER, ACS_LLCORNER, ACS_BTEE, ACS_TTEE,
        ACS_LTEE, ACS_HLINE, ACS_PLUS, ACS_LTEE, ACS_LTEE, ACS_LLCORNER,
        ACS_ULCORNER, ACS_BTEE, ACS_TTEE, ACS_LTEE, ACS_HLINE, ACS_PLUS,
        ACS_BTEE, ACS_BTEE, ACS_TTEE, ACS_TTEE, ACS_LLCORNER,
        ACS_LLCORNER, ACS_ULCORNER, ACS_ULCORNER, ACS_PLUS, ACS_PLUS,
        ACS_LRCORNER, ACS_ULCORNER, ACS_BLOCK,

        // There are no good equivalents for these four:

        '_', '[', ']', '~'
    };

    if (!ansiAbort) {
        chtype ouch, localattrib = attrib;

#ifndef ALLCHARSOK              // unprintable control codes
        switch (c) {            // double musical note
        case 14:
            c = 19;
            break;
        case 15:                // much like an asterisk
            c = '*';
            break;
        case 155:               // ESC + high-bit = slash-o,
            c = 'o';            // except in CP 437
        }
#endif
        if (isoConsole && !isLatin) {
#ifdef NCURSES_VERSION
            if (useAltCharset) {
                ouch = c;
                if (c > 159)
                    ouch |= A_ALTCHARSET;
            } else
#endif
            {
                if ((c > 175) && (c < 224)) {
                    ouch = acstrans[c - 176];

                    // IBM character 219 should map to ACS_BLOCK, but
                    // since that's widely unimplemented, we map it to a
                    // reverse space instead. Note that some terminals
                    // (like PuTTY) would also like cfl and cbr swapped,
                    // but XFree86's xterm prefers otherwise.

                    if (c == 219) {
                        ouch = ' ';

                        crv = !crv;
                        localattrib = colorcore();
                        crv = !crv;
                    }

                    // suppress or'ing of A_ALTCHARSET:
                    c = ' ';

                } else {
                    if (c < 32) {
                        ouch = lowtrans[c];
                        c = ' ';
                    } else {
                        if (c == 127)
                            c = 'A';
                        else
                            if (c & 0x80)
                                c = (unsigned char) dos2isotab[c & 0x7f];
                        ouch = c;
                    }
                }
            }
        } else {
            if (!isoConsole && isLatin)
                if (c & 0x80)
                    c = (unsigned char) iso2dostab[c & 0x7f];
            ouch = c;
        }

        if ((c < ' ') || ((c > 126) && (c < 160)))
            ouch |= A_ALTCHARSET;

        ouch |= localattrib;

        int limit = LINES - 2;

        if (anim) {
            if (cpy > limit) {
                animtext->wscroll(cpy - limit);
                for (int i = cpy - limit; i; i--)
                    animtext->clreol(limit - i + 1, 0);
                cpy = limit;
            }
            animtext->attrib(0);
            animtext->put(cpy, cpx++, ouch);
            animtext->attrib(C_ANSIBACK);
            animtext->update();
            napms(12);
            ansiAbort |= (animtext->keypressed() != ERR);
        } else {
            checkpos();
            chtmp[cpx++] = ouch;
            if (cpx > tlen)
                tlen = cpx;
        }
        if (cpx == COLS) {
            cpx = 0;
            cpy++;
        }
    }
}

void AnsiWindow::ResetChain()
{
    head = new AnsiLine();
    curr = head;
    posreset();
    curr = curr->getnext(true);
    tlen = 0;
    NumOfLines = 1;
    baseline = 0;
}

void AnsiWindow::MakeChain()
{
    unsigned char c;
    unsigned int blen = 1;

    ansiAbort = false;
    if (!anim) {
        ResetChain();
        chtmp = new chtype[COLS];
        curr->unpack(chtmp);
    }
    attrib = C_ANSIBACK;
    colreset();

    source.reset();

    if (bsvparse && isbsv) {
        c = source.nextchar();
        if (0xfd != c) {
            source.backup(c);
            isbsv = false;
        }
        for (blen = 0; blen < 5; blen++)
            c = source.nextchar();
        blen = source.nextchar();
        blen <<= 8;
        blen += c;
    }

    do {
        c = source.nextchar();
#ifdef LIMIT_MEM
        if (maxfreemem() < ((long) NumOfLines * sizeof(AnsiLine *) + 0x200)) {
            c = 0;
            blen = 2;
        }
#endif
        if (bsvparse && isbsv) {
            pc_colorset(source.nextchar());
            if (!c)
                c = ' ';
            update(c);

            blen -= 2;
        } else
            switch (c) {
            case 1:                     // hidden lines (in pos 0)
                if (!cpx) {
                    do
                        c = source.nextchar();
                    while (source.anyleft() && (c != '\n'));
                }
            case 0:
                break;
            case 8:                     // backspace
                if (cpx)
                    cpx--;
                break;
            case 10:
                cpy++;
            case 13:
                cpx = 0;
            case 7:                     // ^G: beep
            case 26:                    // ^Z: EOF for DOS
                break;
            case 12:                    // form feed
                cls();
                break;
            case 22:                    // Main Avatar code
                if (avtparse)
                    avatar();
                else
                    update(c);
                break;
            case 25:                    // Avatar RLE code
                if (avtparse) {
                    unsigned char x;
                    c = source.nextchar();
                    x = source.nextchar();

                    while (x--)
                        update(c);
                } else
                    update(c);
                break;
            case '`':
            case 27:                    // ESC
                c = source.nextchar();
                if ('[' == c) {
                    escparm[0] = '\0';
                    escfig();
                } else {
                    source.backup(c);
                    update('`');
                }
                break;
            case '\t':                  // TAB
                cpx = ((cpx / 8) + 1) * 8;
                while (cpx >= COLS) {
                    cpx -= COLS;
                    cpy++;
                }
                break;
            case '@':
                if (atparse) {
                    athandle();
                    break;
                }
            default:
                update(c);
            }

    } while (c && !ansiAbort && blen);

    if (!anim) {
        curr->pack(chtmp, tlen);
        delete[] chtmp;

        if (!ansiAbort) {
            linelist = new AnsiLine *[NumOfLines];
            curr = head->getnext();
            int i = 0;
            while (curr) {
                linelist[i++] = curr;
                curr = curr->getnext();
            }
        }

        delete head;
    }
}

void AnsiWindow::statupdate(const char *intro)
{
    static const char *helpmsg = "F1 or ? - Help ", *mainstat = " ANSI View";
    char *tmp = new char[COLS + 1];
    const char *pn = mm.resourceObject->get(PacketName);
    bool expert = mm.resourceObject->getInt(ExpertMode);

    int pnlen = strlen(pn);
    if (pnlen > 20)
        pnlen = 20;

    int maxw = COLS - pnlen - (expert ? 16 : 31);
    sprintf(tmp, " %.*s |%s: %-*.*s %s", pnlen, pn, (intro ? intro : mainstat),
            maxw, maxw, title, expert ? "" : helpmsg);

    statbar->cursor_on();
    statbar->put(0, 0, tmp);
    statbar->update();
    statbar->cursor_off();

    delete[] tmp;
}

void AnsiWindow::animate()
{
    animtext = new Win(LINES - 1, COLS, 0, C_ANSIBACK);
    animtext->cursor_on();

    posreset();
    colreset();
    anim = true;

    statupdate(" Animating");

    MakeChain();

    if (!ansiAbort)
        statupdate("      Done");

    anim = false;
    while (!ansiAbort && (animtext->inkey() == ERR));

    animtext->cursor_off();
    delete animtext;

    header->wtouch();
    text->wtouch();
    statupdate();
}

void AnsiWindow::oneLine(int i)
{
    linelist[position + i]->show(text, i);
}

void AnsiWindow::lineCount()
{
    char tmp[44];
    int percent = ((long) position + y) * 100 / NumOfLines;
    if (percent > 100)
        percent = 100;

    sprintf(tmp, "Lines: %6d/%-6d %3d%%", position + 1, NumOfLines, percent);
    header->put(0, COLS - 26, tmp);
    header->delay_update();
}

void AnsiWindow::DrawBody()
{
    lineCount();

    for (int i = 0; i < y; i++)
        if ((position + i) < NumOfLines)
            oneLine(i);
        else
            text->clreol(i, 0);
}

void AnsiWindow::set(letter_body *ansiSource, const char *winTitle,
                     bool latin)
{
    source.init(ansiSource);

    position = 0;
    isLatin = latin;
    title = winTitle;
    isbsv = false;
}

void AnsiWindow::set(file_header *f, const char *winTitle, bool latin)
{
    source.init(f);

    position = 0;
    isLatin = latin;
    title = winTitle;

    const char *fname = f->getName();
    size_t i = strlen(fname);
    isbsv = (i > 4) && !strcasecmp(fname + i - 4, ".bsv");
}

void AnsiWindow::MakeActive()
{
    int i;
    bool end, oldAbort;

    header = new Win(1, COLS, 0, C_LBOTTSTAT);
    text = new Win(LINES - 2, COLS, 1, C_ANSIBACK);
    statbar = new Win(1, COLS, LINES - 1, C_LBOTTSTAT);

    anim = false;
    oldAbort = ansiAbort;

    char *tmp = new char[COLS + 1];
    i = sprintf(tmp, " " MM_TOPHEADER, sysname());
    for (; i < COLS; i++)
        tmp[i] = ' ';
    tmp[i] = '\0';

    header->put(0, 0, tmp);
    header->delay_update();
    delete[] tmp;

    y = LINES - 2;
    x = COLS;

    for (i = 0; i < 64; i++)
        colorsused[i] = false;

    colorsused[PAIR_NUMBER(C_LBOTTSTAT)] = 1;  //don't remap stat bars

    oldcolorx = oldcolory = 0;

    init_pair(((COLOR_WHITE) << 3) + (COLOR_WHITE), COLOR_WHITE, COLOR_WHITE);

    MakeChain();

    // This deals with the unavailability of color pair 0:

    if (colorsused[0]) {    // assumes COLOR_BLACK == 0

        // Find an unused color pair for black-on-black:

        for (end = false, i = 1; i < 64 && !end; i++)
            if (!colorsused[i]) {
                end = true;
                oldcolorx = i >> 3;
                oldcolory = i & 7;
                init_pair(i, COLOR_BLACK, COLOR_BLACK);
            }

        // Remap all instances of color pair 0 to the new pair:

        if (end)
            for (i = 0; i < NumOfLines; i++)
                linelist[i]->remapzero(COL(oldcolorx, oldcolory));
    }
#ifdef PDCURSES
    PDC_set_blink(TRUE);
#endif

    DrawBody();
    text->delay_update();
    statupdate();
    ansiAbort = oldAbort;
}

void AnsiWindow::Delete()
{
    ansiAbort = true;

#ifdef PDCURSES
    PDC_set_blink(FALSE);
#endif
    // Restore remapped color pairs:

    if (oldcolorx + oldcolory)
        init_pair((oldcolorx << 3) + oldcolory, oldcolorx, oldcolory);
    init_pair(((COLOR_WHITE) << 3) + (COLOR_WHITE), COLOR_BLACK, COLOR_BLACK);

    DestroyChain();
    delete header;
    delete text;
    delete statbar;
    source.close();
}

void AnsiWindow::setPos(int n)
{
    position = n;
}

int AnsiWindow::getPos()
{
    return position;
}

searchret AnsiWindow::search(const char *item)
{
    searchret found = False;

    char *buffer = new char[COLS + 1];

    for (int n = position + 1; (n < NumOfLines) && (found == False); n++) {

        if (text->keypressed() == 27) {
            found = Abort;
            break;
        }

        linelist[n]->unpacktext(buffer);
        found = searchstr(buffer, item) ? True : False;

        if (found == True) {
            position = n;
            DrawBody();
            text->delay_update();
        }
    }

    delete[] buffer;
    return found;
}

void AnsiWindow::Save()
{
    FILE *fd;
    static char keepname[128];
    char filename[128], oldfname[128];

    if (keepname[0])
        strcpy(filename, keepname);
    else {
        sprintf(filename, "%.8s.ans", title);
        unspace(filename);
    }

    strcpy(oldfname, filename);

    if (ui->savePrompt("Save to file:", filename)) {
        mychdir(mm.resourceObject->get(SaveDir));
        fd = fopen(homify(filename), "at");
        if (fd) {
            unsigned char c;
            source.reset();
            do {
                c = source.nextchar();
                if (c)
                    fputc(c, fd);
            } while (c);
            fclose(fd);
        } else {
            char tmp[142];

            sprintf(tmp, "%s: Save failed", filename);
            ui->nonFatalError(tmp);
        }
        if (strcmp(filename, oldfname))
            strcpy(keepname, filename);
    } else
        ui->nonFatalError("Save aborted");
}

void AnsiWindow::KeyHandle(int key)
{
    switch (key) {
#ifdef USE_MOUSE
    case MM_MOUSE:
        if (0 == mm_mouse_event.y)
            KeyHandle(KEY_UP);
        else
            if ((LINES - 1) == mm_mouse_event.y)
                KeyHandle(KEY_DOWN);
            else
                if ((mm_mouse_event.y > (LINES >> 1)) || (0 == position))
                    KeyHandle(' ');
                else
                    KeyHandle(KEY_PPAGE);
        break;
#endif
    case MM_UP:
        if (position > 0) {
            position--;
            text->wscroll(-1);
            oneLine(0);
            lineCount();
        }
        break;
    case MM_DOWN:
        if (position < NumOfLines - y) {
            position++;
            text->wscroll(1);
            oneLine(y - 1);
            lineCount();
        }
        break;
    case MM_HOME:
        position = 0;
        DrawBody();
        break;
    case MM_END:
        if (NumOfLines > y) {
            position = NumOfLines - y;
            DrawBody();
        }
        break;
    case 'B':
    case MM_PPAGE:
        position -= ((y < position) ? y : position);
        DrawBody();
        break;
    case ' ':
        position += y;
        if (position < NumOfLines)
            DrawBody();
        else
            ui->setKey('\n');
        break;
    case 'F':
    case MM_NPAGE:
        if (position < NumOfLines - y) {
            position += y;
            if (position > NumOfLines - y)
                position = NumOfLines - y;
            DrawBody();
        }
        break;
    case 'V':
    case 'A':
    case 1:
        animate();
        break;
    case 'S':
        Save();
        DrawBody();
        break;
    case '@':
        atparse++;
        if (3 == atparse)
            atparse = 0;
        ui->redraw();
        break;
    case 22:
        avtparse = !avtparse;
        ui->redraw();
        break;
    case 2:
        bsvparse = !bsvparse;
        ui->redraw();
        break;
    case MM_F1:
    case '?':
        ui->changestate(ansi_help);
    }
    text->delay_update();
}
