/* c_screen.cpp -- console screen driver

   This file is part of the UPX executable compressor.

   Copyright (C) 1996-2025 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 1996-2025 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);
    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: */
