/* s_win32.cpp -- Win32 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>
 */

#define WANT_WINDOWS_LEAN_H 1
#include "../util/system_headers.h"
#if (HAVE_CONIO_H)
#include <conio.h>
#endif
#include "../conf.h"

#if (USE_SCREEN_WIN32)

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

#include "screen.h"

#define this self

#define mask_fg 0x0f
#define mask_bg 0xf0

struct screen_data_t {
    HANDLE hi;
    HANDLE ho;
    CONSOLE_SCREEN_BUFFER_INFO csbi;
#if 0
    char title[512];
#endif

    int mode;
    int cols;
    int rows;
    int cursor_x;
    int cursor_y;
    int scroll_counter;

    WORD attr;
    WORD init_attr;

    CHAR_INFO empty_cell;
    CHAR_INFO empty_line[256];
};

#define P(x) ((SHORT) (x))

static const COORD pos00 = {0, 0};
static const COORD size11 = {1, 1};

/* atExit information */
static struct {
    int is_valid;
    HANDLE ho;
    CONSOLE_CURSOR_INFO cci;
} ae;

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

static int getMode(const screen_t *this) { return this->data->mode; }

static int getPage(const screen_t *this) {
    UNUSED(this);
    return 0;
}

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) {
    UNUSED(this);
    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 = (WORD) ((this->data->attr & mask_bg) | (fg & mask_fg));
    SetConsoleTextAttribute(this->data->ho, this->data->attr);
}

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

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

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
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    if (GetConsoleScreenBufferInfo(this->data->ho, &csbi)) {
        cx = csbi.dwCursorPosition.X;
        cy = csbi.dwCursorPosition.Y;
#if 0
        assert(cx == this->data->cursor_x);
        assert(cy == this->data->cursor_y);
#endif
    }
#endif
    if (x)
        *x = cx;
    if (y)
        *y = cy;
}

static void putCharAttr(screen_t *this, int ch, int attr, int x, int y) {
    CHAR_INFO ci;
    SMALL_RECT region = {P(x), P(y), P(x), P(y)};
    ci.Char.UnicodeChar = 0;
    ci.Char.AsciiChar = (CHAR) ch;
    ci.Attributes = (WORD) attr;
    WriteConsoleOutputA(this->data->ho, &ci, size11, pos00, &region);
}

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

static void putStringAttr(screen_t *this, const char *s, int attr, int x, int y) {
    int i;
    int l = (int) strlen(s);
    if (l <= 0)
        return;
    assert(l <= 256);
    assert(x + l <= this->data->cols);
    CHAR_INFO ci[256];
    COORD size = {P(l), 1};
    SMALL_RECT region = {P(x), P(y), P(x + l - 1), P(y)};
    for (i = 0; i < l; i++) {
        ci[i].Char.UnicodeChar = 0;
        ci[i].Char.AsciiChar = *s++;
        ci[i].Attributes = (WORD) attr;
    }
    WriteConsoleOutputA(this->data->ho, &ci[0], size, pos00, &region);
}

static void putString(screen_t *this, const char *s, int x, int y) {
    this->putStringAttr(this, s, this->data->attr, x, y);
}

/* private */
static int cci2shape(const CONSOLE_CURSOR_INFO *cci) {
    int shape = cci->dwSize & 255;
    if (!cci->bVisible)
        shape |= 0x2000;
    return shape;
}

static int getCursorShape(const screen_t *this) {
    CONSOLE_CURSOR_INFO cci;
    GetConsoleCursorInfo(this->data->ho, &cci);
    return cci2shape(&cci);
}

static void setCursorShape(screen_t *this, int shape) {
    CONSOLE_CURSOR_INFO cci;
    cci.dwSize = shape & 255;
    cci.bVisible = (shape & 0x2000) ? 0 : 1;
    SetConsoleCursorInfo(this->data->ho, &cci);
}

static int hideCursor(screen_t *this) {
    CONSOLE_CURSOR_INFO cci;
    int shape;

    GetConsoleCursorInfo(this->data->ho, &cci);
    shape = cci2shape(&cci);
    if (cci.bVisible) {
        cci.bVisible = 0;
        SetConsoleCursorInfo(this->data->ho, &cci);
    }
    return shape;
}

static int init(screen_t *this, int fd) {
    HANDLE hi, ho;
    CONSOLE_SCREEN_BUFFER_INFO *csbi;
    DWORD mode;
    WORD attr;
    int i;

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

    this->data->hi = INVALID_HANDLE_VALUE;
    this->data->ho = INVALID_HANDLE_VALUE;
    this->data->mode = -1;
    if (fd < 0 || !acc_isatty(fd))
        return -1;

    hi = GetStdHandle(STD_INPUT_HANDLE);
    ho = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hi == INVALID_HANDLE_VALUE || ho == INVALID_HANDLE_VALUE)
        return -1;
    if (!GetConsoleMode(ho, &mode))
        return -1;
    csbi = &this->data->csbi;
    if (!GetConsoleScreenBufferInfo(ho, csbi))
        return -1;
    if (!GetConsoleCursorInfo(ho, &ae.cci))
        return -1;
#if 0
    if (!GetConsoleTitle(this->data->title, sizeof(this->data->title)))
        return -1;
#endif

#if 0
    this->data->cols = csbi->srWindow.Right - csbi->srWindow.Left + 1;
    this->data->rows = csbi->srWindow.Bottom - csbi->srWindow.Top + 1;
    if (csbi->srWindow.Left != 0 || csbi->srWindow.Top != 0)
        return -1;
    if (this->data->cols != csbi->dwSize.X)
        return -1;
#else
    this->data->cols = csbi->dwSize.X;
    this->data->rows = csbi->dwSize.Y;
#if 0
    if (csbi->srWindow.Left != 0)
        return -1;
#endif
#endif

#if 1
    // check if we are running under Wine
    // (this rather direct Win32 screen driver maps poorly to Unix terminals)
    // ntdll.dll wine_get_version() was added in wine-0.9.59 (Apr 2008)
    HMODULE ntdll = GetModuleHandleA("ntdll.dll");
    if (ntdll != nullptr) {
        typedef const char *(CDECL * wine_get_version_func_t)(void);
        wine_get_version_func_t wine_get_version;
        wine_get_version =
            (wine_get_version_func_t) (void *) GetProcAddress(ntdll, "wine_get_version");
        if (wine_get_version != nullptr) {
            return -1;
        }
    }
#endif

    this->data->cursor_x = csbi->dwCursorPosition.X;
    this->data->cursor_y = csbi->dwCursorPosition.Y;

    ae.ho = ho;
    ae.is_valid = 1;

    attr = csbi->wAttributes;
    this->data->hi = hi;
    this->data->ho = ho;
    this->data->mode = 3; // ???
    this->data->attr = attr;
    this->data->init_attr = attr;
    this->data->empty_cell.Char.UnicodeChar = 0;
    this->data->empty_cell.Char.AsciiChar = ' ';
    this->data->empty_cell.Attributes = attr;
    for (i = 0; i < 256; i++)
        this->data->empty_line[i] = this->data->empty_cell;

    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) {
#if 0
        const char *s = (const char *) line;
        int l = len / 2;
        int i;

        assert(l <= 256);
        CHAR_INFO ci[256];
        COORD size = { P(l), 1 };
        SMALL_RECT region = { 0, P(y), P(0 + l - 1), P(y) };
        for (i = 0; i < l; i++)
        {
            ci[i].Char.UnicodeChar = 0;
            ci[i].Char.AsciiChar = *s++;
            ci[i].Attributes = *s++;
        }
        WriteConsoleOutputA(this->data->ho, &ci[0], size, pos00, &region);
#endif
        UNUSED(line);
    }
}

static void clearLine(screen_t *this, int y) {
    if (y >= 0 && y < this->data->rows) {
        COORD size = {P(this->data->cols), 1};
        SMALL_RECT region = {0, P(y), P(this->data->cols - 1), P(y)};
        WriteConsoleOutputA(this->data->ho, this->data->empty_line, size, pos00, &region);
    }
}

static void clear(screen_t *this) {
    int y;
    for (y = 0; y < this->data->rows; y++)
        this->clearLine(this, y);
}

/* private */
static int do_scroll(screen_t *this, int lines, int way) {
    if (lines <= 0 || lines > this->data->rows)
        return 0;
    if (lines == this->data->rows) {
        this->clear(this);
        return lines;
    }

    SMALL_RECT rect = {0, 0, P(this->data->cols - 1), P(this->data->rows - 1)};
    // SMALL_RECT clip = rect;
    COORD dest = {0, 0};
    switch (way) {
    case 0:
        rect.Top = P(rect.Top + lines);
        break;
    case 1:
        rect.Bottom = P(rect.Bottom - lines);
        dest.Y = P(dest.Y + lines);
        break;
    }
    // ScrollConsoleScreenBuffer(this->data->ho, &rect, &clip, dest, &this->data->empty_cell);
    ScrollConsoleScreenBuffer(this->data->ho, &rect, nullptr, dest, &this->data->empty_cell);

    return lines;
}

static int scrollUp(screen_t *this, int lines) {
    lines = do_scroll(this, lines, 0);
    this->data->scroll_counter += lines;
    return lines;
}

static int scrollDown(screen_t *this, int lines) {
    lines = do_scroll(this, lines, 1);
    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);
#if (HAVE_CONIO_H)
#if defined(__RSXNT__)
    return 0;
#elif defined(__BORLANDC__) || defined(__WATCOMC__)
    return kbhit();
#else
    return _kbhit();
#endif
#else
    return 0;
#endif
}

static int intro(screen_t *this, void (*show_frames)(screen_t *)) {
    UNUSED(this);
    UNUSED(show_frames);
    return 0;
}

static void atExit(void) {
    static int done = 0;
    if (done)
        return;
    done = 1;
    if (ae.is_valid) {
    }
}

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_win32_construct(void) { return sobject_construct(&driver, sizeof(*driver.data)); }

#endif /* (USE_SCREEN_WIN32) */

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