/*
**  This file is part of Zterp, and is
**  Copyright 1992, 1993 Charles Hannum
*/

#include <sys/types.h>
#include <stdio.h>
#include "main.h"
#include "object.h"
#include "property.h"
#include "variable.h"
#include "arithmetic.h"
#include "array.h"
#include "jump.h"
#include "print.h"
#include "output.h"
#include "input.h"
#include "misc.h"
#include "interpreter.h"
#include "undo.h"

static void two_operand (zbyte);
static void one_operand (zbyte);
static void zero_operand (zbyte);
static void variable_operand (zbyte);
static void variable_operand_1 (zfunction *, int, int, uword);
static zfunction extended_opcode;

zfunction null_function, null_function_store, null_function_return;
zfunction
  *dispatch2[0x40] =
    {
      /*0x00*/ 0,
      /*0x01*/ je,
      /*0x02*/ jl,
      /*0x03*/ jg,
      /*0x04*/ djl,
      /*0x05*/ ijg,
      /*0x06*/ inside_object,
      /*0x07*/ test,
      /*0x08*/ or,
      /*0x09*/ and,
      /*0x0a*/ test_attribute,
      /*0x0b*/ set_attribute,
      /*0x0c*/ clear_attribute,
      /*0x0d*/ store,
      /*0x0e*/ insert_object,
      /*0x0f*/ fetch_word_from_array,
      /*0x10*/ fetch_byte_from_array,
      /*0x11*/ get_property,
      /*0x12*/ get_property_address,
      /*0x13*/ get_next_property,
      /*0x14*/ add,
      /*0x15*/ subtract,
      /*0x16*/ multiply,
      /*0x17*/ divide,
      /*0x18*/ compute_the_modulus,
/*V4*//*0x19*/ call_function,
/*V5*//*0x1a*/ call_procedure,
/*V5*//*0x1b*/ null_function, /* FIXME v5=new_opcode0 (2) */
/*V5*//*0x1c*/ 0, /* FIXME v5=throw_away_stack_frame (2) return */
      /*0x1d*/ 0,
      /*0x1e*/ 0,
      /*0x1f*/ 0,
      /*0x20*/ call_function,
      /*0x21*/ store_word_in_array,
      /*0x22*/ store_byte_in_array,
      /*0x23*/ set_property,
      /*0x24*/ read_line_and_parse,
      /*0x25*/ print_character,
      /*0x26*/ print_number,
      /*0x27*/ random,
      /*0x28*/ push,
      /*0x29*/ pop_and_store,
      /*0x2a*/ split_window,
      /*0x2b*/ set_window,
/*V4*//*0x2c*/ call_function,
/*V4*//*0x2d*/ erase_window,
/*V4*//*0x2e*/ erase_line,
/*V4*//*0x2f*/ set_cursor,
/*V4*//*0x30*/ 0, /* NIL */
/*V4*//*0x31*/ set_video,
/*V4*//*0x32*/ set_buffer,
      /*0x33*/ set_output,
      /*0x34*/ null_function, /* FIXME record_mode (1) */
      /*0x35*/ sound,
/*V4*//*0x36*/ read_character,
/*V4*//*0x37*/ find_in_array,
      /*0x38*/ not,
/*V5*//*0x39*/ call_procedure,
/*V5*//*0x3a*/ call_procedure,
/*V5*//*0x3b*/ parse,
/*V5*//*0x3c*/ 0, /* FIXME v5=encrypt (4) */
/*V5*//*0x3d*/ copy_array,
/*V5*//*0x3e*/ print_rectangle,
/*V5*//*0x3f*/ num_args,
    },
  *dispatch1[0x10] =
    {
      /*0x00*/ jz,
      /*0x01*/ store_sibling,
      /*0x02*/ store_child,
      /*0x03*/ store_parent,
      /*0x04*/ get_property_length,
      /*0x05*/ increment,
      /*0x06*/ decrement,
      /*0x07*/ print_near,
/*V4*//*0x08*/ call_function_1,
      /*0x09*/ remove_object,
      /*0x0a*/ print_object_name,
      /*0x0b*/ ret,
      /*0x0c*/ jmp,
      /*0x0d*/ print_far,
      /*0x0e*/ fetch_and_store,
      /*0x0f*/ not_or_call_procedure,
    },
  *dispatch0[0x10] =
    {
      /*0x00*/ ret_true,
      /*0x01*/ ret_false,
      /*0x02*/ print_immediate,
      /*0x03*/ print_immediate_and_ret,
      /*0x04*/ 0,
      /*0x05*/ 0, /* FIXME v3=save (0) branch, v4=save (0) store */
      /*0x06*/ 0, /* FIXME v3=restore (0) branch, v4=restore (0) store */
      /*0x07*/ 0, /* FIXME restart (0) */
      /*0x08*/ pop_and_return,
      /*0x09*/ pop,
      /*0x0a*/ quit,
      /*0x0b*/ print_newline,
      /*0x0c*/ print_status,
      /*0x0d*/ verify,
/*V5*//*0x0e*/ extended_opcode,
/*V5*//*0x0f*/ return_or_jump_true,
    },
  *dispatche[0x100] =
    {
/*V5*//*0x00*/ 0, /* FIXME v5=save (0) store */
/*V5*//*0x01*/ 0, /* FIXME v5=restore (0) store */
/*V5*//*0x02*/ logical_shift,
/*V5*//*0x03*/ arithmetic_shift,
/*V5*//*0x04*/ set_graphics,
/*V5*//*0x05*/ null_function,
/*V5*//*0x06*/ null_function_return,
/*V5*//*0x07*/ null_function,
/*V5*//*0x08*/ 0, /* FIXME v5=set_margin? */
/*V5*//*0x09*/ undo_save,
/*V5*//*0x0a*/ undo_restore,
    };
zbyte
  varargs[0x40] =
    {
      0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
      1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
      0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0,
    };


void
null_function ()
{
}

void
null_function_store ()
{
  store_variable_push (0);
}

void
null_function_return ()
{
  return_or_jump (0);
}

void
interpreter_main_loop (void)
{
  set_pc (near_address (initial_pc));
  for (;;)
  {
    zbyte opcode = get_byte_pc ();
#ifdef DEBUG
    fprintf (stderr, "opcode 0x%02x at address 0x%05x\n", opcode, pc - game - 1);
#endif
    if (opcode < 0x80)
      two_operand (opcode);
    else if (opcode < 0xb0)
      one_operand (opcode);
    else if (opcode >= 0xc0)
      variable_operand (opcode);
    else
      zero_operand (opcode);
  }
}

void
illegal_opcode (uword opcode)
{
  die ("illegal opcode 0x%04x at virtual address 0x%05x", opcode, pc - game - 1);
}

void
two_operand (zbyte opcode)
{
  zfunction *f = dispatch2[opcode & 0x1f];
  uword argp[2];
  if (!f) illegal_opcode (opcode);
  argp[0] = opcode & 0x40 ? fetch (2) : fetch (1);
  argp[1] = opcode & 0x20 ? fetch (2) : fetch (1);
  if (! varargs[opcode & 0x1f])
    f (argp[0], argp[1]);
  else
    f (2, argp);
}

void
one_operand (zbyte opcode)
{
  zfunction *f = dispatch1[opcode & 0x0f];
  if (!f) illegal_opcode (opcode);
  f (fetch ((opcode >> 4) & 3));
}

void
zero_operand (zbyte opcode)
{
  zfunction *f = dispatch0[opcode & 0x0f];
  if (!f) illegal_opcode (opcode);
  f ();
}

void
variable_operand (zbyte opcode)
{
  zfunction *f = dispatch2[opcode & 0x3f];
  if (!f) illegal_opcode (opcode);
  opcode &= 0x3f;
  if (opcode == 0x2c || opcode == 0x3a)
    variable_operand_1 (f, varargs[opcode], 8, get_word_pc ());
  else
    variable_operand_1 (f, varargs[opcode], 4, get_byte_pc ());
}

void
extended_opcode ()
{
  zbyte opcode = get_byte_pc ();
  zfunction *f = dispatche[opcode];
  if (!f) illegal_opcode (opcode | 0xbe00);
  variable_operand_1 (f, 0, 4, get_byte_pc ());
}

void
variable_operand_1 (zfunction *f, int varargs, int n, uword x)
{
  int max = n;
  uword args[8];

  while (n)
  {
    args[--n] = x & 3;
    x >>= 2;
  }

  while (n < max)
  {
    uword m = args[n];
    if (m == 3)
      break;
    args[n] = fetch (m);
    n++;
  }

  if (!varargs)
    switch (n)
    {
      case 4:
	f (args[0], args[1], args[2], args[3]);
	break;
      case 3:
	f (args[0], args[1], args[2]);
	break;
      case 2:
	f (args[0], args[1]);
	break;
      case 1:
	f (args[0]);
	break;
      case 0:
	f ();
	break;
    }
  else
    f (n, args);
}
