/*
 * input.c
 *
 * Input routines
 *
 */

#include <string.h>
#include <stdio.h>

#include "ztypes.h"
#include "input.h"
#include "fileio.h"
#include "screen.h"
#include "control.h"
#include "screenio.h"
#include "text.h"
#include "operand.h"
#include "osdepend.h"
#include "unicutils.h"
#include "v6.h"

int replaying = OFF;
int recording = OFF;
unsigned h_terminating_keys;

/*
 * read_char
 *
 * Read one character with optional timeout
 *
 *    argv[0] = 1
 *    argv[1] = timeout value in seconds (optional)
 *    argv[2] = timeout action routine (optional)
 *
 */

void z_read_char(int argc, unsigned *argv)
{
    int c;

    /* Supply default parameters */

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

    /* Flush any buffered output before read */

    flush_buffer();

    /* Read a character with a timeout. If the input timed out then
       call the timeout action routine. If the return status from the
       timeout routine was 0 then try to read a character again */

    do
    {
        if (replaying)
        {
            os_update_window();
            c = playback_key();
        }
        else
            c = input_character(argv[1]);

	if (recording && !replaying)
            record_key(c);
    }
    while (c == 0 && !call_interrupt(argv[2]));

    if (c != 0)
        reset_line_counts();

    store_operand(c);

}/* read_char */

int newline_flag, newline_check;

/*
 * read
 *
 * Read a line of input with optional timeout.
 *
 *    argv[0] = character buffer address
 *    argv[1] = token buffer address
 *    argv[2] = timeout value in seconds (optional)
 *    argv[3] = timeout action routine (optional)
 *
 */

void z_read(int argc, unsigned *argv)
{
    int i, terminator;
    zword_t buffer[256];
    int max_size, read_size;
    int finished=0, continuing=0;
    zbyte_t *dst;

    /* Supply default parameters */

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

    /* Refresh status line */

    if (h_type <= V3)
        z_show_status();

    /* Flush any buffered output before read */

    flush_buffer();

    /* Initialise character pointer and initial read size */

    max_size=get_byte(argv[0]) - 2;

    if (max_size < 1)
    	fatal_lookup("TextShrt");

    if (h_type >= V5)
    {
        read_size=get_byte(argv[0]+1);
        for (i=0; i<read_size; i++)
            buffer[i] = zscii_to_unicode(get_byte(argv[0]+2+i));
    }
    else
    	read_size=0;

    buffer[read_size]='\0';

    if (read_size && scripting && scripting_disable==OFF && h_type != V6)
    	script_backup(read_size);

    while (!finished)
    {
        /* Read the line then script and record it */

        terminator = get_line(max_size, buffer, argv[2], continuing);

    	continuing=TRUE;

        if (terminator == -1)
            continue;

        if (recording && !replaying)
            record_line(buffer, terminator);

        /* Handle timeouts */

        if (terminator == 0)
        {
            newline_flag = 0;
            newline_check = screen_window;

            if (call_interrupt(argv[3]) == 0)
            {
                if (newline_flag)
                    output_string(buffer);
            }
            else
            	finished=1;
        }
        else
            finished=1;
    }

    if (h_type != V6)
    	script_string(buffer);

    /* Reset line count */

    if (terminator != 0)
        reset_line_counts();

    if (terminator == zscii_NEWLINE)
    {
        flush_buffer();
        if (h_type != V6) script_new_line();
        output_new_line();
    }

    /* Convert text in line to lowercase */

    read_size = strlen_u(buffer);

    for (i = 0; i < read_size; i++)
        buffer[i] = tolower_u(buffer[i]);

    /* Copy local buffer back to dynamic memory */
    dst = datap + argv[0] + (h_type <= V4 ? 1 : 2);
    for (i = h_type <= V4 ? read_size : read_size - 1; i>=0; i--)
        dst[i] = unicode_to_zscii(buffer[i], '?');

    if (h_type >= V5)
    	set_byte(argv[0]+1, read_size);

    /* Tokenise the line, if a token buffer is present */

    if (argv[1])
    	z_tokenise(2, argv);

    /* Return the line terminator */

    if (h_type >= V5)
        store_operand(terminator);

}/* z_read */

/*
 * get_line
 *
 * Read a line of input.
 *
 */

int get_line(int buflen, zword_t *buffer, unsigned timeout, int continuing)
{
    int c;

    /* Try to read input from command file */

    if (replaying)
    {
        os_update_window();
    	c = playback_line(buflen, buffer);
        os_update_window();
    }
    else
        c = os_read(buflen, buffer, timeout, continuing);

    return c;

}/* get_line */


/*
 * tokenise_text
 *
 * Translate a single word to a token and append the token to the token
 * buffer. Unrecognised words cause empty slots if the flag is set.
 *
 */

static void tokenise_text(unsigned text, int length, int from, unsigned parse, unsigned dictionary, int flag)
{
    unsigned encoded[3];
    unsigned entry_addr;
    int entry_count;
    unsigned entry;
    zbyte_t token_max;
    zbyte_t token_count;
    int entry_length;
    int entry_number;
    int resolution;
    int sorted;
    int lower, upper;
    int i;

    /* Dictionary entries take two (V1-V3) or three (V4+) words */

    resolution = (h_type <= V3) ? 2 : 3;

    /* Read the information from the dictionary header and copy it to
       entry_length (size of each entry) and entry_count (number of
       entries). If the entry_number is negative then the entries are
       _not_ in alphabetical order. */

    entry_length=get_byte(dictionary);
    dictionary += 1;
    entry_count=(short)get_word(dictionary);
    dictionary += 2;

    sorted = entry_count > 0;
    if (!sorted)
	entry_count = -entry_count;

    /* Encode the word */

    encode_text(length, text + from, encoded);

    /* Use binary search if the entries are sorted alphabetically,
       otherwise do a linear search. This is done quite hacky using
       only a single loop construct. */

    lower = 0;
    upper = entry_count - 1;

    while (lower <= upper)
    {
	entry_number = sorted ? (lower + upper) >> 1 : lower;
	entry_addr = dictionary + entry_number * entry_length;

	/* Compare encoded word to dictionary entry */

	for (i = 0; i < resolution; i++)
	{
	    entry=get_word(entry_addr);
	    if (encoded[i] != entry)
		break;
	    entry_addr += 2;
	}

	/* Leave loop, if the word has been found */

	if (i == resolution)
	{
	    entry_addr -= 2 * resolution;
	    break;
	}

	/* Otherwise set the new upper and lower bounds */

	if (!sorted)
	    lower++;
	else if (encoded[i] > entry)
	    lower = entry_number + 1;
	else
	    upper = entry_number - 1;
    }

    /* Store the token in the token buffer. This is, of course, only
       possible as long as there is room left in the buffer. Note that
       unrecognised words are represented by zero if the flag is clear,
       and that unrecognised words cause empty slots if it is set. */

    if (lower > upper)
	entry_addr = 0;

    token_max=get_byte(parse);
    token_count=get_byte(parse+1);

    if (token_count < token_max)
    {
        set_byte(parse+1, token_count+1);
	if (entry_addr != 0 || flag == 0)
	{
	    set_word(parse + token_count*4 + 2, entry_addr);
	    set_byte(parse + token_count*4 + 4, length);
	    set_byte(parse + token_count*4 + 5, from);
	}
    }

}/* tokenise_text */

/*
 * z_tokenise
 *
 * Analyse a text string (first argument), divide it into words,
 * translate the words to tokens and store the tokens in the token
 * buffer (second argument). The default dictionary is given by a
 * header field but can be replaced by an optional user dictionary
 * (third argument). If the flag (fourth argument) is set, then
 * unrecognised words cause "empty" slots in the token buffer. An
 * empty slot is left unchanged; this way it is possible to analyse
 * the same text string using several different dictionaries.
 *
 */

void z_tokenise(int argc, unsigned *argv)
{
    unsigned text, parse, dictionary, flag;
    unsigned separator_addr, start_addr, end_addr, separator_count;
    zbyte_t last_char;
    zbyte_t c;
    int isseparator;
    int i;

    /* Supply default parameters */

    text       =             argv[0];
    parse      =             argv[1];
    dictionary = argc >= 3 ? argv[2] : 0;
    flag       = argc >= 4 ? argv[3] : 0;

    if (dictionary == 0)
    	dictionary = get_word_priv(H_WORDS_OFFSET);

    if (get_byte(parse) < 1)
    	fatal_lookup("ParseShrt");

    /* Every dictionary is prefixed with a list of word separators */

    separator_addr = dictionary;

    separator_count = get_byte(separator_addr);

    dictionary += 1 + separator_count;

    /* Remove all tokens before inserting new ones */

    set_byte(parse+1, 0);

    /* Move the end_addr pointer across the text buffer searching for
       the beginning of a word. If this succeeds, store the position in
       the start_addr pointer. Continue moving the end_addr pointer
       searching for the end of the word. When it is found, translate
       the word to a token and store it in the token buffer. Continue
       until the end of the buffer is reached. */

    start_addr = 0;
    end_addr = text;

    if (h_type >= V5)
	last_char=get_byte(++end_addr);

    do
    {
	/* Fetch next character */

	end_addr++;

	if (h_type >= V5 && end_addr - 2 == text + last_char)
	    c = 0;
	else
	    c = get_byte(end_addr);

	/* Check for separator */

	isseparator = 0;

	if (c != ' ')
    	    for (i = 1; i <= separator_count; i++)
    	    	if (c == get_byte(separator_addr+i))
    	    	{
    		    isseparator = 1;
    		    break;
    	    	}

	/* Start or end of a word found? */

	if (start_addr == 0)
	{
	    if (!isseparator && c != ' ' && c != 0)
		start_addr = end_addr;
	}
	else if (isseparator || c == ' ' || c == 0)
	{
	    tokenise_text(text, end_addr - start_addr, start_addr - text, parse, dictionary, flag);
	    start_addr = 0;
	}

	/* Translate separator */

	if (isseparator != 0)
	    tokenise_text(text, 1, end_addr - text, parse, dictionary, flag);

    } while (c != 0);

}/* z_tokenise */


/* Is a key code a terminating one? (10.5.2.1) */
int end_key(int c)
{
    int ptr=h_terminating_keys;
    int b;

    if (ptr==0)
    	return 0;

    if (c < 129 || c > 154 && c < 252 || c > 254)
    	return 0;

    while ((b = get_byte(ptr)) != 0)
    {
        if (c==b || b==255)
            return 1;
        ptr++;
    }

    return 0;
}

