/* riscosio.c */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "kernel.h"

#include "macros.h"
#include "osbyte.h"
#include "osfile.h"
#include "osfscontrol.h"
#include "serviceinternational.h"
#include "colourtrans.h"
#include "toolbox.h"
#include "event.h"

#include "options.h"

#include "ztypes.h"
#include "riscosio.h"
#include "riscoswimp.h"
#include "osdepend.h"
#include "screenio.h"
#include "screen.h"
#include "control.h"
#include "fileio.h"
#include "input.h"
#include "rosound.h"
#include "roinput.h"
#include "v6.h"
#include "v6ro.h"
#include "datavox.h"
#include "text.h"
#include "unicutils.h"

const static zword_t SAVE_NAME[]   = { 'S','a','v','e','d','G','a','m','e',0 };
const static zword_t SCRIPT_NAME[] = { 'T','r','a','n','s','c','r','i','p','t',0 };
const static zword_t RECORD_NAME[] = { 'R','e','c','o','r','d','i','n','g',0 };

zbyte_t h_config;
unsigned h_flags;
zbyte_t h_screen_rows = 25;
zbyte_t h_screen_cols = 80;
unsigned h_screen_width;
unsigned h_screen_height;
zbyte_t h_char_width;
zbyte_t h_char_height;

int screen_gamma = 22;

int screen_bg;
int border=TRUE;

int fg, bg;
static int display_font;
int display_attr;

static bool generous_redraw;

int using_mouse;
int mouse_x, mouse_y, mouse_butt;

static int cursor_saved = OFF;

int os_version;


static int current_row;
static char *current_seg;
static zword_t *current_ptr;
static int sx, sy;

os_box changed_box;

static zword_t **upper_text;
static char **upper_col, **upper_font;
static char **lower_window;
static int *lower_size;

char zscii_to_native_table[256];
zword_t native_to_unicode_table[128];
zword_t system_to_zscii_table[256];
zword_t system_to_unicode_table[128];
int system_alphabet_no;

char encoding_name[16]="\\ELatin1";

font_f fontno[F_LAST+1];
int fwidth[F_LAST+1][256];

font_paint_block fblock;

char textfont_name[font_NAME_LIMIT+16]="\\FTrinity.Medium";
char fixedfont_name[font_NAME_LIMIT+16]="\\FCorpus.Medium";

os_colour palette[9]={os_COLOUR_BLACK,
                      os_COLOUR_RED,
                      os_COLOUR_GREEN,
                      os_COLOUR_YELLOW,
                      os_COLOUR_BLUE,
                      os_COLOUR_MAGENTA,
                      os_COLOUR_CYAN,
                      os_COLOUR_WHITE,
                      0x80808000};

int SWidth, SHeight;
int SWidthOS, SHeightOS;

/*
const int char_width=16*400;
const int line_height=32*400;
*/

#define seg_offset(a) (((int*)a)[0])
#define seg_colour(a) (a[4])
#define seg_font_attr(a) (a[5])
#define seg_len(a) (a[6])
#define seg_left(a) (*(int *)(a+8))
#define seg_text(a) ((zword_t *)(a+12))
#define seg_next(a) (a+seg_offset(a))

int area_no;

osspriteop_area *gfx_area;
osspriteop_header **gfx_ptr;
os_factors factors;
/* Wide enough for 2 64 bit entries (you never know!...)*/
static osspriteop_TRANS_TAB(16) trans_tab;


static void do_gfx(const zword_t *text, int x, int y, int bg, int fg, int n);
static void reset_line(int i);

static char *seg_insert(char *old)
{
    char *new;

    new=(char *)(((int)(seg_text(old)+seg_len(old))+4)&~3);
    seg_offset(new)=seg_offset(old)-(new-old);
    seg_colour(new)=bg<<4 | fg;
    seg_font_attr(new)=seg_font_attr(old);
    seg_left(new)=seg_left(seg_next(old));
    seg_len(new)=0;
    seg_text(new)[0]='\0';
    seg_offset(old)=new-old;

    return new;
}

static void extend_row(int row)
{
    char *temp;

    temp=lower_window[row];
    lower_window[row]=(char *)malloc(lower_size[row]+128);
    memcpy(lower_window[row], temp, lower_size[row]);
    if (temp <= current_seg && current_seg <= temp+lower_size[row])
    {
        current_seg=lower_window[row]+(current_seg-temp);
        current_ptr=(zword_t *) (lower_window[row]+((char *) current_ptr-temp));
    }
    free(temp);

    lower_size[row]+=128;
    for (temp=lower_window[row]; seg_offset(seg_next(temp)); temp=seg_next(temp))
    	;

    memmove(lower_window[row]+lower_size[row]-16, seg_next(temp), 16);

    seg_offset(temp)=lower_window[row]+lower_size[row]-16-temp;
}

void resize_window(int new_cols, int new_rows)
{
    int row;
    zword_t **temp;
    char **temp2, **temp3, **temp5;
    int *temp4;

    if (new_cols < 60)
    	new_cols=60;
    if (new_rows < 14)
    	new_rows=14;

    if (new_rows<h_screen_rows)
    {
        int a, b;
        a=current_seg-lower_window[current_row];
        b=(char *) current_ptr-lower_window[current_row];
        temp5=lower_window;
        temp4=lower_size;
        lower_window=calloc(new_rows, sizeof(zword_t *));
        lower_size=calloc(new_rows, sizeof(int));
        /* Delete lower window rows from the top */
        for (row=0; row < new_rows; row++)
        {
            lower_window[row]=temp5[row+h_screen_rows-new_rows];
            lower_size[row]=temp4[row+h_screen_rows-new_rows];
        }
        for (row=new_rows; row < h_screen_rows; row++)
            free(temp5[row]);
        free(temp5);
        free(temp4);
        current_row+=new_rows-h_screen_rows;
        current_seg=a+lower_window[current_row];
        current_ptr=(zword_t *) (b+lower_window[current_row]);
        /* To be neat, reset any lines in status_window */
        for (row=0; row < MIN(status_size, new_rows); row++)
            reset_line(row);

        temp=upper_text;
        temp2=upper_col;
        temp3=upper_font;
        upper_text=calloc(new_rows, sizeof(zword_t *));
        upper_col=calloc(new_rows, sizeof(zword_t *));
        upper_font=calloc(new_rows, sizeof(zword_t *));
        /* Delete upper window rows from the bottom */
        for (row=0; row < new_rows; row++)
        {
            upper_text[row]=temp[row];
            upper_col[row]=temp2[row];
            upper_font[row]=temp3[row];
        }
        for (row=new_rows; row < h_screen_rows; row++)
        {
            free(temp[row]);
            free(temp2[row]);
            free(temp3[row]);
        }
        free(temp);
        free(temp2);
        free(temp3);
        if (status_size>=new_rows)
            status_size=new_rows-1;
        if (sy>=new_rows)
            sy=new_rows-1;
    }
    else if (new_rows>h_screen_rows)
    {
        temp5=lower_window;
        temp4=lower_size;
        lower_window=calloc(new_rows, sizeof(zword_t *));
        lower_size=calloc(new_rows, sizeof(int));
        /* Add lower window rows from the bottom */
        for (row=0; row < h_screen_rows; row++)
        {
            lower_window[row]=temp5[row];
            lower_size[row]=temp4[row];
        }
        for (row=h_screen_rows; row < new_rows; row++)
        {
            lower_window[row]=(char *)malloc(4*h_screen_cols);
            lower_size[row]=4*h_screen_cols;
            reset_line(row);
        }
        free(temp5);
        free(temp4);

        temp=upper_text;
        temp2=upper_col;
        temp3=upper_font;
        upper_text=calloc(new_rows, sizeof(zword_t *));
        upper_col=calloc(new_rows, sizeof(char *));
        upper_font=calloc(new_rows, sizeof(char *));
        /* Add upper window rows from the bottom */
        for (row=0; row < h_screen_rows; row++)
        {
            upper_text[row]=temp[row];
            upper_col[row]=temp2[row];
            upper_font[row]=temp3[row];
        }
        for (row=h_screen_rows; row < new_rows; row++)
        {
            upper_text[row]=calloc(new_cols+1, sizeof(zword_t));
            upper_col[row]=malloc(new_cols+1);
            upper_font[row]=malloc(new_cols+1);
        }
        free(temp);
        free(temp2);
        free(temp3);
    }

    /* All that remains now is to stretch upper window arrays, if required */
    if (new_cols > h_screen_cols)
    {
        zword_t *t1;
        char *t2, *t3;
        for (row=0; row < MIN(h_screen_rows-1, new_rows-1); row++)
        {
            t1=upper_text[row];
            t2=upper_col[row];
            t3=upper_font[row];
            upper_text[row]=calloc(new_cols+1, sizeof(zword_t));
            upper_col[row]=malloc(new_cols+1);
            upper_font[row]=malloc(new_cols+1);
            memcpy(upper_text[row], t1, h_screen_cols * sizeof(zword_t));
            memcpy(upper_col[row], t2, h_screen_cols);
            memcpy(upper_font[row], t3, h_screen_cols);
            free(t1);
            free(t2);
            free(t3);
            if (upper_text[row][0])
            {
                int i;
                for (i = h_screen_cols-1; i < new_cols; i++)
                    upper_text[row][i] = ' ';
    	    	memset(upper_col[row]+h_screen_cols, bg<<4|fg, new_cols-h_screen_cols);
    	    	memset(upper_font[row]+h_screen_cols, TEXT_FONT, new_cols-h_screen_cols);
            }
        }
    }

    if (sy>=new_cols)
    	sy=new_cols-1;

    h_screen_rows=h_screen_height=new_rows;
    h_screen_cols=h_screen_width=new_cols;

    restart_header();

    SWidth=h_screen_cols*char_width;
    SHeight=h_screen_rows*line_height;

    SWidthOS=SWidth/400;
    SHeightOS=SHeight/400;
}

static void mark_area(int minx, int maxx, int toprow, int botrow)
{
    if (changed_box.x0 > minx)
    	changed_box.x0 = minx;
    if (changed_box.x1 < maxx)
    	changed_box.x1 = maxx;
    if (changed_box.y0 > toprow)
        changed_box.y0 = toprow;
    if (changed_box.y1 < botrow)
    	changed_box.y1 = botrow;
}

static void update_window(void)
{
    int temp;

    if (changed_box.x1==0)
    	return;

    changed_box.x0/=400;
    changed_box.x1=changed_box.x1/400+1;
    temp=changed_box.y0;
    changed_box.y0=-changed_box.y1*32-32;
    changed_box.y1=-temp*32+1;
    if (generous_redraw || in_input_line)
    {
        changed_box.y1+=16;
        generous_redraw=0;
    }
    do_update_window(&changed_box);
    changed_box.x0=SWidth;
    changed_box.x1=0;
    changed_box.y0=h_screen_rows;
    changed_box.y1=0;
}

void os_update_window(void)
{
    if (h_type==V6)
    	v6ro_update_window();
    else
    	update_window();
}

void draw_border(int rx, int ry)
{
    /* large expanse of grey */
    wimp_set_colour(wimp_COLOUR_VERY_LIGHT_GREY);
    os_plot(os_MOVE_TO, rx-32, ry+32);
    os_plot(os_PLOT_BY | os_PLOT_RECTANGLE, 31, -(SHeightOS+64));
    os_plot(os_PLOT_BY | os_PLOT_RECTANGLE, SWidthOS+32, 31);
    os_plot(os_PLOT_BY | os_PLOT_RECTANGLE, -31, SHeightOS+32);
    os_plot(os_PLOT_BY | os_PLOT_RECTANGLE, -SWidthOS, -31);

    /* 4 os units of background around edge */
    if (h_type < V6)
        colourtrans_set_gcol(palette[screen_bg], NONE, os_ACTION_OVERWRITE, NULL);
    else
        colourtrans_set_gcol(screen_bg, NONE, os_ACTION_OVERWRITE, NULL);
    os_plot(os_MOVE_TO, rx-2, ry+1);
    os_plot(os_PLOT_BY, SWidthOS+3, 0);
    os_plot(os_PLOT_BY, 0, -SHeightOS-3);
    os_plot(os_PLOT_BY, -SWidthOS-3, 0);
    os_plot(os_PLOT_BY, 0, SHeightOS+3);

    os_plot(os_MOVE_TO, rx-4, ry+3);
    os_plot(os_PLOT_BY, SWidthOS+7, 0);
    os_plot(os_PLOT_BY, 0, -SHeightOS-7);
    os_plot(os_PLOT_BY, -SWidthOS-7, 0);
    os_plot(os_PLOT_BY, 0, SHeightOS+7);

    /* thin black line */
    wimp_set_colour(wimp_COLOUR_BLACK);
    os_plot(os_MOVE_TO, rx-6, ry+5);
    os_plot(os_PLOT_BY, SWidthOS+11, 0);
    os_plot(os_PLOT_BY, 0, -SHeightOS-11);
    os_plot(os_PLOT_BY, -SWidthOS-11, 0);
    os_plot(os_PLOT_BY, 0, SHeightOS+11);

    /* white highlight - lower right */
    wimp_set_colour(wimp_COLOUR_WHITE);
    os_plot(os_MOVE_TO, rx+SWidthOS+11, ry+11);
    os_plot(os_PLOT_BY, 0, -SHeightOS-23);
    if (hiresmode)
        os_plot(os_PLOT_BY, -SWidthOS-23, 0);

    os_plot(os_MOVE_TO, rx-14, ry-SHeightOS-14);
    os_plot(os_PLOT_BY, SWidthOS+27, 0);
    os_plot(os_PLOT_BY, 0, SHeightOS+27);

    /* dark grey highlight - top left */
    wimp_set_colour(wimp_COLOUR_MID_DARK_GREY);
    os_plot(os_MOVE_TO, rx-12, ry-SHeightOS-12);
    os_plot(os_PLOT_BY, 0, SHeightOS+23);
    if (hiresmode)
        os_plot(os_PLOT_BY, SWidthOS+23, 0);

    os_plot(os_MOVE_TO, rx-14, ry-SHeightOS-14);
    os_plot(os_PLOT_BY, 0, SHeightOS+27);
    os_plot(os_PLOT_BY, SWidthOS+27, 0);
}

void redraw_window(wimp_draw *r)
{
    int toprow, bottomrow;
    int rx, ry;
    int row;
    char *seg;
    int x;
    int maxx;
    os_box box;

    if (h_type==V6)
    {
        v6ro_redraw_window(r);
        return;
    }

    /* Relative x+y offsets */
    rx=r->box.x0 - r->xscroll;
    ry=r->box.y1 - r->yscroll;

    box.x0=r->clip.x0 - rx;
    box.x1=r->clip.x1 - rx;
    box.y0=r->clip.y0 - ry;
    box.y1=r->clip.y1 - ry;


    toprow=-(box.y1*400/line_height);;
    if (toprow < 0)
    	toprow=0;

    bottomrow=-(box.y0*400/line_height);;
    if (bottomrow > h_screen_rows - 1)
        bottomrow=h_screen_rows-1;

    for (row=toprow; row<=bottomrow; row++)
    {
        /* lower window background */
        for (seg=lower_window[row]; seg_offset(seg); seg=seg_next(seg))
        {
            int bg;

            if (seg_left(seg_next(seg))==seg_left(seg) || seg_colour(seg)==0xFF)
            	continue;

            if (seg_font_attr(seg) & 0x10)
                bg=seg_colour(seg) & 0xF;
            else
                bg=seg_colour(seg) >> 4;

            if ((seg_font_attr(seg)&0xF) != GRAPHICS_FONT)
            {
                colourtrans_set_gcol(palette[bg], colourtrans_SET_BG,
                                     os_ACTION_OVERWRITE, 0);
                os_plot(os_MOVE_TO, seg_left(seg)/400+rx, ry-32*row-32);
                os_plot(os_PLOT_BG_TO | os_PLOT_RECTANGLE, seg_left(seg_next(seg))/400+rx-1,
                                                        ry-32*row-1);
            }
        }
        if (seg_colour(seg) != 0xFF)
        {
            colourtrans_set_gcol(palette[seg_colour(seg)>>4], colourtrans_SET_BG,
                                os_ACTION_OVERWRITE, 0);
            os_plot(os_MOVE_TO, seg_left(seg)/400+rx, ry-32*row-32);
            os_plot(os_PLOT_BG_TO | os_PLOT_RECTANGLE, SWidthOS+rx-1,
                                                ry-32*row-1);
        }
        /* Now the lower window text */
        for (seg=lower_window[row]; seg_offset(seg); seg=seg_next(seg))
        {
            int bg,fg,font,attr;

            if (seg_left(seg_next(seg))==seg_left(seg))
            	continue;

            font=seg_font_attr(seg) & 0xF;
            attr=seg_font_attr(seg) >> 4;

            if (font==4)
            {
            	font=TEXT_FONT;
            	attr|=FIXED_FONT;
            }
            if (attr & REVERSE)
            {
                bg=seg_colour(seg) & 0xF; fg=seg_colour(seg) >> 4;
            }
            else
            {
                fg=seg_colour(seg) & 0xF; bg=seg_colour(seg) >> 4;
            }

            if (font == TEXT_FONT)
            {
                colourtrans_set_font_colours(fontno[attr>>1],
                                             palette[bg],
                                             palette[fg],
                                             14, 0, 0, 0);
                font_paint_u(fontno[attr>>1],
                           seg_text(seg),
                           /*font_KERN*/ NONE,
                           rx*400+seg_left(seg), (ry+8-32)*400-row*line_height,
                           NULL, NULL, 0);
            }
            else if (font == GRAPHICS_FONT)
            	do_gfx(seg_text(seg), rx*400+seg_left(seg), (ry-32)*400-row*line_height,
            	       bg, fg, seg_len(seg));
        }
        /* Right, now the status win */
        if (upper_text[row][0]=='\0')
            continue;

        x=box.x0*400/char_width;
        if (x<0)
            x=0;

        maxx=box.x1*400/char_width;
        if (maxx > h_screen_cols-1)
            maxx=h_screen_cols-1;

        while (x <= maxx)
        {
            int cfont=upper_font[row][x];
            int font;
            int attr;
            int ccol=upper_col[row][x];
            int bg,fg,ox;
            unsigned temp;

            ox=x;

            while (x<=maxx && upper_font[row][x]==cfont && upper_col[row][x]==ccol)
                x++;

            if (ccol==0xFF)
                continue;

            temp=upper_text[row][x];
            upper_text[row][x]='\0';

            font=cfont & 0xF;
            attr=cfont >> 4;

    	    if (font==4)
    	    	font=TEXT_FONT;
            attr|=FIXED_FONT;

            if (attr & REVERSE)
            {
                bg=ccol & 0xF; fg=ccol >> 4;
            }
            else
            {
                fg=ccol & 0xF; bg=ccol >> 4;
            }

            if (font != GRAPHICS_FONT)
            {
            	colourtrans_set_gcol(palette[bg], colourtrans_SET_BG,
                             	    os_ACTION_OVERWRITE, 0);
            	os_plot(os_MOVE_TO, ox*16+rx, ry-32*row-32);
            	os_plot(os_PLOT_BG_TO | os_PLOT_RECTANGLE, x*16+rx-1, ry-32*row-1);
            }
            if (font == TEXT_FONT)
            {
                colourtrans_set_font_colours(fontno[attr>>1],
                                             palette[bg],
                                             palette[fg],
                                             14, 0, 0, 0);
                font_paint_u(fontno[attr>>1],
                           upper_text[row]+ox,
                           font_GIVEN_BLOCK,
                           ox*char_width+rx*400, (ry+8-32)*400-row*line_height,
                           &fblock, NULL, 0);
            }
            else if (font == GRAPHICS_FONT)
            	do_gfx(upper_text[row]+ox, ox*char_width+rx*400,
            	                            (ry-32)*400-row*line_height,
            	       bg, fg, x-ox);
            upper_text[row][x]=temp;
        }

    }

    if ((box.x0 < 0 || box.x1 > SWidthOS || box.y1 > 0 || box.y0 < - SHeightOS) && border)
        draw_border(rx, ry);
}

void load_gfx(void)
{
    int i, type, size;
    char name[12];

    type=osfile_read_stamped("<Zip2000$Dir>.Resources.Font3", NULL, NULL, &size, NULL, NULL);

    if (type!=fileswitch_IS_FILE)
    	fatal_lookup("NoFnt3");

    gfx_area=malloc(size+4);
    gfx_ptr=calloc(95, sizeof gfx_ptr[0]);

    if (!gfx_area || !gfx_ptr)
    	fatal_lookup("NoMem");

    gfx_area->size=size+4;
    gfx_area->first=16;

    osspriteop_clear_sprites(osspriteop_USER_AREA, gfx_area);

    osspriteop_load_sprite_file(osspriteop_USER_AREA, gfx_area, "<Zip2000$Dir>.Resources.Font3");

    for (i=32; i<=126; i++)
    {
        sprintf(name, "%02x", i);
        gfx_ptr[i-32]=osspriteop_select_sprite(osspriteop_USER_AREA,
                                               gfx_area,
                                               (osspriteop_id) name);
    }

    if (h_type != V6)
    	wimp_read_pix_trans(osspriteop_USER_AREA, gfx_area,
                            (osspriteop_id) "20", &factors, NULL);
}


static void do_gfx(const zword_t *text, int x, int y, int bg, int fg, int n)
{
    os_PALETTE(2) pal;
    int i;
    osspriteop_action flags;
    int ox, oy;

    if (!gfx_area)
    	load_gfx();

    pal.entries[0]=palette[bg];
    pal.entries[1]=palette[fg];

    if (os_version < RISC_OS_360)
    	flags=NONE;
    else
    	flags=colourtrans_RETURN_WIDE_ENTRIES;

    colourtrans_generate_table(0, (os_palette *) &pal,
                               os_CURRENT_MODE, colourtrans_CURRENT_PALETTE,
                               (osspriteop_trans_tab *) &trans_tab,
                               flags, NULL, NULL);

    if (os_version < RISC_OS_360)
    	flags=os_ACTION_OVERWRITE;
    else
    	flags=os_ACTION_OVERWRITE | osspriteop_GIVEN_WIDE_ENTRIES;

    font_convertto_os(x, y, &ox, &oy);
    for (i=0; i<n; i++)
    {
    	osspriteop_put_sprite_scaled(osspriteop_PTR,
                                    gfx_area,
                                    (osspriteop_id) gfx_ptr[text[i]-32],
                                    ox, oy,
                                    flags,
                                    &factors,
                                    (osspriteop_trans_tab *) &trans_tab);
        ox+=char_width/400;
    }
}

void display_char(int c)
{
    if (h_type==V6)
    {
        v6ro_display_char(c);
        return;
    }

#ifdef ALLOW_SPEECH
    if (use_speech && screen_window == TEXT_WINDOW)
    {
    	speech_buffer[speech_ptr++]=c;
    	if (speech_ptr >= SPEECHBUFSIZE)
    	    flush_speech();
    }
#endif

    if (screen_window==TEXT_WINDOW)
    {
        int currently_fixed=seg_font_attr(current_seg)>>4 & FIXED_FONT;
        int oldx;
        if (c=='\n')
        {
            scroll_line();
            return;
        }
        if ((char *) current_ptr > seg_next(current_seg) - 20)
            extend_row(current_row);

    	if (buffering==OFF && SWidth - seg_left(seg_next(current_seg)) < char_width)
    	    scroll_line();

        if (fixed_space_bit && !currently_fixed)
        {
            if (seg_len(current_seg))
            {
                current_seg=seg_insert(current_seg);
                current_ptr=seg_text(current_seg);
            }
            seg_font_attr(current_seg) |= FIXED_FONT<<4;
        }
        else if (!fixed_space_bit && !(display_attr & FIXED_FONT) && currently_fixed)
        {
            if (seg_len(current_seg))
            {
                current_seg=seg_insert(current_seg);
                current_ptr=seg_text(current_seg);
            }
            seg_font_attr(current_seg) &= ~(FIXED_FONT<<4);
        }

        #ifdef ALLOW_QUOTES
        /* Low-level hack for nice quotes */
        if (smartquotes &&
            (c=='i' || c=='l' || c=='-' || c=='\'' || c=='`' || c=='\"') &&
            (seg_font_attr(current_seg) & 0x8F) == TEXT_FONT) /* not fixed-space */
        {
            int last=*(current_ptr-1);

            if (c=='\'' && unicode_to_native(0x2018, 0))
            {
                if (seg_len(current_seg)>0 && last != ' ' && last != '(' && last != 0x201C /**/)
                    c=0x2019 /**/;
                else
                    c=0x2018 /**/;
            }
            else if (c=='`' && unicode_to_native(0x2018, 0))
                c=0x2018 /**/;
            else if (c=='"' && unicode_to_native(0x201C, 0))
            {
                if (seg_len(current_seg)>0 && last != ' ' && last != '(' && last != '[' && last != 0x2018 /**/)
                    c=0x201D /**/;
                else
                    c=0x201C /**/;
            }
            else if (seg_len(current_seg)>0 && (c=='i' || c=='-' || c=='l') && !in_input_line)
            {
                /* Check for ligatures */

                if (last=='f' && (c=='i' || c=='l') && unicode_to_native(0xFB01, 0))
                {
                    current_ptr--;
                    seg_len(current_seg)--;
                    seg_left(seg_next(current_seg))-=fwidth[seg_font_attr(current_seg)>>5]['f'];
                    c= c=='i' ? 0xFB01 : 0xFB02;
                }
                else if ((last=='-' || last==0x2013) && c=='-' && unicode_to_native(0x2014, 0))
                {
                    current_ptr--;
                    seg_len(current_seg)--;
                    seg_left(seg_next(current_seg))-=fwidth[seg_font_attr(current_seg)>>5]['-'];
                    c=0x2014;
                }
                else if (last==' ' && c=='-' && unicode_to_native(0x2013, 0))
                    c=0x2013;
            }
        }
        #endif
    	*current_ptr++=c;
    	*current_ptr='\0';
    	seg_len(current_seg)++;
    	oldx=seg_left(seg_next(current_seg));
    	if ((seg_font_attr(current_seg) & 0xF) != TEXT_FONT)
    	    seg_left(seg_next(current_seg))+=char_width;
    	else
    	    seg_left(seg_next(current_seg))+=fwidth[seg_font_attr(current_seg)>>5][unicode_to_native(c, '?')];

    	/* Allow a little extra for overhanging "f"s and underhanging "j"s, but don't
    	   overflow onto border - this will cause the whole border to get replotted
    	   - slow */
    	mark_area(MAX(oldx-2048,0), MIN(seg_left(seg_next(current_seg))+2048, SWidth),
    	          current_row, current_row);
    }
    else
    {
        if (h_type==V6)
            return;

        if (c=='\n')
        {
            sx=0;
            if (sy<status_size-1)
            	sy++;
            return;
        }
    	if (upper_text[sy][0]==0)
    	{
    	    int i;
    	    for (i=h_screen_cols-1; i>=0; i--)
    	        upper_text[sy][i] = ' ';
    	    memset(upper_col[sy], 0xFF, h_screen_cols);
    	    memset(upper_font[sy], TEXT_FONT, h_screen_cols);
    	}
    	#ifdef ALLOW_QUOTES
        /* Low-level hack for nice quotes */
        if ((c=='\'' || c=='`' || c=='"')
            && display_font == TEXT_FONT
            && sx>0)
        {
            if (c=='\'' && unicode_to_native(0x2018, 0))
            {
                if (upper_text[sy][sx-1] != ' ' && upper_text[sy][sx-1] != '(')
                    c=0x2019 /**/;
                else
                    c=0x2018 /**/;
            }
            else if (c=='`' && unicode_to_native(0x2018, 0))
                c=0x2018 /**/;
            else if (c=='"' && unicode_to_native(0x201C, 0))
            {
                if (upper_text[sy][sx-1] != ' ' && upper_text[sy][sx-1] != '(')
                    c=0x201D /**/;
                else
                    c=0x201C /**/;
            }
        }
        #endif
    	upper_text[sy][sx]=c;
    	upper_font[sy][sx]=display_attr<<4 | display_font;
    	upper_col[sy][sx]=bg<<4 | fg;
    	mark_area(sx*char_width, (sx+1)*char_width, sy, sy);
    	sx++;

    	if (sx==h_screen_cols)
    	{
    	    if (sy<status_size-1)
    	    {
    	        sx=0; sy++;
    	    }
    	    else
    	    	sx--;
    	}
    }

    /* Allow for accented chars by marking a "generous redraw" - redraw a
       bit above the current line */
    if (c >= 192)
        generous_redraw=TRUE;
}

void display_string(const zword_t *s)
{
    while (*s)
        display_char(*s++);
}


static void init_screen_arrays(void)
{
    int i;

    upper_text=calloc(h_screen_rows, sizeof(char *));
    upper_col=calloc(h_screen_rows, sizeof(char *));
    upper_font=calloc(h_screen_rows, sizeof(char *));
    lower_window=calloc(h_screen_rows, sizeof(char *));
    lower_size=calloc(h_screen_rows, sizeof(int));

    for (i=0; i<h_screen_rows; i++)
    {
        upper_text[i]=calloc(h_screen_cols+1, sizeof(zword_t));
        upper_text[i][h_screen_cols]=0;
        upper_col[i]=malloc(h_screen_cols+1);
        upper_font[i]=malloc(h_screen_cols+1);
        lower_window[i]=malloc(h_screen_cols*4);
        lower_size[i]=h_screen_cols*4;
    }
}

//#pragma check_stack

/*
 * Routines to find a font. First try it specifying the encoding, then without
 * encoding.
 */
static font_f find_font(const char *font_name, int xsize, int ysize,
                             int xres, int yres, int *xres_out, int *yres_out)
{
    char buffer[128];
    font_f f;

    font_apply_fields(font_name, encoding_name, buffer, sizeof buffer);

    if (xfont_find_font(buffer, xsize, ysize, xres, yres, &f, xres_out, yres_out))
    {
        if (font_find_field(font_name, 'E', NULL))
            font_apply_fields(font_name, "\\E", buffer, sizeof buffer);
        else
            strcpy(buffer, font_name);

        f = font_find_font(buffer, xsize, ysize, xres, yres, xres_out, yres_out);
    }

    return f;
}

static os_error *xfind_font(const char *font_name, int xsize, int ysize,
                            int xres, int yres, font_f *font,
                            int *xres_out, int *yres_out)
{
    char buffer[128];
    os_error *e;
    int have_e;

    e = xfont_apply_fields(font_name, encoding_name, buffer, sizeof buffer, NULL);
    if (e) return e;

    if (xfont_find_font(buffer, xsize, ysize, xres, yres, font, xres_out, yres_out))
    {
        e = xfont_find_field(font_name, 'E', NULL, &have_e);
        if (e) return e;

        if (have_e)
        {
            e = xfont_apply_fields(font_name, "\\E", buffer, sizeof buffer, NULL);
            if (e) return e;
        }
        else
            strcpy(buffer, font_name);

        e = xfont_find_font(buffer, xsize, ysize, xres, yres, font, xres_out, yres_out);
        if (e) return e;
    }

    return NULL;
}

static font_f modified_font(char *orig, char *mod,
                            int xsize, int ysize, int xres, int yres)
{
    char buffer[128];
    char origname[128];
    char buffer2[128];
    os_error *e=NULL;
    font_f f;
    bool found;
    char *p, *p2;
    char *lastdot;

    found=font_find_field(orig, 'F', &p);

    if (!found)
    	p=orig;

    p2=origname;
    while (*p>32 && *p!='\\')
        *p2++=*p++;
    *p2='\0';

    lastdot=strrchr(origname, '.');

    p=strtok(mod, " ");

    while (p)
    {
        sprintf(buffer, "\\F%s.%s\\f", origname, p);
        font_apply_fields(orig, buffer, buffer2, sizeof buffer2);
        e=xfind_font(buffer2, xsize, ysize, xres, yres, &f, NULL, NULL);
        if (e==NULL)
            break;

        if (lastdot)
        {
            *lastdot='\0';
            sprintf(buffer, "\\F%s.%s\\f", origname, p);
            *lastdot='.';
            font_apply_fields(orig, buffer, buffer2, sizeof buffer2);
            e=xfind_font(buffer2, xsize, ysize, xres, yres, &f, NULL, NULL);
            if (e==NULL)
                break;
        }
        p=strtok(NULL, " ");
    }

    if (e)
    	f=find_font(orig, xsize, ysize, xres, yres, 0, 0);

    return f;
}

void setup_mapping_tables(void)
{
    char buffer[256];
    char buffer2[64];
    int i, n;

    /* Load the encoding table for the current font */
    sprintf(buffer, "<Zip2000$Dir>.Resources.Encodings.%s", encoding_name+2);
    osfile_load_stamped_no_path(buffer, (byte *) native_to_unicode_table,
                                NULL, NULL, NULL, NULL);

    /*
     * And the table for the keyboard. How best to do this? I suppose
     * we want to look at the current system alphabet setting.
     */
    system_alphabet_no = osbyte1(osbyte_ALPHABET_NUMBER, 127, 0);
    if (serviceinternational_alphabet_number_to_alphabet_name(system_alphabet_no,
                     buffer2, sizeof buffer2, &i) == 0)
        buffer2[i] = '\0';
    else
        strcpy(buffer2, "Latin1");

    sprintf(buffer, "<Zip2000$Dir>.Resources.Encodings.%s", buffer2);

    if (xosfile_load_stamped_no_path(buffer, (byte *) system_to_unicode_table,
                                     NULL, NULL, NULL, NULL, NULL))
        for (i=0; i<=127; i++)
            system_to_unicode_table[i] = i+128;

    /*
     * Set up the mapping table from ZSCII to our encoding. Note the
     * firm assumption that the lower set of our encoding is ASCII/ZSCII.
     */
    memset(zscii_to_native_table, 0, 256);
    for (i=32; i<=126; i++)
    	zscii_to_native_table[i] = i;

    for (i=155; i<155+zscii_to_unicode_table_size; i++)
    {
        int u = zscii_to_unicode_table[i-155];

        if (u < 0x0020 || u == 0x007F || u == 0xFFFF)
            continue;

        for (n=128; n<=255; n++)
            if (native_to_unicode_table[n-128] == u)
            {
                zscii_to_native_table[i] = n;
                break;
            }
    }

    /*
     * And do the table from the keyboard to ZSCII.
     */
    memset(system_to_zscii_table, 0, sizeof system_to_zscii_table);
    system_to_zscii_table[8] = 8;
    system_to_zscii_table[10] = NEWLINE;
    system_to_zscii_table[13] = NEWLINE;
    system_to_zscii_table[21] = key_CTRL_U;
    system_to_zscii_table[27] = 27;
    for (i=32; i<=126; i++)
        system_to_zscii_table[i] = i;
    system_to_zscii_table[127] = 8;

    /*
     * Assumption here is that chars 0-127 of the system alphabet are ASCII,
     * and that chars 128-255 do not contain any ASCII chars.
     */
    for (i=128; i<=255; i++)
    {
        if (system_to_unicode_table[i-128] != 0xFFFF)
            for (n=155; n<155+zscii_to_unicode_table_size; n++)
                if (system_to_unicode_table[i-128] == zscii_to_unicode_table[n-155])
                {
                    system_to_zscii_table[i] = n;
                    break;
                }
    }
}

void find_fonts(void)
{
    int i, xr, yr, c;
    char buffer[256];
    int xwidth;

    for (i=0; i<=F_LAST; i++)
    	if (fontno[i])
    	    font_lose_font(fontno[i]);

    xr=yr=180;

    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_XEIG_FACTOR, &i);
    xr >>= i;

    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR, &i);
    yr >>= i;

    hiresmode=(i<=1);

    fontno[F_MEDIUM]=find_font(textfont_name,
                                    12*16, 12*16, xr, yr, NULL, NULL);

    fontno[F_BOLD]=modified_font(textfont_name, getenv("Font$Bold"),
                                 12*16, 12*16, xr, yr);

    fontno[F_ITALIC]=modified_font(textfont_name, getenv("Font$Italic"),
                                   12*16, 12*16, xr, yr);

    font_read_identifier(fontno[F_BOLD], (byte *) buffer);

    fontno[F_BOLDITALIC]=modified_font(buffer, getenv("Font$Italic"),
                                       12*16, 12*16, xr, yr);

    fontno[F_FIXED]=find_font(fixedfont_name,
                                   12*16, 12*16, xr, yr, NULL, NULL);

    /* Have to work out required width of font(!). Yuck */
    font_scan_string(fontno[F_FIXED], "0", font_GIVEN_FONT, -1, -1, NULL, NULL, 0,
                     NULL, &xwidth, NULL, NULL);
    /* OK, xwidth now equals width of 12pt font in millipoints. We need the width in
       millipoints to be roughly char_width */
    font_lose_font(fontno[F_FIXED]);

    xwidth=(int)((double)(12*16*char_width)/xwidth+0.5);

    fontno[F_FIXED]=find_font(fixedfont_name,
                                   xwidth, 12*16, xr, yr, NULL, NULL);

    fontno[F_FIXEDBOLD]=modified_font(fixedfont_name, getenv("Font$Bold"),
                                       xwidth, 12*16, xr, yr);
    fontno[F_FIXEDITALIC]=modified_font(fixedfont_name, getenv("Font$Italic"),
                                        xwidth, 12*16, xr, yr);

    font_read_identifier(fontno[F_FIXEDBOLD], (byte *) buffer);

    fontno[F_FIXEDBOLDITALIC]=modified_font(buffer, getenv("Font$Italic"),
                                            xwidth, 12*16, xr, yr);

    buffer[1]='\0';

    for (i=F_MEDIUM; i<=F_LAST; i++)
    {
        int misc[32];
        int xpres;
    	font_read_font_metrics(fontno[i], 0, (font_width_info *)fwidth[i],
    	                                  0, (font_misc_info *)misc,
    	                       0, NULL, NULL, &xpres, NULL, NULL, NULL);

    	if (!xpres)
    	    for (c=0; c<=255; c++)
    	    	fwidth[i][c]=misc[4];
    }

    /* Work out extra inter-letter spacing for fixed space font */

    fblock.space.x=fblock.space.y=0;
    fblock.letter.x=char_width-fwidth[F_FIXED]['0'];
    fblock.letter.y=0;

}

//#pragma no_check_stack

void initialize_screen(void)
{
    int temp;

    os_version=osbyte1(129, 0, 0xFF);

    if (h_type != V6)
    {
    	SWidth=h_screen_cols*char_width;
    	SHeight=h_screen_rows*line_height;
    }
    else
    {
    	SWidth=16*80*400;
    	SHeight=32*25*400;
    }

    SWidthOS=SWidth/400;
    SHeightOS=SHeight/400;

    #ifdef ALLOW_RECALL
    line_buffer[0]=malloc(1);
    line_buffer[0][0]='\0';
    #endif

    fg=default_fg-2; bg=default_bg-2;

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

    /* This sets up fonts for the first time */
    modechange_handler(NULL, NULL);

    init_screen_arrays();

#ifdef ALLOW_SPEECH
    use_speech = xspch_ready(NULL)==NULL;
#endif

    /* Hacky bootstrap problem - can't set_colours until have a current_seg
       Can't have current_seg until cleared screen. Can't kill screen until
       colours are chosen */
    clear_screen();
    set_colours(1, 1, -1);
    display_font=TEXT_FONT;

    initialise_wimp();
    /* Major hackery !!!! */
    temp=screen_window;
    sy= (h_screen_rows >> 1) - 1;   /* Not /2 to avoid compiler bug */
    sx= (h_screen_cols - strlen(msgs_lookup("Loading"))) / 2 - 1;
    screen_window=STATUS_WINDOW;
    display_string(msgs_lookup_u("Loading"));
    screen_window=temp;
    update_window();

}



void restart_screen(void)
{
    cursor_saved = OFF;

    if (h_type == V3)
    {
    	h_config |= CONFIG_WINDOWS | CONFIG_VARIABLE;
    	h_config &=~ CONFIG_NOSTATUSLINE;
    }

    if (h_type >= V4)
    	h_config |= CONFIG_BOLD | CONFIG_EMPHASIS | CONFIG_FIXED | CONFIG_TIMED_INPUT;

    if (h_type >= V5)
    	h_config |= CONFIG_COLOUR;

    if (h_type >= V6)
    	h_config |= CONFIG_GRAPHICS | CONFIG_SOUND;

    using_mouse=h_flags & MOUSE_FLAG;

    setup_mouse(using_mouse);
    setup_sound((h_flags & SOUND_FLAG) == SOUND_FLAG);

    /* We can do everything, so don't need to alter h_flags */

    /* Set default text colours */
    h_default_bg_col = default_bg;
    h_default_fg_col = default_fg;

    if (h_type < V6)
    {
        h_screen_width = h_screen_cols;
        h_screen_height = h_screen_rows;
        h_char_width = 1;
        h_char_height = 1;
    }
    else
    {
        h_screen_cols = 80;
        h_screen_rows = 25;
        h_screen_width = 80 * V6_CHAR_W;
        h_screen_height = 25 * V6_CHAR_H;
        h_char_width = V6_CHAR_W;
        h_char_height = V6_CHAR_H;
    }

    /* Set the title bar */
    SetTitle();
}

void reset_screen(void)
{
    int i;

    for (i=0; i<=F_LAST; i++)
    	font_lose_font(fontno[i]);

    lose_effects();
}

static void reset_line(int i)
{
    seg_offset(lower_window[i])=lower_size[i]-16;
    seg_colour(lower_window[i])=bg<<4 | fg;
    seg_font_attr(lower_window[i])=display_attr<<4 | display_font;
    seg_left(lower_window[i])=0;
    seg_len(lower_window[i])=0;
    seg_text(lower_window[i])[0]='\0';
    seg_offset(seg_next(lower_window[i]))=0;
    seg_colour(seg_next(lower_window[i]))=bg<<4 | fg;
    seg_left(seg_next(lower_window[i]))=0;
}


void clear_screen(void)
{
    int i;

    for (i=0; i<h_screen_rows; i++)
    {
        upper_text[i][0]='\0';
        reset_line(i);
    }

    if (bg==screen_bg)
        mark_area(0, SWidth, 0, h_screen_rows-1);
    else
        /* Need to redraw "edge" */
        mark_area(-4*400, SWidth+4*400, -1, h_screen_rows);

    screen_bg=bg;
    update_window();

    current_seg=lower_window[current_row];
    current_ptr=seg_text(current_seg);
}

void select_status_window(void)
{
    update_window();
}

void select_text_window(void)
{
    update_window();
    /* Make sure any changes in upper window are reflected */
    set_attribute(display_attr);
}

void create_status_window(void)
{
    int i, x;

    for (i=0; i<status_size; i++)
    	if (seg_offset(seg_next(lower_window[i]))==0 &&
    	          seg_left(seg_next(lower_window[i]))==0)
    	{
    	    seg_colour(seg_next(lower_window[i]))=0xFF;
    	    if (upper_text[i][0]=='\0')
            	for (x=0; x<h_screen_cols; x++)
            	{
            	    upper_text[i][x]=' ';
            	    upper_col[i][x]=bg << 4 | fg;
            	    upper_font[i][x]=TEXT_FONT;
            	}
            else
            	for (x=0; x<h_screen_cols; x++)
            	{
            	    if (upper_col[i][x]==0xFF)
            	    {
            	    	upper_text[i][x]=' ';
            	    	upper_col[i][x]=bg << 4 | fg;
            	    	upper_font[i][x]=TEXT_FONT;
            	    }
            	}
        }

    /* Check we don't swallow the cursor (8.7.2.2) */
    if (current_row<status_size)
    {
        current_row=status_size;
        if (current_row >= h_screen_rows)
            current_row=h_screen_rows-1;
        current_seg=lower_window[current_row];
        current_ptr=seg_text(current_seg);
    }

}

#if 0
void delete_status_window(void)
{
}
#endif

void clear_line(void)
{
    int x;

    if (screen_window==TEXT_WINDOW)
    {
        seg_colour(seg_next(current_seg))=bg << 4 | fg;
        mark_area(seg_left(seg_next(current_seg)), SWidth, current_row, current_row);
    }
    else
    {
        for (x=sx; x<h_screen_cols; x++)
        {
            upper_text[sy][x]=' ';
            upper_col[sy][x]=bg << 4 | fg;
            upper_font[sy][x]=TEXT_FONT;
        }
        upper_text[sy][h_screen_cols]='\0';
        mark_area(sx*char_width, SWidth, sy, sy);
        if (sx==0)
        {
            /* We've cleared a whole line - let's hack and delete the underlying
               text window line! */
            reset_line(sy);
            seg_colour(seg_next(lower_window[sy]))=0xFF;
        }
    }
}

void clear_text_window(void)
{
    int i;

    for (i=status_size; i<h_screen_rows; i++)
    {
        upper_text[i][0]='\0';
        reset_line(i);
    }

    if ((status_size==0 || h_type<V5) && bg!=screen_bg)
    {
    	screen_bg=bg;
        mark_area(-4*400, SWidth+4*400, -1, h_screen_rows);
    }
    else
    	mark_area(0, SWidth, status_size, h_screen_rows-1);

    update_window();

    if (h_type > V4)
    	current_row=status_size;
    else
    	current_row=h_screen_rows-1;

    current_seg=lower_window[current_row];
    current_ptr=seg_text(current_seg);
}

void clear_status_window(void)
{
    int i, x;

    for (i=0; i<status_size; i++)
    {
        for (x=0; x<h_screen_cols; x++)
        {
            upper_text[i][x]=' ';
            upper_col[i][x]=bg << 4 | fg;
            upper_font[i][x]=TEXT_FONT;
        }
        upper_text[i][h_screen_cols]='\0';
        reset_line(i);
        seg_colour(seg_next(lower_window[i]))=0xFF;
    }
    mark_area(0, SWidth, 0, status_size-1);
    update_window();
    if (screen_window==STATUS_WINDOW)
        sx=sy=0;
}

void move_cursor(int row, int col)
{
    if (h_type == V6)
    {
        v6ro_move_cursor(row, col);
        return;
    }

    if (screen_window==STATUS_WINDOW)
    {
        if (row-1 < sy || (row-1==sy && col-1<sx))
            update_window();

    	sx=col-1;
    	sy=row-1;
    }

    if (screen_window==TEXT_WINDOW && col==1)
    {
        reset_line(row-1);
        current_seg=lower_window[row-1];
        current_row=row-1;
        current_ptr=seg_text(current_seg);
        update_window();
    }
}

void get_cursor_position(int *row, int *col)
{
    if (h_type==V6)
    {
        v6ro_get_cursor_position(row, col);
        return;
    }

    if (screen_window == TEXT_WINDOW)
    {
        *row = current_row+1;
        *col = seg_left(seg_next(current_seg))/char_width + 1;
    }
    else
    {
        *row=sy+1;
        *col=sx+1;
    }
}

void set_attribute(int attribute)
{
    if (attribute==NORMAL)
    {
        display_attr=0;
    }
    else
    	display_attr|=attribute;

    if (h_type==V6)
    {
        v6ro_set_attribute(display_attr);
        return;
    }

    if (screen_window==TEXT_WINDOW)
    {
        if (attribute==NORMAL && seg_font_attr(current_seg)>>4!=0 ||
            attribute!=NORMAL && (seg_font_attr(current_seg)>>4 & attribute)!=attribute)
            if (seg_len(current_seg))
            {
            	current_seg = seg_insert(current_seg);
            	current_ptr = seg_text(current_seg);
            }

        if (attribute==NORMAL)
            seg_font_attr(current_seg)&=0x0F;
        else
            seg_font_attr(current_seg)|=attribute<<4;
    }
}

void optimise_status_line(void)
{
    int row;
    int left, right, x;
    bool remove;
    char *seg;

    for (row=0; row < status_size; row++)
    {
        if (upper_text[row][0]=='\0')
            continue;

        seg=lower_window[row];
        for (;;)
        {
            left=seg_left(seg)/char_width;
            if (seg_offset(seg))
            	right=seg_left(seg_next(seg))/char_width;
            else
            	right=h_screen_cols-1;

            remove=TRUE;
            for (x=left; x <= right; x++)
            {
                if (upper_col[row][x]==0xFF)
                {
                    remove=FALSE;
                    break;
                }
            }
            if (remove)
            	seg_colour(seg)=0xFF;

            if (seg_offset(seg)==0)
            	break;

            seg=seg_next(seg);
        }
    }
}

void complete_redraw(void)
{
    wimp_window_state ws;

    ws.w=ScreenW;
    wimp_get_window_state(&ws);
    if (!(ws.flags & wimp_WINDOW_NOT_COVERED))
    {
        wimp_event_no e;
        claim_null_events();
        do
        {
            poll(&e, 0, 0);
        } while (e==wimp_REDRAW_WINDOW_REQUEST);
        release_null_events();
    }
}

void scroll_line(void)
{
#ifdef ALLOW_SPEECH
    if (use_speech && screen_window == TEXT_WINDOW)
    {
    	speech_buffer[speech_ptr++]=' ';
    	if (speech_ptr >= SPEECHBUFSIZE)
    	    flush_speech();
    }
#endif

    if (current_row == h_screen_rows-1)
    {
        int row;
        char *temp1, *temp3, *temp4;
        int temp5;
        zword_t *temp2;

        update_window();
        temp1=lower_window[status_size];
        temp5=lower_size[status_size];
        temp2=upper_text[status_size];
        temp3=upper_col[status_size];
        temp4=upper_font[status_size];
        for (row=status_size; row<h_screen_rows-1; row++)
        {
            lower_window[row]=lower_window[row+1];
            lower_size[row]=lower_size[row+1];
            upper_text[row]=upper_text[row+1];
            upper_col[row]=upper_col[row+1];
            upper_font[row]=upper_font[row+1];
        }
        lower_window[h_screen_rows-1]=temp1;
        lower_size[h_screen_rows-1]=temp5;
        upper_text[h_screen_rows-1]=temp2;
        upper_col[h_screen_rows-1]=temp3;
        upper_font[h_screen_rows-1]=temp4;
    	reset_line(h_screen_rows-1);
    	upper_text[h_screen_rows-1][0]='\0';

    	current_seg=lower_window[h_screen_rows-1];
        current_ptr=seg_text(current_seg);
        wimp_block_copy(ScreenW,
                        0,
                        -SHeightOS,
                        SWidthOS,
                        -32*status_size-32,
                        0,
                        -(SHeight-line_height)/400);
        mark_area(0, SWidth, h_screen_rows-1, h_screen_rows-1);
        update_window();
    	complete_redraw();
    }
    else
    {
        update_window();
        current_row++;
        current_seg=lower_window[current_row];
        current_ptr=seg_text(current_seg);
        seg_colour(current_seg)=bg<<4 | fg;
        seg_font_attr(current_seg)=display_attr<<4 | display_font;
    }
}

void place_caret(void)
{
    int x,y;

    if (!have_input_focus)
    	return;

    if (!(game_wants_caret && caret_enabled))
    {
        if (h_type==V6)
            switchoutput(-1);
        wimp_set_caret_position(ScreenW, wimp_ICON_WINDOW, 0, 0, 1<<25, 0);
        if (h_type==V6)
            switchoutput(+1);
        return;
    }

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

    if (screen_window==TEXT_WINDOW)
    {
        if (in_input_line)
            x=input_x;
        else
            x=seg_left(seg_next(current_seg));
        y=(current_row+1)*line_height;
    }
    else
    {
        if (in_input_line)
            x=input_x;
        else
            x=sx*char_width;
        y=(sy+1)*line_height;
    }

    x/=400;
    y/=-400;

    wimp_set_caret_position(ScreenW, wimp_ICON_WINDOW, x, y, line_height/400, 0);
}

void set_colours(int foreground, int background, int window)
{
    if (foreground==0)
        foreground=fg+2;

    if (background==0)
        background=bg+2;

    if (foreground==1)
    	foreground=default_fg;

    if (background==1)
    	background=default_bg;

    if (h_type==V6)
    {
        /*if (foreground == -1)
            foreground=v6ro_get_colour()+2;
        if (background == -1)
            background=v6ro_get_colour()+2;*/
        v6ro_set_colours(foreground-2, background-2, window);
        return;
    }

    fg=foreground-2;
    bg=background-2;

    if (screen_window==TEXT_WINDOW)
    {
        if (seg_len(current_seg)!=0 && seg_colour(current_seg) != (bg<<4 | fg))
        {
            current_seg=seg_insert(current_seg);
            current_ptr=seg_text(current_seg);
        }

        seg_colour(current_seg)=bg<<4 | fg;
    }
}

/*
 * Translate a single ZSCII character, known to be >127, into a string
 * in the machine alphabet (Latin-1). s contains "?" initially - this
 * can be left untouched.
 */
void codes_to_text(unsigned c, char *s)
{
    if (c < 256 && zscii_to_native_table[c])
        *s = zscii_to_native_table[c];
}

int text_length(const zword_t *line_buffer, int len, int font, int style, int fixed)
{
    int i, c, pos=0;

    for (i=0; i<len; i++)
    {
    	c=unicode_to_native(line_buffer[i], '?');
    	if (c>=32)
        {
            if (font==1 && !fixed)
            	pos+=fwidth[style][c];
            else
            	pos+=char_width;
        }
        else
        {
            if (c==1)
                style=F_MEDIUM;
            else if (c>=2 && c<=9)
                style|=(c-1)>>1;
            else if (c>=14 && c<=17)
            	font=c-13;
            else if (c==18)
            	fixed=OFF;
            else if (c==19)
            	fixed=ON;
        }
    }
    return pos;
}

/*
 * fit_word
 *
 * This routine determines whether a word of text will fit
 * on the screen.
 *
 * line : Line of text to test.
 *
 * Contains attribute codes - need to be placed into strings.
 */

int fit_word(const zword_t *line_buffer, int len)
{
    int pos;
    int font, style, fixed;
    int width;

    if (h_type==V6)
    	return v6ro_fit_word(line_buffer, len);

    /* Can't scroll if in lower line of status window --
       just let it keep going - will be stopped by display_char */
    if (screen_window==STATUS_WINDOW && sy==status_size-1)
    	return TRUE;

    if (screen_window==STATUS_WINDOW)
        return sx+len <= h_screen_cols;

    /* Right, we know we're in the text window */
    pos=seg_left(seg_next(current_seg));

    style=seg_font_attr(current_seg)>>5;
    font=seg_font_attr(current_seg)&0xF;
    fixed=fixed_space_bit;

    width=text_length(line_buffer, len, font, style, fixed);

    return (pos+width <= SWidth);
}


/*
 * set_font
 *
 * Set a new character font. Font can be either be:
 *
 *    TEXT_FONT (1)     = normal text character font
 *    GRAPHICS_FONT (3) = graphical character font
 *
 */

void set_font(int font_type)
{
    if (h_type==V6)
    {
        v6ro_set_font(font_type);
        return;
    }

    display_font=font_type;

    if (screen_window==TEXT_WINDOW)
    {
        if (seg_len(current_seg)!=0)
        {
            current_seg=seg_insert(current_seg);
            current_ptr=seg_text(current_seg);
        }

        seg_font_attr(current_seg)&=0xF0;
        seg_font_attr(current_seg)|=font_type;
    }
}

void z_read_mouse(int table)
{
    set_word(table, mouse_y);
    set_word(table+2, mouse_x);
    set_word(table+4, mouse_butt);
    set_word(table+6, (last_menu<<8)+last_item);
}

void translate_name(char *name)
{
    char temp[FILENAME_MAX+1];
    char *p;

    strcpy(temp, name);

    strcpy(name, "<Zip2000$Dir>.Resources.Files.");

    for (p=temp; *p; p++)
    	*p=toupper(*p);

    p=strchr(temp, '.');

    if (p==0)
    	strcat(temp, ".AUX");

    p=strchr(temp, '.');

    strcat(name, p+1);

    xosfile_create_dir(name, 0);

    *p='\0';
    strcat(name, ".");
    strcat(name, temp);
}

static void relativise_filename(char *name, const char *base)
{
    char temp[FILENAME_MAX+1];

    if (strchr(name, '.') || strchr(name, ':'))
        /* Assume it's a full pathname */
        return;

    /* Stick the name given onto the base name (canonicalised) */

    sprintf(temp, "%s.%s", base, name);

    osfscontrol_canonicalise_path(temp, name, 0, 0, FILENAME_MAX+1);
}

/*
 * get_file_name
 *
 * Return the name of a file. Flag can be one of:
 *    GAME_SAVE    - Save file (write only)
 *    GAME_RESTORE - Save file (read only)
 *    GAME_SCRIPT  - Script file (write only)
 *    GAME_RECORD  - Keystroke record file (write only)
 *    GAME_PLAYBACK - Keystroke record file (read only)
 *
 */

int get_file_name (zword_t *file_name, zword_t *default_name, int flag)
{
    char buffer[FILENAME_MAX+1]; /* 127 is the biggest positive char */
    char buffer2[FILENAME_MAX+1];
    int status = 0;
    int terminator;

    #ifndef ALLOW_V6
    if (h_type == V6)
    	os_reset();
    #endif

    /* If no default file name then supply the standard name */

    if (default_name[0] == '\0') {
        if (flag == GAME_SCRIPT)
            strcpy_u (default_name, SCRIPT_NAME);
        else if (flag == GAME_RECORD || flag == GAME_PLAYBACK)
            strcpy_u (default_name, RECORD_NAME);
        else /* (flag == GAME_SAVE || flag == GAME_RESTORE) */
            strcpy_u (default_name, SAVE_NAME);
    }

    /* Prompt for the file name */

    output_line(msgs_lookup_u("EnterFName"));
    output_string(msgs_lookup_u("DefltIs"));
    /*{
        static const zword_t open_quote[] =
        {
            #ifdef ALLOW_QUOTES
            0x201C,
            #else
            '"',
            #endif
            0
        };
        output_string (open_quote);
    }*/
    {
        static const zword_t ZipSave[] = { 'Z','i','p','S','a','v','e',':',0 };
        output_string (strstr_u(default_name, ZipSave)==default_name ? default_name+8 :
                                                                       default_name);
    }
    {
        static const zword_t close_quote[] =
        {
            /*#ifdef ALLOW_QUOTES
            0x201D,
            #else*/
            '"',
            /*#endif*/
            ')',' ',0
        };
        output_string (close_quote);
    }

    file_name[0]='\0';

    do
    {
        terminator=get_line(FILENAME_MAX, file_name, 0, 0);

        if (terminator != 13 && terminator != -1)
            beep();
    }
    while (terminator != 13 && terminator != -1);

    output_new_line();

    if (terminator == -1)
    	return 1;

    /* If nothing typed then use the default name */

    if (file_name[0] == '\0')
        strcpy_u(file_name, default_name);

    /* Record the file name if it was OK */

    if (status == 0 && recording && !replaying)
        record_line(file_name, 13);

    unicode_string_to_system(buffer2, file_name, '?');

    if (!(flag==GAME_SAVE || flag==GAME_RESTORE))
    	relativise_filename(buffer2, "<Zip2000$Dir>.^");
    else
    {
        if (!strchr(buffer2, '.') && !strchr(buffer2, ':'))
        {
            strcpy(buffer, buffer2);
            sprintf(buffer2, "ZipSave:%s", buffer);
        }
    }

    system_string_to_unicode(file_name, buffer2, '?');

    /* Check if we are going to overwrite the file */

    if (warn_overwrite && (flag==GAME_SAVE || flag==GAME_RECORD))
    {
        FILE *tfp;
        char c, *yn;

        /* Try to access the file */

        tfp = fopen(buffer2, "r");
        if (tfp)
        {
            fclose(tfp);

            /* If it succeeded then prompt to overwrite */

            output_line(msgs_lookup_u("OverWrt"));
            output_string(msgs_lookup_u("Proceed"));

    	    yn=msgs_lookup("YesNo");

            do
            {
                c = input_character(0);
                c = tolower(c);
            } while (c != yn[0] && c != yn[1]);

            output_char(c);
            output_new_line();

            /* If no overwrite then fail the routine */

            if (c == yn[1]) /* No */
                status = 1;
        }
    }

    return status;

}/* get_file_name */

/*void ro_print_error(void)
{
    output_line(_kernel_last_oserror()->errmess);
}*/

void v5ro_input_blank(const zword_t *buffer)
{
    int length;

    length=strlen_u(buffer);

    if (screen_window==STATUS_WINDOW)
    {
        int oldx=sx;

        while (length--)
            upper_text[sy][--sx]=' ';

        mark_area(sx*char_width, (oldx+1)*char_width, sy, sy);
    }
    else
    {
        int attr;
        int old, new;
        attr=seg_font_attr(current_seg);
        old=seg_left(seg_next(current_seg));
        new=old;
        while (length--)
            new -= fwidth[attr>>1][unicode_to_native(*(--current_ptr), '?')];

        seg_left(seg_next(current_seg))=new;
        seg_len(current_seg)=current_ptr-seg_text(current_seg);
        *current_ptr='\0';
        mark_area(new-2048, old+2048, current_row, current_row);
    }
}

void v5ro_input_place_caret(const zword_t *buffer, int left, int pos)
{
    if (screen_window==STATUS_WINDOW)
        input_x=(left+pos)*char_width;
    else
    {
        int attr, i;
        const zword_t *lc;

    	lc=seg_text(current_seg)+seg_len(current_seg)-strlen_u(buffer);

        attr=seg_font_attr(current_seg);

        input_x=left;

        for (i=0; i<pos; i++)
            input_x+=fwidth[attr>>1][unicode_to_native(lc[i], '?')];
    }

    place_caret();
}

int v5ro_input_get_left(const zword_t *buffer)
{
    int left, i;
    int length=strlen_u(buffer);

    if (screen_window==STATUS_WINDOW)
    	left=sx-length;
    else
    {
        int attr=seg_font_attr(current_seg);

        left=seg_left(seg_next(current_seg));
        for (i=length; i>0; i--)
            left-=fwidth[attr>>1][unicode_to_native(*(current_ptr-i), '?')];
    }

    return left;
}

int v5ro_input_will_fit(const zword_t *buffer, unsigned u_char)
{
    int attr=seg_font_attr(current_seg);
    int right, cwidth;

    NOT_USED(buffer);

    right=seg_left(seg_next(current_seg));

    cwidth=fwidth[attr>>1][unicode_to_native(u_char, '?')];

    return right+cwidth <= SWidth;
}
