/*
 * screen.c
 *
 * Generic screen manipulation routines. Most of these routines call the machine
 * specific routines to do the actual work.
 *
 */

#include <stdio.h>

#include "ztypes.h"
#include "screen.h"
#include "screenio.h"
#include "text.h"
#include "osdepend.h"
#include "operand.h"
#include "memory.h"
#include "v6.h"
#include "v6ro.h"

int font = 1;
int fixed_space_bit = OFF;
int screen_window = TEXT_WINDOW;
int status_size = 0;

static zword_t z_palette[256] =
{
    (zword_t) TCOL_CURRENT,
    (zword_t) TCOL_DEFAULT,
    0x0000,
    0x001D,
    0x0340,
    0x03BD,
    0x59A0,
    0x7C1F,
    0x77A0,
    0x7FFF,
    0x5AD6,
    0x4631,
    0x2D6B,
    0x8000,
    0x8000,
    (zword_t) TCOL_TRANSPARENT
};

static int next_z_colour = 16;

int z_true_colour_to_z_colour(zword_t c)
{
    int i;

    for (i=0; i<256; i++)
        if (z_palette[i] == c)
            return i;

    if (c==(zword_t) TCOL_SAMPLE) return COL_SAMPLE;

    if (c & 0x8000) return 14;

    i = next_z_colour;
    z_palette[next_z_colour++] = c;
    // Follow Z-Spec 11 recommendation to keep first colours distinct.
    if (next_z_colour == 256) next_z_colour = 240;

    return i;
}

zword_t z_colour_to_true_colour(int c)
{
    if (c >= 0 && c < 256)
        return z_palette[c];

    if (c == COL_SAMPLE) return (zword_t) TCOL_SAMPLE;

    return 0x8000;
}

/*
 * set_window
 *
 * Put the cursor in the text or status window. The cursor is free to move in
 * the status window, but is fixed to the input line in the text window.
 *
 */

void z_set_window(unsigned w)
{
    flush_buffer();

    screen_window = w;

    if (h_type==V6)
    {
        v6ro_select_window(w);
    	/*v6ro_set_colours((wind_prop[w][WPROP_COLOUR_DATA]&0xFF)-2,
                     	(wind_prop[w][WPROP_COLOUR_DATA]>>8)-2, w);*/
    	v6ro_move_cursor(wind_prop[w][WPROP_Y_CURSOR], wind_prop[w][WPROP_X_CURSOR]);
        buffering=wind_prop[w][WPROP_ATTRIBUTES] & WATTR_BUFFER ? ON : OFF;
        wrapping=wind_prop[w][WPROP_ATTRIBUTES] & WATTR_WRAPPING ? ON : OFF;
        scripting_disable=wind_prop[w][WPROP_ATTRIBUTES] & WATTR_SCRIPT ? OFF : ON;
        font=wind_prop[w][WPROP_FONT_NUMBER];
        v6_update_char_size();
        return;
    }

    if (screen_window == STATUS_WINDOW)
    {
        /* Status window: disable buffering and select status window */

        scripting_disable = ON;
        select_status_window();

        /* Put cursor at top of status area */

        if (h_type < V4)
            move_cursor(2, 1);
        else
            move_cursor(1, 1);

    }
    else
    {
        /* Text window: enable buffering and select text window */

        select_text_window ();
        scripting_disable = OFF;
    }

}/* set_window */

/*
 * split_window
 *
 * Set the size of the status window. The default size for the status window is
 * zero lines for both type 3 and 4 games. The status line is handled specially
 * for type 3 games and always occurs the line immediately above the status
 * window.
 *
 */

void z_split_window(unsigned lines)
{
    if (h_type==V6)
    {
        v6z_split_window(lines);
        return;
    }

    /* The top line is always set for V1 to V3 games, so account for it here. */

    if (h_type < V4)
        lines++;

    #if 1
    /* Bound the status size to the total screen height */
    if (lines > h_screen_rows)
        status_size = h_screen_rows;
    else
        status_size = lines;
    #else
    /* Bound the status size to one line less than the total screen height */
    if (lines > (zword_t) (h_screen_rows - 1))
        status_size = (zword_t) (h_screen_rows - 1);
    else
        status_size = lines;
    #endif

    if (lines)
    {
        /* Create the status window, or resize it */

        create_status_window ();

        /* Need to clear the status window for type 3 games */

        if (h_type < V4)
            z_erase_window(STATUS_WINDOW);

    }
    else
    {
        /* Reset the lines written counter and status size */

        /*lines_written = 0;*/

        /* Delete the status window */

        /*delete_status_window ();*/

        /* Return cursor to text window */

        z_set_window(TEXT_WINDOW);
    }

}/* split_window */

/*
 * erase_window
 *
 * Clear one or all windows on the screen.
 *
 */

void z_erase_window(short w)
{
    flush_buffer();

    if (h_type==V6)
    {
        v6z_erase_window(w);
        return;
    }

    switch (w)
    {
      case -1:
        /* -1 - clear screen, unsplit etc. */
    	z_split_window(0);   /* Unsplits screen */
    	clear_text_window();
    	lines_written=0;
    	break;

      case -2:
        /* -2 - clear screen, don't change anything */
       	clear_screen();
    	lines_written=0;
    	break;

      case 0:
        clear_text_window();
        lines_written=0;
        break;

      case 1:
        clear_status_window();
        break;
    }

}/* erase_window */

/*
 * erase_line
 *
 * Clear one line on the screen.
 *
 */

void z_erase_line(unsigned int flag)
{
    flush_buffer();

    if (h_type==V6)
    	v6ro_clear_line(flag);
    else
    	clear_line();

}/* erase_line */

/*
 * get_cursor
 *
 * Get the cursor position in the status window only.
 *
 */

void z_get_cursor(int table)
{
    int row, col;

    if (h_type==V6)
    {
        v6z_get_cursor(table);
        return;
    }

    /* Can only get cursor if in status window */

    if (screen_window == STATUS_WINDOW)
    {
        get_cursor_position(&row, &col);
        set_word(table, row);
        set_word(table+2, col);
    }

}/* get_cursor */

/*
 * set_cursor
 *
 * Set the cursor position in the status window only.
 *
 */

void z_set_cursor(int argc, unsigned *argv)
{
    int row, column;

    if (h_type<V4)
    {
        warn_bad_use("set_cursor");
        return;
    }

    if (h_type==V6)
    {
        v6z_set_cursor(argc, argv);
        return;
    }

    row=argv[0];
    column=argv[1];

    /* Can only move cursor if in status window */

    if (screen_window == STATUS_WINDOW)
    {
        static int warned;
        int bad = 0;

        if (column < 1) bad = 1, column = 1;
        else if (column > h_screen_width) bad = 1, column = h_screen_width;
        if (row < 1) bad = 1, row = 1;
        else if (row > h_screen_height) bad = 1, row = h_screen_height;
        if (row > status_size) bad = 1, z_split_window(row);
        if (bad && !warned) warned = 1, warning_lookup("BadCursor");
        if (buffering == ON)
            flush_buffer();
        move_cursor (row, column);
    }

}/* set_cursor */

/*
 * pad_line
 *
 * Pad the status line with spaces up to a column position.
 *
 */

static void pad_line (int column)
{
    int i;

    for (i = status_pos; i < column; i++)
        write_char(' ');
    status_pos = column;

}/* pad_line */

/*
 * show_status
 *
 * Format and output the status line for type 3 games only.
 *
 */

void z_show_status(void)
{
    int i, count = 0, end_of_string[3];
    char *status_part[3];

    /* Move the cursor to the top line of the status window, set the reverse
       rendition and print the status line */

    z_set_window(STATUS_WINDOW);
    move_cursor(1, 1);
    z_set_text_style(REVERSE);

    /* Redirect output to the status line buffer */

    z_output_stream(2, 3, 0, 0);

    /* Print the object description for global variable 16 */

    pad_line(1);
    status_part[count] = &status_line[status_pos];
    if (load_variable(16) != 0)
        z_print_obj(load_variable(16));
    end_of_string[count++] = status_pos;
    status_line[status_pos++] = '\0';

    if (get_byte_priv(H_CONFIG) & CONFIG_TIME)
    {
        /* If a time display print the hours and minutes from global
           variables 17 and 18 */

        pad_line(h_screen_cols - 21);
        status_part[count] = &status_line[status_pos];
        write_string(msgs_lookup("Time"));
        print_time(load_variable(17), load_variable(18));
        end_of_string[count++] = status_pos;
        status_line[status_pos++] = '\0';
    }
    else
    {
        /* If a moves/score display print the score and moves from global
           variables 17 and 18 */

        pad_line(h_screen_cols - 31);
        status_part[count] = &status_line[status_pos];
        write_string(msgs_lookup("Score"));
        z_print_num(load_variable(17));
        end_of_string[count++] = status_pos;
        status_line[status_pos++] = '\0';

        pad_line(h_screen_cols - 15);
        status_part[count] = &status_line[status_pos];
        write_string(msgs_lookup("Moves"));
        z_print_num(load_variable(18));
        end_of_string[count++] = status_pos;
        status_line[status_pos++] = '\0';
    }

    /* Pad the end of status line with spaces then disable output redirection */

    pad_line(h_screen_cols);
    z_output_stream(2, -3, 0, 0);

    /* Try and print the status line for a proportional font screen. If this
       fails then remove embedded nulls in status line buffer and just output
       it to the screen */

    /*if (print_status(count, status_part) == FALSE)
    {*/
        for (i = 0; i < count; i++)
            status_line[end_of_string[i]] = ' ';
        status_line[status_pos] = '\0';
        write_string(status_line);
    /*}*/

    z_set_text_style(NORMAL);
    z_set_window(TEXT_WINDOW);

}/* z_show_status */

/*
 * blank_status_line
 *
 * Output a blank status line for type 3 games only.
 *
 */

void blank_status_line(void)
{
    /* Move the cursor to the top line of the status window, set the reverse
       rendition and print the status line */

    z_set_window(STATUS_WINDOW);
    move_cursor(1, 1);
    set_attribute(REVERSE);

    /* Redirect output to the status line buffer and pad the status line with
       spaces then disable output redirection */

    z_output_stream(2, 3, 0, 0);
    pad_line (h_screen_cols);
    status_line[status_pos] = '\0';
    z_output_stream(2, -3, 0, 0);

    /* Write the status line */

    write_string(status_line);

    /* Turn off attributes and return to text window */

    set_attribute(NORMAL);
    z_set_window(TEXT_WINDOW);

}/* blank_status_line */

/*
 * output_string
 *
 * Output a string of characters.
 *
 */

void output_string(const zword_t *s)
{
    int i;
    if (h_type!=V6)
    {
    	while (*s)
            output_char(*s++);
        return;
    }

    if (!output_stream_1)
    	return;

    while (*s)
    {
        i=0;
        while (s[i]>=32)
            i++;

        if (i)
            v6ro_display_string(s, i);
        s+=i;

        while (*s>0 && *s<32)
        {
            if (*s >= EMBED_STYLE && *s <= EMBED_STYLE+MAX_ATTRIBUTE)
                set_attribute(*s-EMBED_STYLE);
            else if (*s == 13)
                v6ro_scroll_line();
            else if (*s >= EMBED_FONT+1 && *s <= EMBED_FONT+4)
                set_font(*s-EMBED_FONT);
            else if (*s == EMBED_FIXED+0)
                fixed_space_bit=FALSE;
            else if (*s == EMBED_FIXED+1) //{
                fixed_space_bit=TRUE;
            //warning("Fixed on"); }
            s++;
        }
    }
}

/*
 * output_line
 *
 * Output a string of characters followed by a new line.
 *
 */

void output_line(const zword_t *s)
{
    output_string(s);
    output_new_line();

}/* output_line */

/*
 * output_char
 *
 * Output a character and rendition selection. This routine also handles
 * selecting rendition attributes such as bolding and reverse. There are
 * five attributes distinguished by a bit mask. 0 means turn all attributes
 * off. The attributes are: 1 = reverse, 2 = bold, 4 = emphasis, and
 * 8 = fixed font.
 *
 */

void output_char(int c)
{
    /* If output is enabled then either select the rendition attribute
       or just display the character */

    if (output_stream_1==ON)
    {
        /* Attribute selection */

        if (c >= 32)
            display_char(c);
        else if (c >= EMBED_STYLE && c <= EMBED_STYLE+MAX_ATTRIBUTE)
            set_attribute(c-EMBED_STYLE);
        else if (c >= EMBED_FONT+1 && c <= EMBED_FONT+4)
            set_font(c-EMBED_FONT);
        else if (c == EMBED_FIXED+0)
            fixed_space_bit=FALSE;
        else if (c == EMBED_FIXED+1)
        //{
            fixed_space_bit=TRUE;
        //    warning("Fixed on");
        //}
        else
            display_char(c);
    }

}/* output_char */

/*
 * output_new_line
 *
 * Scroll the text window up one line and pause the window if it is full.
 *
 */

void output_new_line(void)
{
    int row, col;

    if (h_type==V6)
    {
        v6_output_new_line();
        return;
    }

    /* Don't print if output is disabled or replaying commands */

    if (output_stream_1==ON)
    {
        if (/*buffering == ON && */screen_window == TEXT_WINDOW)
        {
            /* If this is the text window then scroll it up one line */

            scroll_line();

            /* See if we have filled the screen. The spare line is for the [MORE] message */

            if (++lines_written >= (h_screen_rows - status_size - 1))
            {
                /* Display the new status line while the screen is paused */

                if (h_type < V4)
                    z_show_status();

                /* Reset the line count and display the more message */

                lines_written = 0;

                if (replaying == OFF)
                {
                    get_cursor_position(&row, &col);
                    output_string(msgs_lookup_u("More"));
                    input_character(0);
                    move_cursor(row, col);
                    clear_line();
                }
            }
        }
        else
            /* If this is the status window then just output a new line */

            output_char('\n');
    }

}/* output_new_line */

/*
 * print_table
 *
 * Writes text into a rectangular window on the screen.
 *
 *    argv[0] = start of text address
 *    argv[1] = rectangle width
 *    argv[2] = rectangle height (default = 1)
 *    argv[3] = skip this many characters between lines
 *
 */

void z_print_table(int argc, unsigned *argv)
{
    unsigned int address;
    unsigned int row, column;

    /* Supply default arguments */

    if (argc < 4)
    	argv[3] = 0;
    if (argc < 3)
        argv[2] = 1;

    /* Don't do anything if the window is zero high or wide */

    if (argv[1] == 0 || argv[2] == 0)
        return;

    /* Get coordinates of top left corner of rectangle */

    get_cursor_position((int *) &row, (int *) &column);

    address = argv[0];

    /* Write text in width * height rectangle */

    for (int height = 0; height < argv[2]; height++)
    {
        for (int width = 0; width < argv[1]; width++)
            write_char(zscii_to_unicode(get_byte(address++)));

        /* Put cursor back to lefthand side of rectangle on next line */

        if (height != argv[2] - 1)
        {
            flush_buffer();
            row+=h_char_height;
            move_cursor(row, column);
        }

        address+=argv[3];
    }

}/* print_table */

/*
 * set_font
 *
 * Set text or graphic font. 1 = text font, 3 = graphics font.
 *
 */

void z_set_font(int argc, unsigned *argv)
{
    unsigned new_font = argv[0];

    if (h_type == V6)
    {
        v6z_set_font(new_font, (argc >= 2) ? argv[1] : -3);
        return;
    }

    switch (new_font)
    {
      case 1:
      case 3:
      case 4:
    	store_operand(font);
        if (new_font != font)
        {
            font = new_font;
            write_char(new_font+EMBED_FONT);
        }
        break;

      case 0:
        store_operand(font);
    	break;

      default:
        store_operand(0);
        break;
    }

}/* set_font */

/*
 * set_colour
 *
 * Set the colour of the screen. Colour can be set on four things:
 *    Screen background
 *    Text typed by player
 *    Text written by game
 *    Graphics characters
 *
 * Colors can be set to 1 of 9 values:
 *    1 = machine default (IBM/PC = blue background, everything else white)
 *    2 = black
 *    3 = red
 *    4 = green
 *    5 = brown
 *    6 = blue
 *    7 = magenta
 *    8 = cyan
 *    9 = white
 *   10 = light grey
 *   11 = medium grey
 *   12 = dark grey
 *
 */

void z_set_true_colour(int argc, unsigned *argv)
{
    if (h_type<V5)
    {
        warn_bad_use("set_true_colour");
        return;
    }

    flush_buffer();

    set_true_colours((short) argv[0], (short) argv[1], argc >= 3 ? argv[2] : -1);
}

void z_set_colour(int argc, unsigned *argv)
{
    if (h_type<V5)
    {
        warn_bad_use("set_colour");
        return;
    }

    argv[0] = z_colour_to_true_colour((short) argv[0]);
    argv[1] = z_colour_to_true_colour((short) argv[1]);

    z_set_true_colour(argc, argv);
}

void reset_line_counts(void)
{
    if (h_type < V6)
    {
        lines_written=0;
    }
    else
    {
        for (int w=0; w<=7; w++)
            wind_prop[w][WPROP_LINE_COUNT]=0;
    }
}
