/**********
 * Copyright (c) 2003-2004 Greg Parker.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY GREG PARKER ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **********/

#include "includes.h"
#include "vt100.h"
#include "data/prefs.h"
#include "rsrc/rsrc.h"
#include "ssh/ssh.h"

#include "putty.h"
#include "terminal.h"

display *gdd = NULL;

static struct unicode_data *ucsdata;

static int inited = 0;
static FontID NanoFontIDForScreen;
static FontID MediumFontIDForScreen;

static void vt100_set_native_bounds(display *disp, RectangleType bounds) VT100_SEGMENT;
static Boolean read_font_from_prefs(display *disp) VT100_SEGMENT;
static void vt100_read_prefs(display *disp) VT100_SEGMENT;
static void choose_fonts_for_screen(void) VT100_SEGMENT;
FontID font_for_screen(int fontPref) /* NOT VT100_SEGMENT */;
static void set_default_config(Config *cfg) VT100_SEGMENT;

FontID font_for_screen(int fontPref)
{
    UInt32 density;
    Boolean lowres;

    WinScreenGetAttribute(winScreenDensity, &density);

    switch (density) {
    case kDensityDouble:
    case kDensityTriple:
    case kDensityQuadruple:
        lowres = false;
        break;
    case kDensityLow:
    case kDensityOneAndAHalf:
    default:
        lowres = true;
        break;
    }

    switch (fontPref) {
    case font6x10: return lowres ? MediumFontSingleID : MediumFontDoubleID;
    case font4x6: 
    default: return lowres ? NanoFontSingleID : NanoFontDoubleID; 
    }
}

static void choose_fonts_for_screen(void)
{
    vt100_log(__PRETTY_FUNCTION__);
    NanoFontIDForScreen = font_for_screen(font4x6);
    MediumFontIDForScreen = font_for_screen(font6x10);
}

static Boolean read_font_from_prefs(display *disp)
{
    uint32_t value = PrefsGetInt(prefTerminalFont, fontDefault);

    if (value == font4x6  &&  value != disp->fontPref) {
        disp->fontPref = value;
        disp->fontID = NanoFontIDForScreen;
        disp->font_width = 4;
        disp->font_height = 6;
        return true;
    }
    else if (value == font6x10  &&  value != disp->fontPref) {
        disp->fontPref = value;
        disp->fontID = MediumFontIDForScreen;
        disp->font_width = 6; 
        disp->font_height = 10;
        return true;
    }
    else if (disp->fontID == 0) {
        // no font set and pref's value isn't understood - force to default
        disp->fontPref = font4x6;
        disp->fontID = NanoFontIDForScreen;
        disp->font_width = 4;
        disp->font_height = 6;
        return true;
    }
    else {
        // no change
        return false;
    }
}

static short defaultWordness[] = {
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 
    0,1,2,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,
    1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,
    1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,
    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2
};
static void set_default_config(Config *cfg)
{
    strcpy(cfg->host, "");
    cfg->port = 22;
    cfg->protocol = PROT_SSH;
    cfg->close_on_exit = 0;
    cfg->warn_on_close = 0;
    cfg->ping_interval = 0;
    cfg->tcp_nodelay = 1;

    strcpy(cfg->proxy_exclude_list, "");
    cfg->proxy_dns = 1;
    cfg->even_proxy_localhost = 0;
    cfg->proxy_type = PROXY_NONE;
    strcpy(cfg->proxy_host, "");
    cfg->proxy_port = 80;
    strcpy(cfg->proxy_username, "");
    strcpy(cfg->proxy_password, "");
    strcpy(cfg->proxy_telnet_command, "connect %host %port");

    strcpy(cfg->remote_cmd, "");
    strcpy(cfg->remote_cmd2, "");
    cfg->remote_cmd_ptr = cfg->remote_cmd;
    cfg->remote_cmd_ptr2 = cfg->remote_cmd2;
    cfg->nopty = 0;
    cfg->compression = 0;
    cfg->agentfwd = 0;
    cfg->change_username = 0;
    // cfg->ssh_cipherlist
    // cfg->filename
    cfg->sshprot = 2;
    cfg->ssh2_des_cbc = 0;
    cfg->try_tis_auth = 0;
    cfg->try_ki_auth = 0;
    cfg->ssh_subsys = 0;
    cfg->ssh_subsys2 = 0;

    cfg->termtype = arena_strdup("xterm");
    strcpy(cfg->termspeed, "38400,38400");
    strcpy(cfg->environmt, "");
    strcpy(cfg->username, "");
    strcpy(cfg->localusername, "");
    cfg->rfc_environ = 0;
    cfg->passive_telnet = 0;

    cfg->bksp_is_delete = 1;
    cfg->rxvt_homeend = 0;
    cfg->funky_type = 0;
    cfg->no_applic_c = 0;
    cfg->no_applic_k = 0;
    cfg->no_mouse_rep = 0;
    cfg->no_remote_resize = 1;
    cfg->no_alt_screen = 0;
    cfg->no_remote_wintitle = 0;
    cfg->no_dbackspace = 0;
    cfg->no_remote_charset = 0;
    cfg->no_remote_qtitle = 1;
    cfg->app_cursor = 0;
    cfg->app_keypad = 0;
    cfg->nethack_keypad = 0;
    cfg->telnet_keyboard = 0;
    cfg->telnet_newline = 1;
    cfg->alt_f4 = 0;
    cfg->alt_space = 0;
    cfg->alt_only = 0;
    cfg->localecho = 0;
    cfg->localedit = 0;
    cfg->alwaysontop = 1;
    cfg->fullscreenonaltenter = 0;
    cfg->scroll_on_key = defaultScrollDownOnTyping;
    cfg->scroll_on_disp = defaultScrollDownOnActivity;
    cfg->erase_to_scrollback = 1;
    cfg->compose_key = 0;
    cfg->ctrlaltkeys = 1;
    strcpy(cfg->wintitle, "");
    cfg->savelines = defaultScrollbackLines;
    cfg->dec_om = 0;
    cfg->wrap_mode = 1;
    cfg->lfhascr = 0;
    cfg->cursor_type = 0;
    cfg->blink_cur = 0;
    cfg->beep = BELL_AUDIBLE;
    cfg->beep_ind = 0;
    cfg->bellovl = 1;
    cfg->bellovl_n = 5;
    cfg->bellovl_t = 2*TICKSPERSEC;
    cfg->bellovl_s = 5*TICKSPERSEC;
    cfg->scrollbar = 1;
    cfg->scrollbar_in_fullscreen = 0;
    cfg->resize_action = 0;
    cfg->bce = 1;
    cfg->blinktext = 0;
    cfg->win_name_always = 1;
    cfg->width = 80;
    cfg->height = 24;
    // cfg->font fixme;
    cfg->logtype = 0;
    cfg->logxfovr = 0;
    cfg->hide_mouseptr = 0;
    cfg->sunken_edge = 0;
    cfg->window_border = 1;
    strcpy(cfg->answerback, "pssh");
    strcpy(cfg->printer, "");

    cfg->system_colour = 0;
    cfg->try_palette = 0;
    cfg->bold_colour = 1;
    // cfg->colours fixme

    cfg->mouse_is_xterm = 0;
    cfg->rect_select = 0;
    cfg->rawcnp = 0;
    cfg->rtf_paste = 0;
    cfg->mouse_override = 1;
    cfg->wordness = defaultWordness;

    cfg->vtmode = VT_UNICODE;
    strcpy(cfg->line_codepage, "ISO-8859-1");
    cfg->xlat_capslockcyr = 0;
}


// bounds is STANDARD-RESOLUTION
display *vt100_new(struct ssh_session_t *ss, RectangleType bounds) 
{
    display *disp;

    vt100_log(__PRETTY_FUNCTION__);
    if (!inited) {
        // first-time init
        choose_fonts_for_screen();
        inited = 1;
    }

    disp = arena_calloc(sizeof(display));
    gdd = disp;
    disp->ss = ss;
    disp->cfg = arena_calloc(sizeof(struct config_tag));

    palette_reset(disp);
    set_default_config(disp->cfg);
    vt100_read_prefs(disp);

    ucsdata = arena_calloc(sizeof(struct unicode_data));
    init_ucs(ucsdata, DEFAULT_CODEPAGE, disp->cfg->vtmode);

    disp->term = term_init(disp->cfg, ucsdata, disp);
    disp->term->ldisc = disp;
    vt100_set_bounds(disp, bounds);

    return disp;
}

void vt100_free(display *disp) 
{
    term_clrsb(disp->term);
    arena_free(disp);
    gdd = NULL;
}

void vt100_write(display *disp, const char *bytes, size_t len) 
{
    if (len > 0) {
        term_data(disp->term, 0, bytes, len);
        term_out(disp->term);
    }
}


int from_backend(void *frontend, int is_stderr, const char *data, size_t len)
{
    display *disp = (display *)frontend;
    vt100_write(disp, data, len);
    return 0;
}


// trivial ldisc - fixme use putty's instead

void ldisc_send(void *handle, char *buf, ssize_t len, int interactive)
{
    display *disp = (display *)handle;
    if (len == 0) return;

    // len < 0 isn't used in terminal.c

    ssh_bytes(disp->ss, (uint8_t *)buf, len);
}


void lpage_send(void *handle,
		int codepage, char *buf, size_t len, int interactive)
{
    // no codepage support other than ISO 8859-1
    ldisc_send(handle, buf, len, interactive);
}


void luni_send(void *handle, wchar_t * widebuf, size_t len, int interactive)
{
    display *disp = (display *)handle;
    unsigned int ratio = (in_utf(disp->term))?3:1;
    char *linebuffer;
    size_t linesize;
    unsigned int i;
    char *p;

    linesize = len * ratio * 2;
    linebuffer = snewn(linesize, char);

    if (in_utf(disp->term)) {
	/* UTF is a simple algorithm */
	for (p = linebuffer, i = 0; i < len; i++) {
	    wchar_t ch = widebuf[i];
	    /* We only deal with 16-bit wide chars */
	    if ((ch&0xF800) == 0xD800) ch = '.';

	    if (ch < 0x80) {
		*p++ = (char) (ch);
	    } else if (ch < 0x800) {
		*p++ = (0xC0 | (ch >> 6));
		*p++ = (0x80 | (ch & 0x3F));
	    } else {
		*p++ = (0xE0 | (ch >> 12));
		*p++ = (0x80 | ((ch >> 6) & 0x3F));
		*p++ = (0x80 | (ch & 0x3F));
	    }
	}
    } else {
	ssize_t rv;
	rv = wc_to_mb(disp->term->ucsdata->line_codepage, 0, widebuf, len,
		      linebuffer, linesize, NULL, NULL, disp->term->ucsdata);
	if (rv >= 0)
	    p = linebuffer + rv;
	else
	    p = linebuffer;
    }
    if (p > linebuffer)
	ldisc_send(disp, linebuffer, p - linebuffer, interactive);

    sfree(linebuffer);
}



void vt100_update(display *disp)
{
    term_task(disp->term);
    term_update(disp->term);
}

void vt100_task(display *disp)
{
    term_task(disp->term);
}

void vt100_scroll(display *disp, int lines) 
{
    term_scroll(disp->term, 0, lines);
}

void vt100_activate(display *disp) 
{
    disp->inactive = false;
    term_invalidate(disp->term);
}

void vt100_deactivate(display *disp) 
{
    disp->inactive = true;
}

// bounds is NATIVE-RESOLUTION
static void vt100_set_native_bounds(display *disp, RectangleType bounds)
{
    int cw, ch;

    disp->gadgetBounds = bounds;

    cw = disp->gadgetBounds.extent.x / disp->font_width;
    ch = disp->gadgetBounds.extent.y / disp->font_height;

    disp->textBounds.topLeft = disp->gadgetBounds.topLeft;
    disp->textBounds.extent.x = cw * disp->font_width;
    disp->textBounds.extent.y = ch * disp->font_height;
    // fixme center text area inside gadget

    term_size(disp->term, ch, cw, PrefsGetInt(prefScrollbackLines,defaultScrollbackLines));
    
    term_invalidate(disp->term);
}


// bounds is STANDARD-RESOLUTION
void vt100_set_bounds(display *disp, RectangleType bounds)
{
    // scale bounds to native-resolution
    WinPushDrawState();
    WinSetCoordinateSystem(kCoordinatesNative);
    WinScaleRectangle(&bounds); // convert from standard to native
    disp->closeBoxSize = WinScaleCoord(6, false);
    WinPopDrawState();

    vt100_set_native_bounds(disp, bounds);
}


// bounds returned is NATIVE-RESOLUTION
void vt100_size(display *disp, int *charsWide, int *charsHigh, 
                RectangleType *bounds)
{
    if (charsWide) *charsWide = disp->textBounds.extent.x / disp->font_width;
    if (charsHigh) *charsHigh = disp->textBounds.extent.y / disp->font_height;
    if (bounds) *bounds = disp->gadgetBounds;
}


// x and y are STANDARD-RESOLUTION
// clickCount is 1 or more for mouse down, 0 for mouse up
void vt100_click(display *disp, int x, int y, int clickCount)
{
    Coord hix, hiy;
    int charx, chary;
    Mouse_Action action;

    // scale point to hi-res
    WinPushDrawState();
    WinSetCoordinateSystem(kCoordinatesNative);
    hix = WinScaleCoord(x, false); // convert from standard to native
    hiy = WinScaleCoord(y, false); // convert from standard to native
    WinPopDrawState();

    // calculate coordinate
    charx = (hix - disp->textBounds.topLeft.x) / disp->font_width;
    chary = (hiy - disp->textBounds.topLeft.y) / disp->font_height;

    // choose mouse button and mouse action
    switch (clickCount) {
    case -1: action = MA_DRAG; break;
    case 0:  action = MA_RELEASE; break; // mouse up
    case 1:  action = MA_CLICK;   break; // mouse down
    case 2:  action = MA_2CLK;    break; // double-click
    case 3:  action = MA_3CLK;    break; // triple-click
    default: return; // ignore 4+ clicks (fixme?)
    }
    
    // fixme shift/ctrl/alt modifiers?
    // send at least one drag before release
    term_mouse(disp->term, MBT_LEFT, MBT_SELECT, action, charx, chary, 0, 0, 0);
}


void vt100_deselect(display *disp)
{
    term_deselect(disp->term);
}


void vt100_copy(display *disp)
{
    term_copy(disp->term);
}

void vt100_paste(display *disp) 
{
    term_do_paste(disp->term);
}

int vt100_selection_exists(display *disp) 
{
    return term_selection_exists(disp->term);
}

static void vt100_read_prefs(display *disp)
{
    Config *cfg = disp->cfg;

    vt100_log(__PRETTY_FUNCTION__);

    // values used outside cfg
    read_font_from_prefs(disp);
    read_default_colors();

    // values used only in cfg

    if (cfg->termtype) arena_free(cfg->termtype);
    cfg->termtype = PrefsGetString(prefTerminalType, "xterm");

    cfg->scroll_on_key = PrefsGetInt(prefScrollDownOnTyping, defaultScrollDownOnTyping);
    cfg->scroll_on_disp = PrefsGetInt(prefScrollDownOnActivity, defaultScrollDownOnActivity);
    cfg->savelines = PrefsGetInt(prefScrollbackLines, defaultScrollbackLines);

    cfg->beep = 0;
    if (PrefsGetInt(prefBellBeep, defaultBellBeep))  cfg->beep |= BELL_AUDIBLE;
    if (PrefsGetInt(prefBellFlash,defaultBellFlash)) cfg->beep |= BELL_VISIBLE;

    // cfg->width used only in calls to request_resize()
    // cfg->height used only in calls to request_resize()

    if (disp->term) term_reconfig(disp->term, disp->cfg);
}

void vt100_reread_prefs(display *disp) 
{
    vt100_read_prefs(disp);

    // resize and set scrollback
    vt100_set_native_bounds(disp, disp->gadgetBounds);

    // force redraw
    term_invalidate(disp->term);
}

void vt100_seen_key(display *disp)
{
    term_seen_key_event(disp->term);
}

int vt100_app_cursor_keys(display *disp) 
{
    return term_app_cursor_keys(disp->term);
}
