/*
 * text.c
 *
 * Text manipulation routines
 *
 */

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

#include <oslib/types.h>
#include "ztypes.h"
#include "text.h"
#include "osdepend.h"
#include "operand.h"
#include "fileio.h"
#include "control.h"
#include "memory.h"
#include "screen.h"
#include "screenio.h"
#include "object.h"
#include "input.h"
#include "unicutils.h"
#include "v6.h"
#include "v6ro.h"

zword_t *line = NULL;
char *status_line = NULL;
int lines_written = 0;
int status_pos = 0;
int buffering;
int wrapping;
int output_stream_1 = ON;
int output_stream_3 = OFF;
int scripting_disable = OFF;
int scripting = OFF;
const char *lookup_table[3];

static int saved_buffering = ON;
static int saved_wrapping = ON;
static int line_pos = 0;
static int story_buffer = 0;
static int story_pos = 0;
static int story_count = 0;
static int output_stream_3_width = 0;

unsigned h_synonyms_offset;
unsigned h_strings_offset;

#define MAX_SAVED_STREAM3 16
static struct saved_stream3
{
    int story_buffer;
    int story_pos;
    int story_count;
    int width;
    int buffering;
    int wrapping;
} saved_stream3[MAX_SAVED_STREAM3];

static int stream3_depth;

zbyte_t zscii_to_unicode_table_size = 69;
zword_t zscii_to_unicode_table[MAX_UNICODE_TABLE_SIZE] =
{
    0x00E4, /* 155:LATIN SMALL LETTER A WITH DIAERESIS */
    0x00F6, /* 156:LATIN SMALL LETTER O WITH DIAERESIS */
    0x00FC, /* 157:LATIN SMALL LETTER U WITH DIAERESIS */
    0x00C4, /* 158:LATIN CAPITAL LETTER A WITH DIAERESIS */
    0x00D6, /* 159:LATIN CAPITAL LETTER O WITH DIAERESIS */
    0x00DC, /* 160:LATIN CAPITAL LETTER U WITH DIAERESIS */
    0x00DF, /* 161:LATIN SMALL LETTER SHARP S (German) */
    0x00BB, /* 162:RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */
    0x00AB, /* 163:LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */
    0x00EB, /* 164:LATIN SMALL LETTER E WITH DIAERESIS */
    0x00EF, /* 165:LATIN SMALL LETTER I WITH DIAERESIS */
    0x00FF, /* 166:LATIN SMALL LETTER Y WITH DIAERESIS */
    0x00CB, /* 167:LATIN CAPITAL LETTER E WITH DIAERESIS */
    0x00CF, /* 168:LATIN CAPITAL LETTER I WITH DIAERESIS */
    0x00E1, /* 169:LATIN SMALL LETTER A WITH ACUTE */
    0x00E9, /* 170:LATIN SMALL LETTER E WITH ACUTE */
    0x00ED, /* 171:LATIN SMALL LETTER I WITH ACUTE */
    0x00F3, /* 172:LATIN SMALL LETTER O WITH ACUTE */
    0x00FA, /* 173:LATIN SMALL LETTER U WITH ACUTE */
    0x00FD, /* 174:LATIN SMALL LETTER Y WITH ACUTE */
    0x00C1, /* 175:LATIN CAPITAL LETTER A WITH ACUTE */
    0x00C9, /* 176:LATIN CAPITAL LETTER E WITH ACUTE */
    0x00CD, /* 177:LATIN CAPITAL LETTER I WITH ACUTE */
    0x00D3, /* 178:LATIN CAPITAL LETTER O WITH ACUTE */
    0x00DA, /* 179:LATIN CAPITAL LETTER U WITH ACUTE */
    0x00DD, /* 180:LATIN CAPITAL LETTER Y WITH ACUTE */
    0x00E0, /* 181:LATIN SMALL LETTER A WITH GRAVE */
    0x00E8, /* 182:LATIN SMALL LETTER E WITH GRAVE */
    0x00EC, /* 183:LATIN SMALL LETTER I WITH GRAVE */
    0x00F2, /* 184:LATIN SMALL LETTER O WITH GRAVE */
    0x00F9, /* 185:LATIN SMALL LETTER U WITH GRAVE */
    0x00C0, /* 186:LATIN CAPITAL LETTER A WITH GRAVE */
    0x00C8, /* 187:LATIN CAPITAL LETTER E WITH GRAVE */
    0x00CC, /* 188:LATIN CAPITAL LETTER I WITH GRAVE */
    0x00D2, /* 189:LATIN CAPITAL LETTER O WITH GRAVE */
    0x00D9, /* 190:LATIN CAPITAL LETTER U WITH GRAVE */
    0x00E2, /* 191:LATIN SMALL LETTER A WITH CIRCUMFLEX */
    0x00EA, /* 192:LATIN SMALL LETTER E WITH CIRCUMFLEX */
    0x00EE, /* 193:LATIN SMALL LETTER I WITH CIRCUMFLEX */
    0x00F4, /* 194:LATIN SMALL LETTER O WITH CIRCUMFLEX */
    0x00FB, /* 195:LATIN SMALL LETTER U WITH CIRCUMFLEX */
    0x00C2, /* 196:LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
    0x00CA, /* 197:LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
    0x00CE, /* 198:LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
    0x00D4, /* 199:LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
    0x00DB, /* 200:LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
    0x00E5, /* 201:LATIN SMALL LETTER A WITH RING ABOVE */
    0x00C5, /* 202:LATIN CAPITAL LETTER A WITH RING ABOVE */
    0x00F8, /* 203:LATIN SMALL LETTER O WITH STROKE */
    0x00D8, /* 204:LATIN CAPITAL LETTER O WITH STROKE */
    0x00E3, /* 205:LATIN SMALL LETTER A WITH TILDE */
    0x00F1, /* 206:LATIN SMALL LETTER N WITH TILDE */
    0x00F5, /* 207:LATIN SMALL LETTER O WITH TILDE */
    0x00C3, /* 208:LATIN CAPITAL LETTER A WITH TILDE */
    0x00D1, /* 209:LATIN CAPITAL LETTER N WITH TILDE */
    0x00D5, /* 210:LATIN CAPITAL LETTER O WITH TILDE */
    0x00E6, /* 211:LATIN SMALL LETTER AE */
    0x00C6, /* 212:LATIN CAPITAL LETTER AE */
    0x00E7, /* 213:LATIN SMALL LETTER C WITH CEDILLA */
    0x00C7, /* 214:LATIN CAPITAL LETTER C WITH CEDILLA */
    0x00FE, /* 215:LATIN SMALL LETTER THORN (Icelandic) */
    0x00F0, /* 216:LATIN SMALL LETTER ETH (Icelandic) */
    0x00DE, /* 217:LATIN CAPITAL LETTER THORN (Icelandic) */
    0x00D0, /* 218:LATIN CAPITAL LETTER ETH (Icelandic) */
    0x00A3, /* 219:POUND SIGN */
    0x0153, /* 220:LATIN SMALL LIGATURE OE */
    0x0152, /* 221:LATIN CAPITAL LIGATURE OE */
    0x00A1, /* 222:INVERTED EXCLAMATION MARK */
    0x00BF  /* 223:INVERTED QUESTION MARK */
};

/*
 * decode_text
 *
 * Convert encoded text to ZSCII. Text is encoded by squeezing each character
 * into 5 bits. 3 x 5 bit encoded characters can fit in one word with a spare
 * bit left over. The spare bit is used to signal to end of a string. The 5 bit
 * encoded characters can either be actual character codes or prefix codes that
 * modifier the following code.
 *
 */

void decode_text(unsigned *address)
{
    int i, synonym_flag, synonym = 0, zscii_flag, zscii = 0;
    int data, code, shift_state, shift_lock;
    unsigned addr;

    /* Set state variables */

    shift_state = 0;
    shift_lock = 0;
    zscii_flag = 0;
    synonym_flag = 0;

    do
    {
        /*
         * Read one 16 bit word. Each word contains three 5 bit codes. If the
         * high bit is set then this is the last word in the string.
         */

        data = read_data_word(address);

        for (i = 10; i >= 0; i -= 5)
        {
            /* Get code, high bits first */

            code = (data >> i) & 0x1f;

            /* Synonym codes */

            if (synonym_flag)
            {
                synonym_flag = 0;
                synonym = (synonym - 1) * 64;
                addr = get_word(h_synonyms_offset + synonym + (code * 2)) * 2;
                decode_text(&addr);
                shift_state = shift_lock;

            /* ZSCII codes */

            }
            else if (zscii_flag)
            {
                /*
                 * If this is the first part ZSCII code then remember it.
                 * Because the codes are only 5 bits you need two codes to make
                 * one eight bit ZSCII character. The first code contains the
                 * top 5 bits. The second code contains the bottom 5 bits.
                 */

                if (zscii_flag++ == 1)
                    zscii = code << 5;

                /*
                 * If this is the second part ZSCII code then assemble the
                 * character from the two codes and output it.
                 */

                else
                {
                    zscii_flag = 0;
                    #ifdef ZSPEC11DRAFT
                    zscii |= code;
                    if (zscii >= 768)
                    {
                        /* Unicode escape - read n-767 Unicode values */
                        int n;
                        for (n=767; n < zscii; n++)
                            z_print_unicode(read_data_word(address) ^ 0xFFFF);
                    }
                    else
                    #endif
                        write_zchar(zscii | code);
                }

            /* Character codes */

            }
            else if (code >= 6)
            {
                /*
                 * If this is character 0 in the punctuation set then the next two
                 * codes make an ZSCII character.
                 */

                if (shift_state == 2 && code == 6)
                    zscii_flag = 1;

                /*
                 * If this is character 1 in the punctuation set then this
                 * is a new line.
                 */

                else if (shift_state == 2 && code == 7 && h_type >= V2)
                    z_new_line();

                /*
                 * This is a normal character so select it from the character
                 * table appropriate for the current shift state.
                 */

                else
                    write_zchar(lookup_table[shift_state][code-6]);

                shift_state = shift_lock;

            /* Special codes 0 to 5 */

            }
            else
            {
                /*
                 * Space: 0
                 *
                 * Output a space character.
                 *
                 */

                if (code == 0)

                    write_zchar(' ');

                else
                {
                    /*
                     * The use of the synonym and shift codes is the only difference between
                     * the different versions.
                     */

                    if (h_type < V3)
                    {
                        /*
                         * Newline or synonym: 1
                         *
                         * Output a newline character or set synonym flag.
                         *
                         */

                        if (code == 1)
                        {
                            if (h_type == V1)

                                z_new_line();

                            else
                            {
                                synonym_flag = 1;
                                synonym = code;
                            }

                        /*
                         * Shift keys: 2, 3, 4 or 5
                         *
                         * Shift keys 2 & 3 only shift the next character and can be used regardless of
                         * the state of the shift lock. Shift keys 4 & 5 lock the shift until reset.
                         *
                         * The following code implements the the shift code state transitions:
                         *
                         * +------+-------------+-------------+-------------+-------------+
                         * | Code |      2      |       3     |      4      |      5      |
                         * +------+-------------+-------------+-------------+-------------+
                         * |  A0  | Shift to A1 | Shift to A2 | Lock to A1  | Lock to A2  |
                         * |  A1  | Shift to A2 | Shift to A0 | Lock to A2  | Lock to A0  |
                         * |  A2  | Shift to A0 | Shift to A1 | Lock to A0  | Lock to A1  |
                         * +------+-------------+-------------+-------------+-------------+
                         *
                         */

                        }
                        else
                        {
                            if (code < 4)
                                shift_state = (shift_lock + code + 2) % 3;
                            else
                                shift_lock = shift_state = (shift_lock + code) % 3;
                        }

                    }
                    else /* version 3 upwards */
                    {
                        /*
                         * Synonym table: 1, 2 or 3
                         *
                         * Selects which of three synonym tables the synonym
                         * code following in the next code is to use.
                         *
                         */

                        if (code < 4)
                        {
                            synonym_flag = 1;
                            synonym = code;

                        /*
                         * Shift key: 4 or 5
                         *
                         * The Z-machine standard says 4 and 5 are temporary
                         * shifts to alphabets 1 and 2. In fact, we now follow
                         * Infocom's interpreters, with the state transitions:
                         *
                         * +------+-------------+-------------+
                         * | Code |      4      |      5      |
                         * +------+-------------+-------------+
                         * |  A0  | Shift to A1 | Shift to A2 |
                         * |  A1  | Lock to A1  | Lock to A0  |
                         * |  A2  | Lock to A0  | Lock to A2  |
                         * +------+-------------+-------------+
                         *
                         */

                        }
                        else
                        {
                            if (shift_state == 0)
                                shift_state = code - 3;
                            else if (shift_state == code - 3)
                                shift_lock = shift_state;
                            else
                                shift_lock = shift_state = 0;
                        }
                    }
                }
            }
        }
    } while ((data & 0x8000) == 0);

}/* decode_text */

/*
 * encode_text
 *
 * Pack a string into up to 9 codes or 3 words.
 *
 */

void encode_text(int len, unsigned zscii_text, unsigned *buffer)
{
    int i, j, prev_table, table, next_table, shift_state, code, codes_count;
    int incomplete = 0;
    int limit;
    char codes[9];

    /* Initialise codes count and prev_table number */

    codes_count = 0;
    prev_table = 0;
    if (h_type <= V3)
        limit = 6;
    else
        limit = 9;

    /* Scan do the string one character at a time */

    while (len--)
    {
        /*
         * Set the table and code to be the ZSCII character inducer, then
         * look for the character in the three lookup tables. If the
         * character isn't found then it will be an ZSCII character.
         */
        int c = get_byte_priv(zscii_text);

        table = 2;
        code = 0;
        for (i = 0; i < 3; i++)
        {
            const char *t = lookup_table[i];
            for (j = 0; j < 26; j++)
                 if (*t++ == c)
                 {
                     table = i;
                     code = j;
                     goto found;
                 }
        }

      found:
        /*
         * Type 1 and 2 games differ on how the shift keys are used. Switch
         * now depending on the game version.
         */

        if (h_type < V3)
        {
            /*
             * If the current table is the same as the previous table then
             * just store the character code, otherwise switch tables.
             */

            if (table != prev_table)
            {
                /* Find the table for the next character */
                int next = get_byte_priv(zscii_text+1);

                next_table = 0;
                if (len)
                {
                    next_table = 2;
                    for (i = 0; i < 3; i++)
                        if (memchr(lookup_table[i], next, 26))
                        {
                            next_table = i;
                            break;
                        }
                }

                /*
                 * Calculate the shift key. This magic. See the description in
                 * decode_text for more information on version 1 and 2 shift
                 * key changes.
                 */

                shift_state = (table + (prev_table * 2)) % 3;

                /* Only store the shift key if there is a change in table */

                if (shift_state)
                {
                    /*
                     * If the next character as the uses the same table as
                     * this character then change the shift from a single
                     * shift to a shift lock. Also remember the current
                     * table for the next iteration.
                     */

                    if (next_table == table)
                    {
                        shift_state += 2;
                        prev_table = table;
                    }
                    else
                        prev_table = 0;

                    /* Store the code in the codes buffer */

                    if (codes_count < limit)
                    {
                        codes[codes_count++] = (char) (shift_state + 1);
                        incomplete = 1;
                    }
                }
            }
        }
        else
        {
            /*
             * For V3 games each uppercase or punctuation table is preceded
             * by a separate shift key. If this is such a shift key then
             * put it in the codes buffer.
             */

            if (table && codes_count < limit)
            {
                codes[codes_count++] = (char) (table + 3);
                incomplete = 1;
            }
        }

        /* Put the character code in the code buffer */

        if (codes_count < limit)
        {
            codes[codes_count++] = code + 6;
            incomplete = 0;
        }

        /*
         * Cannot find character in table so treat it as a literal ZSCII
         * code. The ZSCII code inducer (code 0 in table 2) is followed by
         * the high 3 bits of the ZSCII character followed by the low 5
         * bits to make 8 bits in total.
         */

        if (table == 2 && code == 0)
        {
            incomplete = 1;
            if (codes_count < limit)
                codes[codes_count++] = (c >> 5) & 0x07;
            if (codes_count < limit)
            {
                codes[codes_count++] = c & 0x1f;
                incomplete = 0;
            }
        }

        /* Advance to next character */

        zscii_text++;
    }

    /* Pad out codes with shift 5's */

    while (codes_count < limit)
        codes[codes_count++] = 5;

    /* Pack codes into buffer */

    buffer[0] = (codes[0] << 10) | (codes[1] << 5) | codes[2];
    buffer[1] = (codes[3] << 10) | (codes[4] << 5) | codes[5];
    buffer[2] = (codes[6] << 10) | (codes[7] << 5) | codes[8];

    /* Terminate buffer at 6 or 9 codes depending on the version */

    if (h_type >= V4)
        buffer[2] |= 0x8000;
    else if (h_type == V3)
        buffer[1] |= 0x8000;
    else /* V1 or V2 */
    {
        if (!incomplete)
            buffer[1] |= 0x8000;
    }

}/* encode_text */

/*
 * write_zchar
 *
 * High level ZSCII character output routine. Translates ZSCII characters to
 * machine specific character(s) before output. If it cannot translate it then
 * use the default translation. If the character is still unknown then display
 * a '?'.
 *
 */

void write_zchar(int c)
{
    /* If character is not special character then just write it */
    if (c>=' ' && c<='~')
    {
        write_char(c);
    }
    else if (c==13)
    {
    	z_new_line();
    }
    else if (c==9 && h_type==V6)
    {
        if (line_pos == 0 &&
            wind_prop[screen_window][WPROP_X_CURSOR] ==
                 wind_prop[screen_window][WPROP_L_MARGIN] + 1)
        {
            unsigned coord[2], maxx;
            coord[0] = wind_prop[screen_window][WPROP_Y_CURSOR];
            coord[1] = wind_prop[screen_window][WPROP_X_CURSOR] + 3*V6_CHAR_W;
            maxx = 1 + wind_prop[screen_window][WPROP_X_WIDTH]
                     - wind_prop[screen_window][WPROP_R_MARGIN];
            if (coord[1] > maxx) coord[1] = maxx;
            z_erase_line(1+3*V6_CHAR_W);
            z_set_cursor(2, coord);
        }
        else
            write_char(' ');
    }
    else if (c==11 && h_type==V6)
    {
        write_char(' ');
        write_char(' ');
    }
    else if (c==0)
    {
        /* Do nothing */
    }
    else if (c < 155 || c >= 155 + zscii_to_unicode_table_size)
    {
        write_char('?');
    }
    else
    {
        z_print_unicode(zscii_to_unicode_table[c - 155]);
    }

}/* write_zchar */

unsigned unicode_to_native(unsigned u, unsigned fail)
{
    if (u < 0x0080)
        return u;
    else
    {
        unsigned c;
        for (c = 128; c < 256; c++)
        {
            if (native_to_unicode_table[c-128] == u)
                return c;
        }

        return fail;
    }
}

unsigned unicode_to_system(unsigned u, unsigned fail)
{
    if (u < 0x0080)
        return u;
    else
    {
        unsigned c;
        for (c = 128; c < 256; c++)
        {
            if (system_to_unicode_table[c-128] == u)
                return c;
        }

        return fail;
    }
}

unsigned unicode_to_zscii(unsigned u, unsigned fail)
{
    if (u < 0x0080)
        return u;
    else
    {
        unsigned c;
        for (c = 155; c < 155+zscii_to_unicode_table_size; c++)
        {
            if (zscii_to_unicode_table[c-155] == u)
                return c;
        }

        return fail;
    }
}

unsigned zscii_to_unicode(unsigned z)
{
    if (z < 0x0080)
        return z;
    else if (z >= 155 && z < 155+zscii_to_unicode_table_size)
        return zscii_to_unicode_table[z-155];
    else
        return 0xFFFD;
}

unsigned native_to_unicode(unsigned c)
{
    if (c < 0x0080)
        return c;
    else
        c = native_to_unicode_table[c-128];

    return c == 0xFFFF ? 0xFFFD : c;
}

unsigned system_to_unicode(unsigned c)
{
    if (c < 0x0080)
        return c;
    else
        c = system_to_unicode_table[c-128];

    return c == 0xFFFF ? 0xFFFD : c;
}

char *unicode_string_to_native(char *dest, const zword_t *src, unsigned fail)
{
    char *p = dest;

    while ((*p++ = unicode_to_native(*src++, fail)) != 0);

    return dest;
}

zword_t *native_string_to_unicode(zword_t *dest, const char *src)
{
    zword_t *p = dest;

    while ((*p++ = native_to_unicode(*src++)) != 0);

    return dest;
}

extern int system_alphabet_no;

char *unicode_string_to_system(char * restrict dest, const zword_t * restrict src, unsigned fail)
{
    char *p = dest;

    if (system_alphabet_no == 111)
    {
        do
        {
            p = UCS_to_UTF8(p, *src++);
        } while (*(p-1) != '\0');
    }
    else
        while ((*p++ = unicode_to_system(*src++, fail)) != 0);

    return dest;
}

zword_t *system_string_to_unicode(zword_t * restrict dest, const char * restrict src)
{
    zword_t *p = dest;

    if (system_alphabet_no == 111)
    {
        do
        {
            src += UTF8_to_UCS(src, p++);
        } while (*(p-1)!=0);
    }
    else
        while ((*p++ = system_to_unicode(*src++)) != 0);

    return dest;
}

/*
 * write_char
 *
 * High level character output routine. The write_char routine is slightly
 * complicated by the fact that the output can be limited by a fixed character
 * count, as well as, filling up the buffer.
 *
 * These days, the parameter is a Unicode character.
 *
 */
void write_char(int u)
{
    char temp[256];
    int temp_pos;
    int c, z;

    UNSET(temp_pos);

    c = unicode_to_native(u, '?');
    z = unicode_to_zscii(u, '?');

    /* Text buffering can only be on in the lower window before V6 */
    if (buffering==ON && output_stream_1==ON && (screen_window==TEXT_WINDOW || h_type==V6))
    {
        /* Deal with buffered screen output */
        if (u==' ' || u=='-')
        {
            if (u=='-')
            	line[line_pos++]='-';

            /* End of word. Does it fit? */

    	    if (output_stream_3)
    	    {
    	        /* We must be doing a formatted V6 OUTPUT_STREAM 3 */
    	        if (v6ro_text_length_z((char *)(datap+story_buffer+2),
    	                                 story_pos-(story_buffer+2))+
    	            v6ro_text_length(line, line_pos) > output_stream_3_width)
    	        {
    	            /* Right, the chunk of text in the buffer won't fit */
    	            set_word(story_buffer, story_pos-(story_buffer+2));
    	            story_buffer=story_pos;
    	            story_pos=story_buffer+2;
    	        }
    	    }
            else if (wrapping && fit_word(line, line_pos)==0)
            {
                /* It doesn't fit - print a new line. */
                if (h_type<=V3 || h_type==V6)
                {
                    memcpy(temp, line, line_pos);
                    temp_pos=line_pos;
                    line_pos=0;
                }

                script_new_line();
                output_new_line();

                if (h_type<=V3 || h_type==V6)
                {
                    memcpy(line, temp, temp_pos);
                    line_pos=temp_pos;
                }
            }

    	    if (u==' ')
            	line[line_pos++]=' ';

            if (!output_stream_3)
            {
                line[line_pos]='\0';
                script_string(line);
                output_string(line);
                line_pos=0;
            }
            else
            {
                int i;
            	for (i=0; i<line_pos; i++)
            	    set_byte(story_pos++, line[i]);
            	line_pos=0;
            }
        }
        else
            line[line_pos++]=u;
    }
    else if (output_stream_3==ON)
    {
        /* If redirect is on then write the character to the status line for V1 to V3
           games or into the writeable data area for V4+ games */

        if (h_type < V4)
            status_line[status_pos++] = (char) c;
        else
        {
            set_byte (story_pos++, z);
            if (output_stream_3_width)
            {
            	if (v6ro_text_length_z((char *)(datap+story_buffer+2),
            	                        story_pos-(story_buffer+2))>output_stream_3_width)
            	{
                    story_pos--;
                    set_word(story_buffer, story_pos-(story_buffer+2));
                    story_buffer=story_pos;
                    story_pos=story_buffer+2;
                    set_byte(story_pos++, z);
                }
            }
            else
            	story_count++;

        }
    }
    else
    {
        /* No buffering or output redirection, so just output the character */

        script_char(u);
        output_char(u);
    }
}

/*
 * set_text_style
 *
 * Set a video attribute. Write the video mode, from 0 to 8, incremented.
 * This is so the output routines don't confuse video attribute 0 as the
 * end of the string.
 *
 */

void z_set_text_style(int mode)
{
    if (mode <= MAX_ATTRIBUTE)
    {
        write_char(mode+EMBED_STYLE);
        if (h_type == V6)
            v6_set_text_style(mode);
    }
}

/*
 * write_string
 *
 * Output a string
 *
 */

void write_string(const char * restrict s)
{
    while (*s)
        write_zchar(*s++);

}/* write_string */

/*
 * flush_buffer
 *
 * Send output buffer to the screen.
 *
 */

void flush_buffer(void)
{
    if (line_pos)
    {
        if (wrapping && fit_word(line, line_pos)==0)
        {
            char temp[256];
            int temp_pos;

            UNSET(temp_pos);

            /* It doesn't fit - print a new line. */
            if (h_type<=V3 || h_type==V6)
            {
                memcpy(temp, line, line_pos);
                temp_pos=line_pos;
                line_pos=0;
            }

            script_new_line();
            output_new_line();

            if (h_type<=V3 || h_type==V6)
            {
                memcpy(line, temp, temp_pos);
                line_pos=temp_pos;
            }
        }

        /* Terminate the line */

        line[line_pos] = '\0';

        /* Send the line buffer to the printer */

        script_string(line);

        /* Send the line buffer to the screen */

        output_string(line);

        /* Reset the buffer pointer */

        line_pos = 0;
    }

}/* flush_buffer */

void reset_buffer(void)
{
    line_pos = 0;
}

/*
 * buffer_mode
 *
 * Set the format mode flag. Formatting disables writing into the output buffer.
 *
 */

void z_buffer_mode(int flag)
{
    /* Flush any current output */

    flush_buffer();

    /* Set buffering depending on the flag */

    if (h_type!=V6)
    	wrapping = buffering = flag ? ON : OFF;
    #if 0
    /* Standard suggests ignore this */
    else
    {
        unsigned arglist[3];

        arglist[0]=screen_window;
        arglist[1]=WATTR_BUFFER;
        arglist[2]=flag ? 1 : 2;
        z_window_style(3, arglist);
    }
    #endif

}/* buffer_mode */

/*
 * output_stream
 *
 * Set various printing modes. These can be: disabling output, scripting and
 * redirecting output. Redirection is peculiar. I use it to format the status
 * line for V1 to V3 games, otherwise it wasn't used. V4 games format the status line
 * themselves in an internal buffer in the writeable data area. To use the normal
 * text decoding routines they have to redirect output to the writeable data
 * area. This is done by passing in a buffer pointer. The first word of the
 * buffer will receive the number of characters written since the output was
 * redirected. The remainder of the buffer will contain the redirected text.
 *
 */

void z_output_stream(int argc, short type, int option, short width)
{
    switch (type)
    {
      case 1:
        /* Turn on text output */

        flush_buffer();
        output_stream_1 = ON;
        break;

      case 2:
        /* Turn on scripting */

        open_script ();
        break;

      case 3:
        /* Turn on output redirection */

        flush_buffer();

        if (output_stream_3 == ON)
        {
            if (stream3_depth >= MAX_SAVED_STREAM3)
                fatal_lookup("TooMany3");

            saved_stream3[stream3_depth].story_buffer = story_buffer;
            saved_stream3[stream3_depth].story_pos = story_pos;
            saved_stream3[stream3_depth].story_count = story_count;
            saved_stream3[stream3_depth].width = output_stream_3_width;
            saved_stream3[stream3_depth].buffering = buffering;
            saved_stream3[stream3_depth].wrapping = wrapping;
            stream3_depth++;
        }
        else
        {
            saved_buffering = buffering;
            saved_wrapping = wrapping;
        }

        /* Disable text buffering during redirection */

    	if (argc<3)
    	{
            buffering = OFF;
            wrapping = OFF;
        }
        else
        {
            buffering = ON;
            wrapping = ON;
        }

        /* Enable text redirection */

        output_stream_3 = ON;

        /* Set up the redirection pointers */

        if (h_type < V4)
            status_pos = 0;
        else
        {
            story_count = 0;
            story_buffer = option;
            if (h_type == V6 && argc>=3)
            {
                if (width >= 0)
                    output_stream_3_width=wind_prop[width][WPROP_X_WIDTH];
                else
                    output_stream_3_width=-width;
            }
            else
            	output_stream_3_width=0;

            story_pos=option+2;
        }
        break;

      case 4:
        /* Turn on input recording */

        open_record ();
        break;

      case -1:
        /* Turn off text output */

    	flush_buffer();
        output_stream_1 = OFF;
        break;

      case -2:
        /* Turn off scripting */

        close_script ();
        break;

      case -3:
        /* Turn off output redirection */

        if (output_stream_3 == ON)
        {
            /* Terminate the redirection buffer and store the count of character
               in the buffer into the first word of the buffer */

            if (h_type > V3 && !(h_type==V6 && output_stream_3_width))
                set_word(story_buffer, story_count);

            if (h_type==V6)
            {
                int width=v6ro_text_length_z((char *)datap+story_buffer+2, story_count);

            	set_word_priv(H_STREAM3_WIDTH, width);

            	if (output_stream_3_width)
            	{
            	    int i;
        	    if (v6ro_text_length_z((char *)(datap+story_buffer+2),
        	                              story_pos-(story_buffer+2))+
        	        v6ro_text_length(line, line_pos) > output_stream_3_width)
        	    {
        	        /* Right, the chunk of text in the buffer won't fit */
        	        set_word(story_buffer, story_pos-(story_buffer+2));
        	        story_buffer=story_pos;
        	        story_pos=story_buffer+2;
        	    }
            	    for (i=0; i<line_pos; i++)
            	    	set_byte(story_pos++, line[i]);

    	    	    line_pos=0;
            	    set_word(story_buffer, story_pos-(story_buffer+2));
            	    set_word(story_pos, 0);
            	}
            }

            if (stream3_depth > 0)
            {
                stream3_depth--;
                story_buffer = saved_stream3[stream3_depth].story_buffer;
                story_pos = saved_stream3[stream3_depth].story_pos;
                story_count = saved_stream3[stream3_depth].story_count;
                output_stream_3_width = saved_stream3[stream3_depth].width;
                buffering = saved_stream3[stream3_depth].buffering;
                wrapping = saved_stream3[stream3_depth].wrapping;
           }
            else
            {
                /* Restore the buffer mode and turn off redirection */

                wrapping = saved_wrapping;
                buffering = saved_buffering;
                output_stream_3 = OFF;
            }
        }
        break;

      case -4:
        /* Turn off input recording */

        close_record ();
        break;
    }

}/* output_stream */

/*
 * print_char
 *
 * Write a character.
 *
 */

void z_print_char(int c)
{
    write_zchar(c);
}

/*
 * print_num
 *
 * Write a signed number.
 *
 */

void z_print_num(short num)
{
    z_print_num_32(num);
}

void z_print_num_32(int num)
{
    int i, count;
    char buffer[12];

    i = num;
    sprintf(buffer, "%d%n", i, &count);
    for (i = 0; i < count; i++)
        write_char(buffer[i]);
}

/*
 * print_paddr
 *
 * Print using a packed address. Packed addresses are used to save space and
 * reference addresses outside of the data region.
 *
 */

void z_print_paddr(int packed_address)
{
    unsigned address;

    /* Convert packed address to real address */

    address = (packed_address << story_shift) + h_strings_offset;

    /* Decode and output text at address */

    decode_text(&address);
}

/*
 * print_addr
 *
 * Print using a real address. Real addresses are just offsets into the
 * data region.
 *
 */

void z_print_addr(int offset)
{
    unsigned address;

    address = offset;

    /* Decode and output text at address */

    decode_text(&address);
}

/*
 * print_obj
 *
 * Print an object description. Object descriptions are stored as ASCIC
 * strings at the front of the property list for the object.
 *
 */

void z_print_obj(int obj)
{
    zword_t offset;
    unsigned address;

    /* Check for NULL object */

    if (obj == 0)
        return;

    /* Calculate address of property list */

    offset = object_address(obj);
    offset += (h_type < V4) ? O3_PROPERTY_OFFSET : O4_PROPERTY_OFFSET;

    /* Read the property list address and skip the count byte */

    address = get_word(offset) + 1;

    /* Decode and output text at address */

    decode_text(&address);
}

/*
 * print
 *
 * Print the string embedded in the instruction stream at this point. All
 * strings that do not need to be referenced by address are embedded in the
 * instruction stream. All strings that can be refered to by address are placed
 * at the end of the code region and referenced by packed address.
 *
 */

void z_print(void)
{
    unsigned address=pc-datap;
    /* Decode and output text at PC */

    decode_text(&address);

    pc=datap+address;
}

/*
 * print_ret
 *
 * Print a string embedded in the instruction stream as with print,
 * except flush the output buffer and write a new line. After this return from
 * the current subroutine with a status of true.
 *
 */

void z_print_ret(void)
{
    z_print();
    z_new_line();
    z_rtrue();
}

/*
 * new_line
 *
 * Simply flush the current contents of the output buffer followed by a new
 * line.
 *
 */

void z_new_line(void)
{
    /* Only flush buffer if story redirect is off */

    if (output_stream_3 == OFF)
    {
        flush_buffer();
        script_new_line();
        output_new_line();

        if (screen_window==newline_check)
            newline_flag=1;
    }
    else
        write_char('\r');
}

/*
 * print_time
 *
 * Print the time as HH:MM [am|pm]. This is a bit language dependent and can
 * quite easily be changed. If you change the size of the time string output
 * then adjust the status line position in z_show_status.
 *
 */

void print_time(int hours, int minutes)
{
    int pm_indicator;

    /* Remember if time is pm */

    pm_indicator = (hours < 12) ? OFF : ON;

    /* Convert 24 hour clock to 12 hour clock */

    hours %= 12;
    if (hours == 0)
        hours = 12;

    /* Write hour right justified */

    if (hours < 10)
        write_char(' ');
    z_print_num(hours);

    /* Write hours/minutes separator */

    write_char(':');

    /* Write minutes zero filled */

    if (minutes < 10)
        write_char('0');
    z_print_num(minutes);

    /* Write the am or pm string */

    if (pm_indicator == ON)
        write_string(" PM");
    else
        write_string(" AM");
}

/*
 * encode_text
 *
 * Convert text to packed text.
 *
 */

void z_encode_text(int word_addr, int word_length, int word_offset, int dest_addr)
{
    unsigned word[3];
    int i;

    /* Encode the word */

    encode_text(word_length, word_addr + word_offset, word);

    /* Move the encoded word, byte swapped, into the destination buffer */

    for (i = 0; i < (h_type <= V3 ? 2 : 3); i++, dest_addr += 2)
        set_word(dest_addr, word[i]);
}

/*
 * print_unicode
 *
 * Print a Unicode character.
 *
 */
void z_print_unicode(unsigned u)
{
    if (u >= 0x0020 && u < 0x0080 || u >= 0x00A0)
        write_char(u);
    else
        write_char('?');
}

/*
 * check_unicode
 *
 * Determine whether the interpreter can print or receive from the
 * keyboard a given Unicode character.
 */
void z_check_unicode(unsigned u)
{
    unsigned result = 0, i;

    /* Can we print it? */
    if (u >= 0x0020 && u < 0x0080)
        result = 1;
    else
        for (i = 0; i < 128; i++)
            if (native_to_unicode_table[i] == u)
            {
                result = 1;
                break;
            }

    /* Can we read it from the keyboard? (Must be in ZSCII) */
    if (u == 0x0008 || u == 0x000D || u == 0x001B ||
        u >= 0x0020 && u < 0x0080)
        result |= 2;
    else if (system_alphabet_no == 111)
    {
        if (unicode_to_zscii(u, 0) != 0)
            result |= 2;
    }
    else
        for (i = 128; i < 255; i++)
            if (system_to_zscii_table[i] != 0 &&
                system_to_unicode_table[i-128] == u)
            {
                result |= 2;
                break;
            }

    store_operand(result);
}
