/* s_djgpp2.cpp -- djggp2 DOS 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) && (ACC_OS_DOS32) && defined(__DJGPP__)
#if (ACC_CC_GNUC >= 0x040300ul)
#pragma GCC diagnostic ignored "-Wvla"
#endif

#include "screen.h"

#define this self

#define mask_fg 0x0f
#define mask_bg 0xf0

/* #define USE_SCROLLBACK 1 */

/*************************************************************************
// direct screen access
**************************************************************************/

#include <dos.h>
#if 0
#include <conio.h>
#endif
#include <dpmi.h>
#include <go32.h>
#include <sys/exceptn.h>
#include <sys/farptr.h>
#include <sys/movedata.h>
#define dossel _go32_info_block.selector_for_linear_memory
#define co80   _go32_info_block.linear_address_of_primary_screen
#undef kbhit

#define Cell upx_uint16_t

struct screen_data_t {
    int mode;
    int cols;
    int rows;
    int cursor_x;
    int cursor_y;
    int scroll_counter;
    unsigned char attr;
    unsigned char init_attr;
    unsigned char empty_attr;
    Cell empty_cell;
#if USE_SCROLLBACK
    /* scrollback buffer */
    Cell sb_buf[32][256];
    int sb_size;
    int sb_base;
    int sb_sp;
#endif /* USE_SCROLLBACK */
};

/* atExit information */
static struct {
    int cursor_shape;
} ae = {-1};

#if USE_SCROLLBACK
static __inline__ void sb_add(screen_t *this, int *val, int inc) {
    *val = (*val + inc) & (this->data->sb_size - 1);
}

static void sb_push(screen_t *this, const Cell *line, int len) {
    memcpy(this->data->sb_buf[this->data->sb_sp], line, len);
    sb_add(this, &this->data->sb_sp, 1);
    if (this->data->sb_sp == this->data->sb_base)
        sb_add(this, &this->data->sb_base, 1);
}

static const Cell *sb_pop(screen_t *this) {
    if (this->data->sb_sp == this->data->sb_base)
        return nullptr;
    sb_add(this, &this->data->sb_sp, -1);
    return this->data->sb_buf[this->data->sb_sp];
}
#endif /* USE_SCROLLBACK */

static void refresh(screen_t *this) { UNUSED(this); }

static __inline__ Cell make_cell(screen_t *this, int ch, int attr) {
    UNUSED(this);
    return (Cell) (((attr & 0xff) << 8) | (ch & 0xff));
}

static int getMode(const screen_t *this) {
    UNUSED(this);
    return ScreenMode();
}

static int getPage(const screen_t *this) {
    UNUSED(this);
    return _farpeekb(dossel, 0x462);
}

static int getRows(const screen_t *this) { return this->data->rows; }

static int getCols(const screen_t *this) { return this->data->cols; }

static int isMono(const screen_t *this) {
    if (this->data->mode == 7)
        return 1;
    if ((_farpeekb(dossel, 0x465) & (4 | 16)) != 0)
        return 1;
    return 0;
}

static int getFg(const screen_t *this) { return this->data->attr & mask_fg; }

static int getBg(const screen_t *this) { return this->data->attr & mask_bg; }

static void setFg(screen_t *this, int fg) {
    this->data->attr = (this->data->attr & mask_bg) | (fg & mask_fg);
}

static void setBg(screen_t *this, int bg) {
    this->data->attr = (this->data->attr & mask_fg) | (bg & mask_bg);
}

static void setCursor(screen_t *this, int x, int y) {
    if (x >= 0 && y >= 0 && x < this->data->cols && y < this->data->rows) {
        ScreenSetCursor(y, x);
        this->data->cursor_x = x;
        this->data->cursor_y = y;
    }
}

/*
// I added ScreenGetCursor, because when upx prints something longer than
// 1 line (an error message for example), the this->data->cursor_y can
// have a bad value - ml1050

// FIXME:
//   Laszlo: when does this happen ? This probably indicates a
//     bug in c_screen.cpp(print0) I've introduced with
//     the 2 passes implementation.
*/

static void getCursor(const screen_t *this, int *x, int *y) {
    int cx = this->data->cursor_x;
    int cy = this->data->cursor_y;
#if 1
    ScreenGetCursor(&cy, &cx);
#endif
    if (x)
        *x = cx;
    if (y)
        *y = cy;
}

static void putCharAttr(screen_t *this, int ch, int attr, int x, int y) {
    UNUSED(this);
    ScreenPutChar(ch, attr, x, y);
}

static void putChar(screen_t *this, int ch, int x, int y) {
    ScreenPutChar(ch, this->data->attr, x, y);
}

static void putStringAttr(screen_t *this, const char *s, int attr, int x, int y) {
    UNUSED(this);
    assert((int) strlen(s) <= 256);
    assert(x + (int) strlen(s) <= this->data->cols);
    ScreenPutString(s, attr, x, y);
}

static void putString(screen_t *this, const char *s, int x, int y) {
    assert((int) strlen(s) <= 256);
    assert(x + (int) strlen(s) <= this->data->cols);
    ScreenPutString(s, this->data->attr, x, y);
}

/* private */
static void getChar(screen_t *this, int *ch, int *attr, int x, int y) {
    UNUSED(this);
    ScreenGetChar(ch, attr, x, y);
}

static int getCursorShape(const screen_t *this) {
    UNUSED(this);
    return _farpeekw(dossel, 0x460);
}

static void setCursorShape(screen_t *this, int shape) {
    __dpmi_regs r;

    memset(&r, 0, sizeof(r)); /* just in case... */
    r.x.ax = 0x0103;
#if 1
    if (this)
        r.h.al = getMode(this); /* required for buggy BIOSes */
#endif
    r.x.cx = shape & 0x7f1f;
    __dpmi_int(0x10, &r);
}

static int hideCursor(screen_t *this) {
    int shape = getCursorShape(this);
    setCursorShape(this, 0x2000);
    return shape;
}

static int init(screen_t *this, int fd) {
    int mode;
    int cols, rows;
    int attr;

#if 0
    /* force linkage of conio.o */
    (void) _conio_kbhit();
#endif

    if (!this || !this->data)
        return -1;

    this->data->mode = -1;
#if USE_SCROLLBACK
    this->data->sb_size = 32;
    this->data->sb_base = 0;
    this->data->sb_sp = 0;
#endif
    if (fd < 0 || !acc_isatty(fd))
        return -1;
    if (getPage(this) != 0)
        return -1;

#if 1 && (ACC_OS_DOS32) && defined(__DJGPP__)
    /* check for Windows NT/2000/XP */
    if (_get_dos_version(1) == 0x0532)
        return -1;
#endif

    cols = ScreenCols();
    rows = ScreenRows();
    mode = getMode(this);
    if (mode > 0x13) {
        /* assume this is some SVGA/VESA text mode */
        __dpmi_regs r;

        memset(&r, 0, sizeof(r)); /* just in case... */
        r.x.ax = 0x4f03;          /* VESA - get current video mode */
        __dpmi_int(0x10, &r);
        if (r.h.ah == 0)
            mode = r.x.bx;
    } else {
        if (mode != 2 && mode != 3 && mode != 7)
            return -1;
    }
    ScreenGetCursor(&this->data->cursor_y, &this->data->cursor_x);
    getChar(this, nullptr, &attr, this->data->cursor_x, this->data->cursor_y);
    this->data->init_attr = attr;
    if (mode != 7) {
        /* Does it normally blink when bg has its 3rd bit set?  */
        int b_mask = (_farpeekb(dossel, 0x465) & 0x20) ? 0x70 : 0xf0;
        attr = attr & (mask_fg | b_mask);
    }
    this->data->mode = mode;
    this->data->cols = cols;
    this->data->rows = rows;
    this->data->attr = attr;
    this->data->empty_attr = attr;
    this->data->empty_cell = make_cell(this, ' ', attr);

    ae.cursor_shape = getCursorShape(this);

    return 0;
}

static void updateLineN(screen_t *this, const void *line, int y, int len) {
    if (y >= 0 && y < this->data->rows && len > 0 && len <= 2 * this->data->cols)
        movedata(_my_ds(), (unsigned) line, dossel, co80 + y * this->data->cols * 2, len);
}

static void clearLine(screen_t *this, int y) {
    if (y >= 0 && y < this->data->rows) {
        unsigned sp = co80 + y * this->data->cols * 2;
        Cell a = this->data->empty_cell;
        int i = this->data->cols;

        _farsetsel(dossel);
        do {
            _farnspokew(sp, a);
            sp += 2;
        } while (--i);
    }
}

static void clear(screen_t *this) {
    unsigned char attr = ScreenAttrib;
    ScreenAttrib = this->data->empty_attr;
    ScreenClear();
    ScreenAttrib = attr;
}

static int scrollUp(screen_t *this, int lines) {
    int sr = this->data->rows;
    int sc = this->data->cols;
    int y;

    if (lines <= 0 || lines > sr)
        return 0;

#if USE_SCROLLBACK
    /* copy to scrollback buffer */
    for (y = 0; y < lines; y++) {
        Cell buf[sc];
        movedata(dossel, co80 + y * this->data->cols * 2, _my_ds(), (unsigned) buf, sizeof(buf));
        sb_push(this, buf, sizeof(buf));
    }
#endif

    /* move screen up */
    if (lines < sr)
        movedata(dossel, co80 + lines * sc * 2, dossel, co80, (sr - lines) * sc * 2);

    /* fill in blank lines at bottom */
    for (y = sr - lines; y < sr; y++)
        clearLine(this, y);

    this->data->scroll_counter += lines;
    return lines;
}

static int scrollDown(screen_t *this, int lines) {
    int sr = this->data->rows;
    int sc = this->data->cols;
    int y;

    if (lines <= 0 || lines > sr)
        return 0;

    /* move screen down */
    if (lines < sr) {
        /* !@#% movedata can't handle overlapping regions... */
        /* movedata(dossel,co80,dossel,co80+lines*sc*2,(sr-lines)*sc*2); */
        Cell buf[(sr - lines) * sc];
        movedata(dossel, co80, _my_ds(), (unsigned) buf, sizeof(buf));
        movedata(_my_ds(), (unsigned) buf, dossel, co80 + lines * sc * 2, sizeof(buf));
    }

    /* copy top lines from scrollback buffer */
    for (y = lines; --y >= 0;) {
#if USE_SCROLLBACK
        const Cell *buf = sb_pop(this);
        if (buf == nullptr)
            clearLine(this, y);
        else
            updateLineN(this, buf, y, sc * 2);
#else
        clearLine(this, y);
#endif
    }

    this->data->scroll_counter -= lines;
    return lines;
}

static int getScrollCounter(const screen_t *this) { return this->data->scroll_counter; }

static int s_kbhit(screen_t *this) {
    UNUSED(this);
    return kbhit();
}

static int intro(screen_t *this, void (*show_frames)(screen_t *)) {
    int shape;
    upx_uint16_t old_flags = __djgpp_hwint_flags;

    if ((this->data->init_attr & mask_bg) != BG_BLACK)
        return 0;

    __djgpp_hwint_flags |= 3;
    while (kbhit())
        (void) getkey();

    shape = hideCursor(this);
    show_frames(this);
    setCursorShape(this, shape);

    while (kbhit())
        (void) getkey();
    __djgpp_hwint_flags = old_flags;

    return 1;
}

static void atExit(void) {
    static int done = 0;
    if (done)
        return;
    done = 1;
    if (ae.cursor_shape >= 0)
        setCursorShape(nullptr, ae.cursor_shape);
}

static const screen_t driver = {sobject_destroy, nullptr, /* finalize */
                                atExit,          init,       refresh,
                                getMode,         getPage,    getRows,
                                getCols,         isMono,     getFg,
                                getBg,           getCursor,  getCursorShape,
                                setFg,           setBg,      setCursor,
                                setCursorShape,  hideCursor, putChar,
                                putCharAttr,     putString,  putStringAttr,
                                clear,           clearLine,  updateLineN,
                                scrollUp,        scrollDown, getScrollCounter,
                                s_kbhit,         intro,      (struct screen_data_t *) nullptr};

/* public constructor */
screen_t *screen_djgpp2_construct(void) { return sobject_construct(&driver, sizeof(*driver.data)); }

#endif /* (USE_SCREEN) && (ACC_OS_DOS32) && defined(__DJGPP__) */

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