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

#include "ztypes.h"
#include "operand.h"
#include "osdepend.h"
#include "screen.h"
#include "screenio.h"
#include "control.h"
#include "text.h"
#include "quetzal.h"
#include "blorb.h"
#include "fileio.h"
#include "v6.h"
#include "v6ro.h"
#include "variable.h"


int mouse_window;
int game_wants_caret;

unsigned wind_prop[8][16];

FILE *GFile;
gfx_header GHeader;
static gfx_dir *GDir;
static int max_image;

/*
 * draw_picture
 *
 * Draw picture argv[0] at (argv[1],argv[2]), or at the cursor
 * pos if co-ords are (0,0) or not supplied
 */
void z_draw_picture(int argc, unsigned *argv)
{
    int x, y;
    gfx_dir *imdir;

    if (!blorb_map && !GFile)
    	fatal_lookup("NoGfx");

    if (argc<3)
    {
        argv[1]=0;
        argv[2]=0;
    }
    y=(short)argv[1];
    x=(short)argv[2];

    if (x==0 || y==0)
    {
        int tx, ty;
        v6ro_get_cursor_position(&ty, &tx);
        if (x==0) x=tx;
        if (y==0) y=ty;
    }

    imdir = v6_image_info(argv[0]);

    if (imdir == NULL)
    {
        char buffer[32];
        sprintf(buffer, "%d", argv[0]);
        warning_lookup_1("NoGfxN", buffer);
    }
    else
        v6ro_plot_picture(argv[0], imdir, y, x);
}

/*
 * picture_data
 *
 * If the picture number is valid, data on it is filled into the
 * table and the branch happens.
 *
 * If the picture number is 0, the highest valid picture number
 * is written into the table, the release number of the picture
 * file is written into the second word.
 *
 * Otherwise, nothing happens.
 */
void z_picture_data(unsigned picture, unsigned table)
{
    gfx_dir *imdir;

    //if (!blorb_map && !GFile)
      //	fatal_lookup("NoGfx");

    if (picture==0)
    {
    	set_word(table, max_image);
    	set_word(table+2, GHeader.version);
    	conditional_jump(FALSE);
    }
    else
    {
        //char buf[200];
        imdir=v6_image_info(picture);
        if (imdir==0)
        {
            //sprintf(buf, "Picture %d not available", picture);
            //warning(buf);
            conditional_jump(FALSE);
            return;
        }
        //sprintf(buf, "Picture %d is %dx%d", picture, imdir->image_width, imdir->image_height);
        //warning(buf);
        set_word(table, imdir->image_height);
        set_word(table+2, imdir->image_width);
        conditional_jump(TRUE);
    }
}

/*
 * erase_picture
 */
void z_erase_picture(int argc, unsigned *argv)
{
    int x, y;
    gfx_dir *imdir;

    if (!blorb_map && !GFile)
    	fatal_lookup("NoGfx");

    if (argc<3)
    {
        argv[1]=0;
        argv[2]=0;
    }
    y=(short)argv[1];
    x=(short)argv[2];

    if (x==0 || y==0)
    {
        int tx, ty;
        v6ro_get_cursor_position(&ty, &tx);
        if (x==0) x=tx;
        if (y==0) y=ty;
    }

    imdir = v6_image_info(argv[0]);

    if (imdir == NULL)
    {
        char buffer[32];
        sprintf(buffer, "%d", argv[0]);
        warning_lookup_1("NoGfxN", buffer);
    }
    else
        v6ro_remove_picture(argv[0], imdir, y, x);
}

/*
 * set_margins
 *
 * Set L&R text margins for given window
 */
void z_set_margins(int argc, unsigned *argv)
{
    unsigned l,r;
    int w;
    if (argc<3)
    	w=screen_window;
    else
    	w=(short)argv[2];

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

    if (argc<1)
    	l=0;
    else
    	l=argv[0];

    if (w==-3)
    	w=screen_window;

    if (wind_prop[w][WPROP_X_CURSOR]<l+1)
    {
    	wind_prop[w][WPROP_X_CURSOR]=(zword_t)(l+1);
    	if (w==screen_window)
    	    v6ro_move_cursor(wind_prop[w][WPROP_Y_CURSOR], l+1);
    }
    else if (wind_prop[w][WPROP_X_CURSOR]>wind_prop[w][WPROP_X_WIDTH]-r)
    {
    	wind_prop[w][WPROP_X_CURSOR]=(zword_t)(wind_prop[w][WPROP_X_WIDTH]-r);
    	if (w==screen_window)
    	    v6ro_move_cursor(wind_prop[w][WPROP_Y_CURSOR], wind_prop[w][WPROP_X_WIDTH]-r);
    }
    wind_prop[w][WPROP_L_MARGIN]=(zword_t) l;
    wind_prop[w][WPROP_R_MARGIN]=(zword_t) r;
}

/*
 * move_window
 *
 */
void z_move_window(short window, unsigned y, unsigned x)
{
    if (window == -3)
    	window=screen_window;

    wind_prop[window][WPROP_Y]=(zword_t) y;
    wind_prop[window][WPROP_X]=(zword_t) x;

    if (window==screen_window)
    	v6ro_select_window(window);   /* Hack to move cursor etc */
}

/*
 * window_size
 */
void z_window_size(short window, unsigned y, unsigned x)
{
    if (h_type!=V6)
    {
    	warn_bad_use("window_size");
    	return;
    }

    if (window==-3)
    	window=screen_window;

    wind_prop[window][WPROP_Y_HEIGHT]=(zword_t) y;
    wind_prop[window][WPROP_X_WIDTH]=(zword_t) x;

    if (wind_prop[window][WPROP_Y_CURSOR] > y ||
          wind_prop[window][WPROP_X_CURSOR] > x)
    {
        wind_prop[window][WPROP_Y_CURSOR]=1;
        wind_prop[window][WPROP_X_CURSOR]=wind_prop[window][WPROP_L_MARGIN]+1;
    }

    if (window==screen_window)
    	v6ro_select_window(window);   /* Hack to move cursor etc */
}

/*
 * window_style
 */
void z_window_style(int argc, unsigned *argv)
{
    int window;
    unsigned flags, operation;

    if (h_type!=V6)
    {
    	warn_bad_use("window_style");
    	return;
    }

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

    window=(short)argv[0];
    flags=argv[1];
    operation=argv[2];

    if (window==-3)
    	window=screen_window;

    switch (operation)
    {
        case 0:
            wind_prop[window][WPROP_ATTRIBUTES] = flags;
            break;

        case 1:
            wind_prop[window][WPROP_ATTRIBUTES] |= flags;
            break;

        case 2:
            wind_prop[window][WPROP_ATTRIBUTES] &=~ flags;
            break;

        case 3:
            wind_prop[window][WPROP_ATTRIBUTES] ^= flags;
            break;
    }

    if (window==screen_window)
    {
        buffering=(wind_prop[window][WPROP_ATTRIBUTES] & (WATTR_BUFFER|WATTR_WRAPPING)) == (WATTR_BUFFER|WATTR_WRAPPING) ? ON : OFF;
        scripting_disable=wind_prop[window][WPROP_ATTRIBUTES] & WATTR_SCRIPT ? OFF : ON;
    }
}

/*
 * get_wind_prop
 */
void z_get_wind_prop(short window, unsigned property)
{
    if (window == -3)
    	window=screen_window;

    if (window==screen_window && (property==WPROP_X_CURSOR ||
                                  property==WPROP_Y_CURSOR ||
                                  property==WPROP_TEXT_STYLE))
        flush_buffer(TRUE);     /* (Think about it) */

    store_operand(wind_prop[window][property]);
}

/*
 * scroll_window
 */
void z_scroll_window(unsigned window, short pixels)
{
    v6ro_scroll_pixels(window, pixels);
}

/*
 * push_stack
 *
 * Push a value onto the system stack, or a user stack, branching
 * if successful, not branching if no room.
 */
void z_push_stack(int argc, unsigned *argv)
{
    int value=argv[0];

    if (argc==1)
    {
        /* Push the value onto the stack */
        z_push(value);
        conditional_jump(TRUE);
    }
    else
    {
        int stack, free_slots;

        stack=argv[1];
        free_slots=get_word(stack);

        if (free_slots==0)
        {
            conditional_jump(FALSE);
        }
        else
        {
            set_word(stack+free_slots*2, value);
            set_word(stack, free_slots-1);
            conditional_jump(TRUE);
        }
    }
}

/*
 * pop_stack
 */
void z_pop_stack(int argc, unsigned *argv)
{
    int items=argv[0];

    if (argc==1)
    {
        /* Pop some values off the stack */
        sp+=items;
        return;
    }
    else
    {
        /* Otherwise pop some values of a user stack */

    	int stack, free_slots;

    	stack=argv[1];
        free_slots=get_word(stack);

        set_word(stack, free_slots+items);
    }
}

/*
 * mouse_window
 */

void z_mouse_window(short window)
{
    if (window == -3)
    	window=screen_window;

    mouse_window=window;
}

/*
 * put_wind_prop
 */

void z_put_wind_prop(short window, unsigned property, unsigned value)
{
    if (h_type!=V6)
    	warn_bad_use("put_wind_prop");
    else
    {
        if (window == -3)
            window=screen_window;

        if (window < 0 || window > 7)
            warning_lookup("BadWind");
        else
        {
            if (property != WPROP_INTERRUPT_ROUTINE &&
                property != WPROP_INTERRUPT_COUNT &&
                property != WPROP_LINE_COUNT)
                warning_lookup("BadProp");
            else
    	    	wind_prop[window][property]=(zword_t) value;
    	}
    }
}

/*
 * print_form
 */

void z_print_form(unsigned table)
{
    int len, i;

    while ((len=get_word(table)) != 0)
    {
        table+=2;
        for (i=0; i<len; i++, table++)
            z_print_char(get_byte(table));
        z_new_line();
    }
}

/*
 * make_menu
 */
void z_make_menu(unsigned menu, unsigned table)
{
    const char *list[32];
    int len[32];
    char *rlist[32];
    int n, p, i;

    v6ro_remove_menu(menu);

    if (table)
    {
        n=get_word(table);
        if (n>32) n=32;
        for (i=0; i<n; i++)
        {
            p=get_word(table+2+2*i);
            len[i]=datap[p];
            list[i]=(char *)datap+p+1;
            rlist[i]=malloc(len[i]+1);
            memcpy(rlist[i], list[i], len[i]);
            rlist[i][len[i]]='\0';
        }
        v6ro_add_menu(menu, n, rlist, len);
        for (i=0; i<n; i++)
            free(rlist[i]);
    }

    conditional_jump(TRUE);
}

/*
 * picture_table
 */
void z_picture_table(unsigned table)
{
    int pic;

#ifdef POSTPONE
    for (i=0; i<GHeader.images; i++)
        GDir[i].cacheable=0;
#endif

    v6ro_clear_cache();

    while ((pic=get_word(table)) != 0)
    {
    	v6ro_cache_picture(v6_image_info(pic));
    	table+=2;
    }
}

void v6z_get_cursor(int table)
{
    flush_buffer(TRUE);
    set_word(table, wind_prop[screen_window][WPROP_Y_CURSOR]);
    set_word(table+2, wind_prop[screen_window][WPROP_X_CURSOR]);
}

extern void place_caret(void);

void v6z_set_cursor(int argc, unsigned *argv)
{
    int window;

    if (argv[0] == (zword_t) -1)
    {
        game_wants_caret=0;
        place_caret();
        return;
    }
    if (argv[0] == (zword_t) -2)
    {
        game_wants_caret=1;
        place_caret();
        return;
    }

    if (argc<3)
    	window=screen_window;
    else
    {
    	window=(short)argv[2];
    	if (window == -3)
    	    window=screen_window;
    }

    /* Confine within margins */
    if (argv[1] < 1+wind_prop[window][WPROP_L_MARGIN])
        argv[1] = 1+wind_prop[window][WPROP_L_MARGIN];

    if (argv[1] > 1+wind_prop[window][WPROP_X_WIDTH]-wind_prop[window][WPROP_R_MARGIN])
    	argv[1] = 1+wind_prop[window][WPROP_X_WIDTH]-wind_prop[window][WPROP_R_MARGIN];

    if (window==screen_window)
    {
        flush_buffer(TRUE);
    	v6ro_move_cursor(argv[0], argv[1]);
    }
    else
    {
    	wind_prop[window][WPROP_Y_CURSOR]=(zword_t)argv[0];
    	wind_prop[window][WPROP_X_CURSOR]=(zword_t)argv[1];
    }
}

void v6_output_new_line()
{
    int y, x;
    unsigned arglist[2];
    short count=wind_prop[screen_window][WPROP_LINE_COUNT];

    if (output_stream_1==OFF)
    	return;

#ifndef OldInt
    if (wind_prop[screen_window][WPROP_INTERRUPT_COUNT] &&
        --wind_prop[screen_window][WPROP_INTERRUPT_COUNT] == 0)
    {
        int z0_hack;
        /* Don't ask - Spec 1.0 8.8.3.2.2.1 */
        if (h_release == 393 && h_interpreter == 6 &&
                      memcmp(datap + H_SERIAL_NUMBER, "890714", 6) == 0)
            z0_hack = 1;
        else
            z0_hack = 0;

        if (z0_hack) v6ro_scroll_line();
        call_interrupt(wind_prop[screen_window][WPROP_INTERRUPT_ROUTINE]);
        if (!z0_hack) v6ro_scroll_line();
    }
    else
        v6ro_scroll_line();
#else
    v6ro_scroll_line();
#endif


    count++;

    if (count >= (signed)(wind_prop[screen_window][WPROP_Y_HEIGHT]/V6_CHAR_H-1)
        && (wind_prop[screen_window][WPROP_ATTRIBUTES] & WATTR_SCROLL))
    {
        wind_prop[screen_window][WPROP_LINE_COUNT]=0;

        if (replaying==OFF)
        {
            y=wind_prop[screen_window][WPROP_Y_CURSOR];
            x=wind_prop[screen_window][WPROP_X_CURSOR];
            output_string(msgs_lookup_u("More"));
            input_character(0);
            arglist[0]=y; arglist[1]=x;
            v6z_set_cursor(2, arglist);
            z_erase_line(1);
        }
    }
    else
     	wind_prop[screen_window][WPROP_LINE_COUNT]=(zword_t) count;

#ifdef OldInt
    if (wind_prop[screen_window][WPROP_INTERRUPT_COUNT])
        if (--wind_prop[screen_window][WPROP_INTERRUPT_COUNT] == 0)
            call_interrupt(wind_prop[screen_window][WPROP_INTERRUPT_ROUTINE]);
#endif
}

void v6z_erase_window(short w)
{
    if (w == -1 || w == -2)
    {
        if (w == -1)
            z_set_window(0);
        v6ro_clear_screen();
        if (w == -1)
            v6z_split_window(0);

        for (w=0; w<8; w++)
            wind_prop[w][WPROP_LINE_COUNT]=0;
    }
    else
    {
        unsigned arglist[3];

        if (w == -3)
            w=screen_window;

    	v6ro_clear_window(w);

    	/* Why was this in here?
    	arglist[0]=0;
    	arglist[1]=0;
    	arglist[2]=w;
    	z_set_margins(3, arglist);
    	*/

    	arglist[0]=1;
    	arglist[1]=1+wind_prop[w][WPROP_L_MARGIN];
    	arglist[2]=w;
    	v6z_set_cursor(3, arglist);

    	wind_prop[w][WPROP_LINE_COUNT]=0;
    }
}

void v6z_split_window(int lines)
{
    unsigned arglist[3];

    z_move_window(1, 1, 1);        /* Move window 1 to top left */
    z_window_size(1, lines, h_screen_width);  /* Make it appropriate height, full width */
    z_move_window(0, lines+1, 1);  /* Put window 0 below it */

    if (lines)
        z_window_size(0, h_screen_height-lines, h_screen_width);
    else
    {
    	z_window_size(0, h_screen_height, h_screen_width);
    	arglist[0]=1;
    	arglist[1]=1;
    	arglist[2]=0;
    	z_set_cursor(3,arglist);
    }
    z_set_window(0);
}

static void v6_prepare_mga_gfx(void)
{
    int i;

    v6ro_open_gfx();

    if (!GFile)
    	return;

    if (fread(&GHeader, sizeof GHeader, 1, GFile) < 1)
    {
    	fatal_lookup("GfxErr");
    	return;
    }

    GDir=calloc(GHeader.images, sizeof *GDir);

    if (GDir==NULL)
    {
        fatal_lookup("NoMem");
        return;
    }

    for (i=0; i<GHeader.images; i++)
    {
    	if (fread(GDir+i, 8, 1, GFile) < 1)
    	{
            fatal_lookup("GfxErr");
            return;
    	}
    	GDir[i].image_width = GDir[i].phys_width * 2;
    	#ifdef HIRES_COORDS
    	GDir[i].image_height = GDir[i].phys_height * 2;
    	#else
    	GDir[i].image_height = GDir[i].phys_height;
    	#endif
    	GDir[i].image_data_addr = fget24(GFile);
    	if (GHeader.dir_size == 14)
            GDir[i].image_cm_addr = fget24(GFile);
        if (GDir[i].image_number > max_image)
            max_image=GDir[i].image_number;
        if (GDir[i].image_flags & 1)
            GDir[i].alphatype = 1;
    }
}

static void v6_prepare_blorb_gfx()
{
    bb_err_t err;
    bb_result_t res;
    bb_resolution_t *reso;
    bb_aux_pict_t *aux;
    int images, i, r, min_image, w, h;
    float ERF = 1.0F, tmp1, tmp2, min, max, R;

    reso = bb_get_resolution(blorb_map);
    if (reso)
    {
        tmp1 = (float) 640 / reso->px;
        tmp2 = (float) 400 / reso->py;
        ERF = tmp1 < tmp2 ? tmp1 : tmp2;
    }

    err = bb_count_resources(blorb_map, bb_ID_Pict, &images,
                             &min_image, &max_image);
    if (err)
        fatal_lookup_1("BlorbErr", bb_err_to_string(err));
    GHeader.version = bb_get_release_num(blorb_map);
    GHeader.images = (unsigned short) images;

    if (images == 0) return;

    GDir=calloc(images, sizeof *GDir);
    if (GDir==NULL)
    {
        fatal_lookup("NoMem");
        return;
    }

    for (i=0, r=min_image; r <= max_image; r++)
    {
        err = bb_load_resource_pict(blorb_map, bb_method_FilePos,
                                    &res, r, &aux);
        if (err == bb_err_NotFound) continue;
        else if (err) fatal_lookup_1("BlorbErr", bb_err_to_string(err));

        GDir[i].image_number = r;
        fseek(bfp, res.data.startpos + 16L, SEEK_SET);
        w = GDir[i].phys_width = fget32(bfp);
        h = GDir[i].phys_height = fget32(bfp);
        if (reso && aux)
        {
            R = ERF * aux->ratnum / aux->ratden;
            if (aux->minnum)
            {
                min = (float) aux->minnum / aux->minden;
                if (R < min) R = min;
            }
            if (aux->maxnum)
            {
                max = (float) aux->maxnum / aux->maxden;
                if (R > max) R = max;
            }
            GDir[i].image_width = (int)(w * R + 0.5F);
            #ifdef HIRES_COORDS
            GDir[i].image_height = (int)(h * R + 0.5F);
            #else
            GDir[i].image_height = (int)(h * R / 2.0F + 0.5F);
            #endif
        }
        else
        {
            GDir[i].image_width = GDir[i].phys_width;
            #ifdef HIRES_COORDS
            GDir[i].image_height = GDir[i].phys_height;
            #else
            GDir[i].image_height = GDir[i].phys_height / 2;
            #endif
        }
        GDir[i].image_data_addr = res.data.startpos;
        i++;
    }
}

void v6_prepare_gfx(void)
{
    if (blorb_map)
        v6_prepare_blorb_gfx();
    else
        v6_prepare_mga_gfx();
}


gfx_dir *v6_image_info(int image)
{
    int i;

    for (i=0; i<GHeader.images; i++)
        if (GDir[i].image_number==image)
            return GDir+i;

    return NULL;
}

unsigned short ntohs(unsigned short s)
{
    return s>>8 | (s & 0xFF)<<8;
}

unsigned long ntohl(unsigned long s)
{
    unsigned long r;

    r=(s>>24);
    r|=(s>>8) & 0xFF00;
    r|=(s<<8) & 0xFF0000;
    r|=(s<<24);

    return r;
}

