/* c_screen.cpp -- console screen driver

   This file is part of the UPX executable compressor.

   Copyright (C) Markus Franz Xaver Johannes Oberhumer
   Copyright (C) Laszlo Molnar
   All Rights Reserved.

   UPX and the UCL library are free software; you can redistribute them
   and/or modify them under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of
   the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; see the file COPYING.
   If not, write to the Free Software Foundation, Inc.,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   Markus F.X.J. Oberhumer              Laszlo Molnar
   <markus@oberhumer.com>               <ezerotven+github@gmail.com>
 */

#include "../conf.h"

#if (USE_SCREEN)

#include "screen.h"

#define mask_fg 0x0f
#define mask_bg 0xf0

/*************************************************************************
//
**************************************************************************/

static int do_init(screen_t *s, int fd) {
    int fg, bg;

    if (s->init(s, fd) != 0)
        return -1;

    if (s->getCols(s) < 80 || s->getCols(s) > 256)
        return -1;
    if (s->getRows(s) < 24)
        return -1;

    fg = s->getFg(s);
    bg = s->getBg(s);
    if (s->isMono(s))
        fg = -1;
    if (fg == (bg >> 4))
        return -1;
    if (bg != BG_BLACK)
        if (!s->isMono(s)) {
            /* return 0; */ /* we could emulate ANSI mono */
            return -1;
        }

    return 0;
}

static screen_t *do_construct(screen_t *s, int fd) {
    if (!s)
        return nullptr;
    if (do_init(s, fd) != 0) {
        s->destroy(s);
        return nullptr;
    }
    return s;
}

/*************************************************************************
//
**************************************************************************/

static screen_t *screen = nullptr;

static void __acc_cdecl_atexit do_destroy(void) {
    if (screen) {
        if (screen->atExit)
            screen->atExit();
        screen->destroy(screen);
        screen = nullptr;
    }
}

static int mode = -1;
static int init_fg = -1;
static int init_bg = -1;
static int cur_fg = -1;
static int cur_bg = -1;

static int init(FILE *f, int o, int now) {
    int fd = fileno(f);
    int n;

    UNUSED(now);
    assert(screen == nullptr);

    if (o == CON_SCREEN)
        n = CON_SCREEN;
    else if (o == CON_INIT) /* use by default */
        n = CON_SCREEN;
    else if (o == CON_ANSI_COLOR) /* can emulate ANSI color */
        n = CON_ANSI_COLOR;
    else if (o == CON_ANSI_MONO) /* can emulate ANSI mono */
        n = CON_ANSI_MONO;
    else
        return CON_INIT;

#if (ACC_OS_DOS32) && defined(__DJGPP__)
    if (!screen)
        screen = do_construct(screen_djgpp2_construct(), fd);
#endif
#if (USE_SCREEN_WIN32)
    if (!screen)
        screen = do_construct(screen_win32_construct(), fd);
#endif
#if (USE_SCREEN_VCSA)
    if (!screen)
        screen = do_construct(screen_vcsa_construct(), fd);
#endif
#if (USE_SCREEN_CURSES)
    if (!screen && o == CON_SCREEN)
        screen = do_construct(screen_curses_construct(), fd);
#endif
    if (!screen)
        return CON_INIT;

    mode = screen->getMode(screen);
    UNUSED(mode);
    init_fg = cur_fg = screen->getFg(screen);
    init_bg = cur_bg = screen->getBg(screen);
    if (screen->isMono(screen))
        cur_fg = -1;

    atexit(do_destroy);
    return n;
}

static int set_fg(FILE *f, int fg) {
    const int last_fg = cur_fg;
    int f1 = fg & mask_fg;
    int f2 = init_fg & mask_fg;

    UNUSED(f);
    cur_fg = fg;
    if (screen->isMono(screen)) {
        const int b = (init_bg & mask_bg) >> 4;
        if (fg == -1) /* restore startup fg */
            f1 = f2;
        else if (b == 0)
            f1 = (f2 <= 8) ? 15 : 8;
        else if (b <= 8)
            f1 = (f2 == 0) ? 15 : 0;
        else
            f1 = (f2 == 0) ? 8 : 0;
    } else if (con_mode == CON_ANSI_MONO && f1 != f2) {
        f1 = f2 ^ 0x08;
    }

    screen->setFg(screen, f1 & mask_fg);
    return last_fg;
}

static void print0(FILE *f, const char *ss) {
    int cx, cy;
    int old_cx = 0, old_cy = 0;
    const int sx = screen->getCols(screen);
    const int sy = screen->getRows(screen);
    int pass;

    // Note:
    //   We use 2 passes to avoid unnecessary system calls because
    //   scrollUp() under Win32 is *extremely* slow.
    UNUSED(f);

    screen->getCursor(screen, &old_cx, &old_cy);
    cx = old_cx;
    cy = old_cy;

    for (pass = 0; pass < 2; pass++) {
        const char *s = ss;
        // char buffer for pass 2
        char p[256 + 1];
        int pi = 0, px = 0, py = 0;

        for (;;) {
            // walk over whitespace
            for (;;) {
                if (*s == '\n') {
                    cx = 0;
                    cy++;
                } else if (*s == '\r') {
                    cx = 0;
                    if (pass > 0 && cy < sy)
                        screen->clearLine(screen, cy);
                } else
                    break;
                s++;
            }
            // adjust cursor
            if (cx >= sx) {
                cx = 0;
                cy++;
            }
            if (pass > 0) {
                // check if we should print something
                if (pi > 0 && (*s == 0 || py != cy)) {
                    p[pi] = 0;
                    screen->putString(screen, p, px, py);
                    pi = 0;
                }
                // check if we should scroll even more (this can happen
                // if the string is longer than sy lines)
                if (cy >= sy) {
                    int scroll_y = cy - sy + 1;
                    screen->scrollUp(screen, scroll_y);
                    cy -= scroll_y;
                    if (cy < 0)
                        cy = 0;
                }
            }
            // done ?
            if (*s == 0)
                break;
            if (pass > 0) {
                // store current char
                if (pi == 0) {
                    px = cx;
                    py = cy;
                }
                p[pi++] = *s;
            }
            // advance
            cx++;
            s++;
        }

        if (pass == 0) {
            // end of pass 1 - scroll up, restore cursor
            if (cy >= sy) {
                int scroll_y = cy - sy + 1;
                screen->scrollUp(screen, scroll_y);
                cy = old_cy - scroll_y;
                if (cy < 0)
                    cy = 0;
            } else
                cy = old_cy;
            cx = old_cx;
        }
    }

    screen->setCursor(screen, cx, cy);
    screen->refresh(screen);
}

static bool intro(FILE *f) {
    UNUSED(f);
#if (USE_FRAMES)
    if (screen->intro)
        return screen->intro(screen, screen_show_frames);
#endif
    return 0;
}

console_t console_screen = {init, set_fg, print0, intro};

#endif /* USE_SCREEN */

/* vim:set ts=4 sw=4 et: */
