//
// Copyright(C) 2005-2014 Simon Howard
//
// This program is free software; you can redistribute it and/or
// modify it 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.
//

#include <stdlib.h>
#include <string.h>

#include "txt_gui.h"
#include "txt_io.h"
#include "txt_main.h"
#include "txt_utf8.h"

typedef struct txt_cliparea_s txt_cliparea_t;

struct txt_cliparea_s
{
    int x1, x2;
    int y1, y2;
    txt_cliparea_t *next;
};

// Array of border characters for drawing windows. The array looks like this:
//
// +-++
// | ||
// +-++
// +-++

static const int borders[4][4] = 
{
    {0xda, 0xc4, 0xc2, 0xbf},
    {0xb3, ' ',  0xb3, 0xb3},
    {0xc3, 0xc4, 0xc5, 0xb4},
    {0xc0, 0xc4, 0xc1, 0xd9},
};

static txt_cliparea_t *cliparea = NULL;

#define VALID_X(x) ((x) >= cliparea->x1 && (x) < cliparea->x2)
#define VALID_Y(y) ((y) >= cliparea->y1 && (y) < cliparea->y2)

void TXT_DrawDesktopBackground(const char *title)
{
    int i;
    unsigned char *screendata;
    unsigned char *p;

    screendata = TXT_GetScreenData();
    
    // Fill the screen with gradient characters

    p = screendata;
    
    for (i=0; i<TXT_SCREEN_W * TXT_SCREEN_H; ++i)
    {
        *p++ = 0xb1;
        *p++ = TXT_COLOR_GREY | (TXT_COLOR_BLUE << 4);
    }

    // Draw the top and bottom banners

    p = screendata;

    for (i=0; i<TXT_SCREEN_W; ++i)
    {
        *p++ = ' ';
        *p++ = TXT_COLOR_BLACK | (TXT_COLOR_GREY << 4);
    }

    p = screendata + (TXT_SCREEN_H - 1) * TXT_SCREEN_W * 2;

    for (i=0; i<TXT_SCREEN_W; ++i)
    {
        *p++ = ' ';
        *p++ = TXT_COLOR_BLACK | (TXT_COLOR_GREY << 4);
    }

    // Print the title

    TXT_GotoXY(0, 0);
    TXT_FGColor(TXT_COLOR_BLACK);
    TXT_BGColor(TXT_COLOR_GREY, 0);

    TXT_DrawString(" ");
    TXT_DrawString(title);
}

void TXT_DrawShadow(int x, int y, int w, int h)
{
    unsigned char *screendata;
    unsigned char *p;
    int x1, y1;

    screendata = TXT_GetScreenData();

    for (y1=y; y1<y+h; ++y1)
    {
        p = screendata + (y1 * TXT_SCREEN_W + x) * 2;

        for (x1=x; x1<x+w; ++x1)
        {
            if (VALID_X(x1) && VALID_Y(y1))
            {
                p[1] = TXT_COLOR_DARK_GREY;
            }

            p += 2;
        }
    }
}

void TXT_DrawWindowFrame(const char *title, int x, int y, int w, int h)
{
    txt_saved_colors_t colors;
    int x1, y1;
    int bx, by;

    TXT_SaveColors(&colors);
    TXT_FGColor(TXT_COLOR_BRIGHT_CYAN);

    for (y1=y; y1<y+h; ++y1)
    {
        // Select the appropriate row and column in the borders
        // array to pick the appropriate character to draw at
        // this location.
        //
        // Draw a horizontal line on the third line down, so we
        // draw a box around the title.

        by = y1 == y ? 0 :
             y1 == y + 2 && title != NULL ? 2 :
             y1 == y + h - 1 ? 3 : 1;

        for (x1=x; x1<x+w; ++x1)
        {
            bx = x1 == x ? 0 :
                 x1 == x + w - 1 ? 3 : 1;
                 
            if (VALID_X(x1) && VALID_Y(y1))
            {
                TXT_GotoXY(x1, y1);
                TXT_PutChar(borders[by][bx]);
            }
        }
    }

    // Draw the title

    if (title != NULL)
    {
        TXT_GotoXY(x + 1, y + 1);
        TXT_BGColor(TXT_COLOR_GREY, 0);
        TXT_FGColor(TXT_COLOR_BLUE);

        for (x1=0; x1<w-2; ++x1)
        {
            TXT_DrawString(" ");
        }
    
        TXT_GotoXY(x + (w - TXT_UTF8_Strlen(title)) / 2, y + 1);
        TXT_DrawString(title);
    }

    // Draw the window's shadow.

    TXT_DrawShadow(x + 2, y + h, w, 1);
    TXT_DrawShadow(x + w, y + 1, 2, h);

    TXT_RestoreColors(&colors);
}

void TXT_DrawSeparator(int x, int y, int w)
{
    txt_saved_colors_t colors;
    unsigned char *data;
    int x1;
    int b;

    data = TXT_GetScreenData();

    TXT_SaveColors(&colors);
    TXT_FGColor(TXT_COLOR_BRIGHT_CYAN);

    if (!VALID_Y(y))
    {
        return;
    }

    data += (y * TXT_SCREEN_W + x) * 2;

    for (x1=x; x1<x+w; ++x1)
    {
        TXT_GotoXY(x1, y);

        b = x1 == x ? 0 :
            x1 == x + w - 1 ? 3 :
            1;

        if (VALID_X(x1))
        {
            // Read the current value from the screen
            // Check that it matches what the window should look like if
            // there is no separator, then apply the separator

            if (*data == borders[1][b])
            {
                TXT_PutChar(borders[2][b]);
            }
        }

        data += 2;
    }

    TXT_RestoreColors(&colors);
}

// Alternative to TXT_DrawString() where the argument is a "code page
// string" - characters are in native code page format and not UTF-8.
void TXT_DrawCodePageString(const char *s)
{
    int x, y;
    int x1;
    const char *p;

    TXT_GetXY(&x, &y);

    if (VALID_Y(y))
    {
        x1 = x;

        for (p = s; *p != '\0'; ++p)
        {
            if (VALID_X(x1))
            {
                TXT_GotoXY(x1, y);
                TXT_PutChar(*p);
            }

            x1 += 1;
        }
    }

    TXT_GotoXY(x + strlen(s), y);
}

static void PutUnicodeChar(unsigned int c)
{
    int d;

    // Treat control characters specially.
    if (c == '\n' || c == '\b')
    {
        TXT_PutChar(c);
        return;
    }

    // Map Unicode character into the symbol used to represent it in this
    // code page. For unrepresentable characters, print a fallback instead.
    // Note that we use TXT_PutSymbol() here because we just want to do a
    // raw write into the screen buffer.
    d = TXT_UnicodeCharacter(c);

    if (d >= 0)
    {
        TXT_PutSymbol(d);
    }
    else
    {
        TXT_PutSymbol('\xa8');
    }
}

void TXT_DrawString(const char *s)
{
    int x, y;
    int x1;
    const char *p;
    unsigned int c;

    TXT_GetXY(&x, &y);

    if (VALID_Y(y))
    {
        x1 = x;

        for (p = s; *p != '\0'; )
        {
            c = TXT_DecodeUTF8(&p);

            if (c == 0)
            {
                break;
            }

            if (VALID_X(x1))
            {
                TXT_GotoXY(x1, y);
                PutUnicodeChar(c);
            }

            x1 += 1;
        }
    }

    TXT_GotoXY(x + TXT_UTF8_Strlen(s), y);
}

void TXT_DrawHorizScrollbar(int x, int y, int w, int cursor, int range)
{
    txt_saved_colors_t colors;
    int x1;
    int cursor_x;

    if (!VALID_Y(y))
    {
        return;
    }

    TXT_SaveColors(&colors);
    TXT_FGColor(TXT_COLOR_BLACK);
    TXT_BGColor(TXT_COLOR_GREY, 0);

    TXT_GotoXY(x, y);
    TXT_PutChar('\x1b');

    cursor_x = x + 1;

    if (range > 0)
    {
        cursor_x += (cursor * (w - 3)) / range;
    }

    if (cursor_x > x + w - 2)
    {
        cursor_x = x + w - 2;
    }

    for (x1=x+1; x1<x+w-1; ++x1)
    {
        if (VALID_X(x1))
        {
            if (x1 == cursor_x)
            {
                TXT_PutChar('\xdb');
            }
            else
            {
                TXT_PutChar('\xb1');
            }
        }
    }

    TXT_PutChar('\x1a');
    TXT_RestoreColors(&colors);
}

void TXT_DrawVertScrollbar(int x, int y, int h, int cursor, int range)
{
    txt_saved_colors_t colors;
    int y1;
    int cursor_y;

    if (!VALID_X(x))
    {
        return;
    }

    TXT_SaveColors(&colors);
    TXT_FGColor(TXT_COLOR_BLACK);
    TXT_BGColor(TXT_COLOR_GREY, 0);

    TXT_GotoXY(x, y);
    TXT_PutChar('\x18');

    cursor_y = y + 1;

    if (cursor_y > y + h - 2)
    {
        cursor_y = y + h - 2;
    }

    if (range > 0)
    {
        cursor_y += (cursor * (h - 3)) / range;
    }

    for (y1=y+1; y1<y+h-1; ++y1)
    {
        if (VALID_Y(y1))
        {
            TXT_GotoXY(x, y1);

            if (y1 == cursor_y)
            {
                TXT_PutChar('\xdb');
            }
            else
            {
                TXT_PutChar('\xb1');
            }
        }
    }

    TXT_GotoXY(x, y + h - 1);
    TXT_PutChar('\x19');
    TXT_RestoreColors(&colors);
}

void TXT_InitClipArea(void)
{
    if (cliparea == NULL)
    {
        cliparea = malloc(sizeof(txt_cliparea_t));
        cliparea->x1 = 0;
        cliparea->x2 = TXT_SCREEN_W;
        cliparea->y1 = 0;
        cliparea->y2 = TXT_SCREEN_H;
        cliparea->next = NULL;
    }
}

void TXT_PushClipArea(int x1, int x2, int y1, int y2)
{
    txt_cliparea_t *newarea;

    newarea = malloc(sizeof(txt_cliparea_t));

    // Set the new clip area to the intersection of the old
    // area and the new one.

    newarea->x1 = cliparea->x1;
    newarea->x2 = cliparea->x2;
    newarea->y1 = cliparea->y1;
    newarea->y2 = cliparea->y2;

    if (x1 > newarea->x1)
        newarea->x1 = x1;
    if (x2 < newarea->x2)
        newarea->x2 = x2;
    if (y1 > newarea->y1)
        newarea->y1 = y1;
    if (y2 < newarea->y2)
        newarea->y2 = y2;

#if 0
    printf("New scrollable area: %i,%i-%i,%i\n", x1, y1, x2, y2);
#endif

    // Hook into the list

    newarea->next = cliparea;
    cliparea = newarea;
}

void TXT_PopClipArea(void)
{
    txt_cliparea_t *next_cliparea;

    // Never pop the last entry

    if (cliparea->next == NULL)
        return;

    // Unlink the last entry and delete
   
    next_cliparea = cliparea->next;
    free(cliparea);
    cliparea = next_cliparea;
}

