/**********
 * Copyright (c) 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 "data/prefs.h"
#include "putty.h"
#include "vt100.h"

// Default color set
// BOLD is more saturated, except for white and black themselves
#define DEFAULT_COLORS \
    {0, 0x00,0x00,0x00}, {0, 0x22,0x22,0x22},  \
    {0, 0xdd,0x00,0x00}, {0, 0xff,0x00,0x00},  \
    {0, 0x00,0xdd,0x00}, {0, 0x00,0xff,0x00},  \
    {0, 0xdd,0xdd,0x00}, {0, 0xff,0xff,0x00},  \
    {0, 0x00,0x00,0xdd}, {0, 0x00,0x00,0xff},  \
    {0, 0xdd,0x00,0xdd}, {0, 0xff,0x00,0xff},  \
    {0, 0x00,0xdd,0xdd}, {0, 0x00,0xff,0xff},  \
    {0, 0xff,0xff,0xff}, {0, 0xdd,0xdd,0xdd},

static RGBColorType defaultColors[16] = {
    DEFAULT_COLORS
};

static RGBColorType colors[16] = {
    DEFAULT_COLORS
};

static unsigned int DefaultForeColor = defaultForeColor;
static unsigned int DefaultBackColor = defaultBackColor;


void read_default_colors(void)
{
    DefaultForeColor = PrefsGetInt(prefTerminalForeColor, defaultForeColor);
    if (DefaultForeColor >= 8) DefaultForeColor = defaultForeColor;
    DefaultBackColor = PrefsGetInt(prefTerminalBackColor, defaultBackColor);
    if (DefaultBackColor >= 8) DefaultBackColor = defaultBackColor;
}

void set_palm_color(int fg_color, int bg_color)
{
    WinSetForeColorRGB(&colors[fg_color], NULL);
    WinSetTextColorRGB(&colors[fg_color], NULL);
    WinSetBackColorRGB(&colors[bg_color], NULL);
}



void set_iconic(void *frontend, int iconic)
{
    // palm: do nothing
}


void move_window(void *frontend, int x, int y)
{
    // palm: do nothing
}


void set_zorder(void *frontend, int top)
{
    // palm: do nothing
}


void refresh_window(void *frontend)
{
    // palm: queue update event for later redraw
    EventType e = {0};
    e.eType = usrDrawVT100Event;
    EvtAddUniqueEventToQueue(&e, 0, true);
}


void set_zoomed(void *frontend, int zoomed)
{
    // palm: do nothing
}


int is_iconic(void *frontend)
{
    // palm: never iconic
    return 0;
}


void get_window_pos(void *frontend, int *x, int *y)
{
    // palm: never moves
    if (x) *x = 0;
    if (y) *y = 0;
}


void get_window_pixels(void *frontend, int *x, int *y)
{
    display *disp = (display *)frontend;

    if (x) *x = disp->textBounds.extent.x;
    if (y) *y = disp->textBounds.extent.y;
}


char *get_window_title(void *frontend, int icon)
{
    // palm: fixme
    return "pssh";
}


void set_raw_mouse_mode(void *frontend, int activate)
{
    // palm: do nothing
}


void request_resize(void *frontend, int w, int h)
{
    // palm: do nothing
}


void palette_set(void *frontend, int n, int r, int g, int b)
{
    // palm: fixme
}


void palette_reset(void *frontend)
{
    int i;
    for (i = 0; i < 16; i++) {
        colors[i] = defaultColors[i];
    }
}


void write_clip(void *frontend, char *data, size_t len, int must_deselect)
{
    display *disp = (display *)frontend;
    char *newdata;
    unsigned int i;
    // convert newlines - Palm uses LF
    // fixme newline conversion might not be necessary
    newdata = snewn(len, char);
    for (i = 0; i < len; i++) {
        if (data[i] == 13) newdata[i] = 10;
        else newdata[i] = (char)data[i];
    }

    ClipboardAddItem(clipboardText, newdata, len);
    sfree(newdata);

    if (must_deselect) term_deselect(disp->term);
}


void request_paste(void *frontend)
{
    // palm: do nothing (only used in response to X11 paste button)
}


void get_clip(void *frontend, wchar_t **p, size_t *len)
{
    MemHandle clipH;
    char *clipP;
    UInt16 clipLen;
    static wchar_t *wclipP = NULL;

    if (p) {
        clipH = ClipboardGetItem(clipboardText, &clipLen);
        *len = clipLen;
        if (clipH) {
            UInt16 i;
            clipP = MemHandleLock(clipH);
            wclipP = snewn(*len, wchar_t);
            for (i = 0; i < *len; i++) {
                wclipP[i] = (wchar_t)clipP[i];
            }
            *p = wclipP;
            MemHandleUnlock(clipH);
        } else {
            // no clipboard to paste
            *p = NULL;
            if (wclipP) sfree(wclipP);
            wclipP = NULL;
        }
    } else {
        // clear previously retrieved clip
        if (wclipP) {
            sfree(wclipP);
            wclipP = NULL;
        }
    }
}


void set_title(void *frontend, char *title)
{
    // palm: fixme do something?
}


void set_icon(void *frontend, char *title)
{
    // palm: fixme do something?
}


void set_sbar(void *frontend, int total, int start, int page)
{
    // palm: queue update event for later redraw
    SetScrollBarEventType e = {0};
    e.eType = usrSetScrollBarEvent;
    e.data.sbar.frontend = frontend;
    e.data.sbar.total = total;
    e.data.sbar.start = start;
    e.data.sbar.page = page;
    EvtAddUniqueEventToQueue((EventPtr)&e, 0, true);
}


void sys_cursor(void *frontend, int x, int y)
{
    // palm: do nothing
}


void beep(void *frontend, int mode)
{
    if (mode & BELL_AUDIBLE) {
        // beep at 400 Hz for 100 ms
        SndCommandType snd;
        UInt16 volume = PrefGetPreference(prefGameSoundVolume);
        snd.cmd = sndCmdFrqOn; // asynchronous
        snd.param1 = 400; // frequency in Hz
        snd.param2 = 100; // duration in ms
        snd.param3 = volume; // volume [0..sndMaxAmp]
        SndDoCmd(NULL, &snd, false);
    }
}


int char_width(Context ctx, unsigned int uc)
{
    // palm: fixme
    return 1;
}


Context get_ctx(void *frontend)
{
    display *disp = (display *)frontend;

    if (disp->inactive) return NULL;

    WinPushDrawState();
    WinSetCoordinateSystem(kCoordinatesNative);

    FntSetFont(disp->fontID);

    return (Context)disp;
}


void free_ctx(Context ctx)
{
    if (!ctx) return;
    WinPopDrawState();
}


static int SectRect(RectangleType a, RectangleType b)
{
    RectangleType c;
    RctGetIntersection(&a, &b, &c);
    return (c.extent.x == 0  ||  c.extent.y == 0);
}


void do_text(Context ctx, int x, int y, char *text, int len, 
             unsigned long attr, unsigned long lattr)
{
#define LEFT(rr) ((rr).topLeft.x)
#define TOP(rr) ((rr).topLeft.y)
#define RIGHT(rr) ((rr).topLeft.x + (rr).extent.x)
#define BOTTOM(rr) ((rr).topLeft.y + (rr).extent.y)

    display *dd = (display *)ctx;

    int fg_color;
    int bg_color;
    int font_width = dd->font_width;
    int font_height = dd->font_height;

    RectangleType r, big, fill;
    RectangleType closeBox;
    int closeBoxInvalid = 0;

    closeBox.topLeft.x = RIGHT(dd->gadgetBounds) - dd->closeBoxSize;
    closeBox.topLeft.y = TOP(dd->gadgetBounds);
    closeBox.extent.x = dd->closeBoxSize;
    closeBox.extent.y = dd->closeBoxSize;

    r.topLeft.x = dd->textBounds.topLeft.x + x * font_width;
    r.topLeft.y = dd->textBounds.topLeft.y + y * font_height;
    r.extent.x = len * font_width;
    r.extent.y = font_height;

    fg_color = (attr & ATTR_FGMASK) >> ATTR_FGSHIFT;
    bg_color = (attr & ATTR_BGMASK) >> ATTR_BGSHIFT;

    // handle VT100 linedraw chars
    // 0x5f maps to space
    // 0x60..0x7e map to 0x01..0x1f
    if ((attr & CSET_MASK) == ATTR_LINEDRW) {
        unsigned char *c, *end;
        for (c = (unsigned char *)text, end = (unsigned char *)text+len; c < end; c++) {
            if (*c == 0x5f) *c = ' ';
            else if (*c >= 0x60  &&  *c <= 0x7e) *c = *c - 0x60 + 0x01;
        }
    }

    // handle default colors
    if (fg_color == (ATTR_DEFFG >> ATTR_FGSHIFT)) fg_color = DefaultForeColor;
    if (bg_color == (ATTR_DEFBG >> ATTR_BGSHIFT)) bg_color = DefaultBackColor;

    // translate into indexes into colors[]
    fg_color *= 2;
    bg_color *= 2;

    if (attr & ATTR_REVERSE) {
        int temp = fg_color;
        fg_color = bg_color;
        bg_color = temp;
    }

    // fixme can do real bold and underline with bigger fonts
    if ((attr & ATTR_BOLD)  ||  (attr & ATTR_UNDER)) {
        fg_color++;
    }
    // fixme allow optional real blink
    if (attr & ATTR_BLINK) {
        bg_color++;
    }

    set_palm_color(fg_color, bg_color);
    WinDrawChars(text, len, r.topLeft.x, r.topLeft.y);
    if (SectRect(r, closeBox)) closeBoxInvalid = 1;

    // If the drawn rect extends to any edge of the text area, fill the 
    // gap between the text area and the gadget bounds with the 
    // background color.
    big = r;
    if (LEFT(r) == LEFT(dd->textBounds)) {
        big.topLeft.x = LEFT(dd->gadgetBounds);
        big.extent.x += LEFT(dd->textBounds) - LEFT(dd->gadgetBounds);
    }
    if (TOP(r) == TOP(dd->textBounds)) {
        big.topLeft.y = TOP(dd->gadgetBounds);
        big.extent.y += TOP(dd->textBounds) - TOP(dd->gadgetBounds);
    }
    if (RIGHT(r) == RIGHT(dd->textBounds)) {
        big.extent.x += RIGHT(dd->gadgetBounds) - RIGHT(dd->textBounds);
    }
    if (BOTTOM(r) == BOTTOM(dd->textBounds)) {
        big.extent.y += BOTTOM(dd->gadgetBounds) - BOTTOM(dd->textBounds);
    }

    // Fill any gaps outside big and inside r
    if (LEFT(big) != LEFT(r)) {
        fill.topLeft.x = LEFT(big);
        fill.topLeft.y = TOP(big);
        fill.extent.x = LEFT(r) - LEFT(big);
        fill.extent.y = big.extent.y;
        WinEraseRectangle(&fill, 0);
        if (SectRect(fill, closeBox)) closeBoxInvalid = 1;
    }
    if (TOP(big) != TOP(r)) {
        fill.topLeft.x = LEFT(big);
        fill.topLeft.y = TOP(big);
        fill.extent.x = big.extent.x;
        fill.extent.y = TOP(r) - TOP(big);
        WinEraseRectangle(&fill, 0);
        if (SectRect(fill, closeBox)) closeBoxInvalid = 1;
    }
    if (RIGHT(big) != RIGHT(r)) {
        fill.topLeft.x = RIGHT(r);
        fill.topLeft.y = TOP(big);
        fill.extent.x = RIGHT(big) - RIGHT(r);
        fill.extent.y = big.extent.y;
        WinEraseRectangle(&fill, 0);
        if (SectRect(fill, closeBox)) closeBoxInvalid = 1;
    }
    if (BOTTOM(big) != BOTTOM(r)) {
        fill.topLeft.x = LEFT(big);
        fill.topLeft.y = BOTTOM(r);
        fill.extent.x = big.extent.x;
        fill.extent.y = BOTTOM(big) - BOTTOM(r);
        WinEraseRectangle(&fill, 0);
        if (SectRect(fill, closeBox)) closeBoxInvalid = 1;
    }

    // cursor
    // fixme better cursor, box cursor, etc
    if (attr & (TATTR_ACTCURS | TATTR_PASCURS | TATTR_RIGHTCURS)) {
        WinInvertRectangle(&r, 0);
    }

    if (closeBoxInvalid) {
        EventType e = {0};
        e.eType = usrDrawCloseBoxEvent;
        EvtAddUniqueEventToQueue(&e, 0, true);
    }
}


void do_cursor(Context ctx, int x, int y, char *text, int len, 
               unsigned long attr, unsigned long lattr)
{
    do_text(ctx, x, y, text, len, attr, lattr);
}


unsigned long getticks(void) 
{
    return TimGetTicks();
}

unsigned long tickspersec(void) 
{
    return SysTicksPerSecond();
}



void ldisc_update(void *frontend, int echo, int edit)
{
}

void frontend_keypress(void *handle)
{
}
