/*-
 * Copyright 2010-2011 Chris Spiegel.
 *
 * This file is part of Bocfel.
 *
 * Bocfel is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version
 * 2, as published by the Free Software Foundation.
 *
 * Bocfel is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Bocfel.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <setjmp.h>

#ifdef ZTERP_GLK
#include <glk.h>
#endif

#include "process.h"
#include "branch.h"
#include "dict.h"
#include "math.h"
#include "memory.h"
#include "objects.h"
#include "random.h"
#include "screen.h"
#include "stack.h"
#include "table.h"
#include "util.h"
#include "zoom.h"
#include "zterp.h"

uint16_t zargs[8];
int znargs;

static jmp_buf *jumps;
static size_t njumps;

/* Each time an interrupt happens, process_instructions() is called
 * (effectively starting a whole new round of interpreting).  This
 * variable holds the current level of interpreting: 0 for no
 * interrupts, 1 if one interrupt has been called, 2 if an interrupt was
 * called inside of an interrupt, and so on.
 */
static long ilevel = -1;

long interrupt_level(void)
{
  return ilevel;
}

/* When this is called, the interrupt at level “level” will stop
 * running: if a single interrupt is running, then break_from(1) will
 * stop the interrupt, going back to the main program.  Breaking from
 * interrupt level 0 (which is not actually an interrupt) will end the
 * program.  This is how @quit is implemented.
 */
void break_from(long level)
{
  ilevel = level - 1;
  longjmp(jumps[level], 1);
}

/* If a restore happens inside of an interrupt, the level needs to be
 * set back to 0, but without a longjmp(), so break_from() cannot be
 * used.
 */
void reset_level(void)
{
  ilevel = 0;
}

/* To signal a restart, longjmp() is called with 2; this advises
 * process_instructions() to restart the story file and then continue
 * execution, whereas a value of 1 tells it to return immediately.
 */
static void zrestart(void)
{
  ilevel = 0;
  longjmp(jumps[0], 2);
}

/* Returns 1 if decoded, 0 otherwise (omitted) */
static int decode_base(uint8_t type, uint16_t *loc)
{
  if     (type == 0) *loc = WORD(pc++);		/* Large constant. */
  else if(type == 1) *loc = BYTE(pc);		/* Small constant. */
  else if(type == 2) *loc = variable(BYTE(pc));	/* Variable. */
  else               return 0;			/* Omitted. */

  pc++;

  return 1;
}

static void decode_var(uint8_t types)
{
  uint16_t ret;

  if(!decode_base((types >> 6) & 0x03, &ret)) return;
  zargs[znargs++] = ret;
  if(!decode_base((types >> 4) & 0x03, &ret)) return;
  zargs[znargs++] = ret;
  if(!decode_base((types >> 2) & 0x03, &ret)) return;
  zargs[znargs++] = ret;
  if(!decode_base((types >> 0) & 0x03, &ret)) return;
  zargs[znargs++] = ret;
}

/* op[0] is 0OP, op[1] is 1OP, etc */
static void (*op[5][256])(void);
static const char *opnames[5][256];
enum { ZERO, ONE, TWO, VAR, EXT };

#define op_call(opt, opnumber)	(op[opt][opnumber]())

/* This nifty trick is from Frotz. */
static void zextended(void)
{
  uint8_t opnumber = BYTE(pc++);

  decode_var(BYTE(pc++));

  /* §14.2.1
   * The exception for 0x80–0x83 is for the Zoom extensions.
   * Standard 1.1 implicitly updates §14.2.1 to recommend ignoring
   * opcodes in the range EXT:30 to EXT:255, due to the fact that
   * @buffer_screen is EXT:29.
   */
  if(opnumber > 0x1d && (opnumber < 0x80 || opnumber > 0x83)) return;

  op_call(EXT, opnumber);
}

static void illegal_opcode(void)
{
#ifndef ZTERP_NO_SAFETY_CHECKS
  die("illegal opcode (pc = 0x%lx)", zassert_pc);
#else
  die("illegal opcode");
#endif
}

void setup_opcodes(void)
{
  for(int i = 0; i < 5; i++)
  {
    for(int j = 0; j < 256; j++)
    {
      op[i][j] = illegal_opcode;
    }
  }
#define OP(args, opcode, fn) do { op[args][opcode] = fn; opnames[args][opcode] = #fn; } while(0)
  OP(ZERO, 0x00, zrtrue);
  OP(ZERO, 0x01, zrfalse);
  OP(ZERO, 0x02, zprint);
  OP(ZERO, 0x03, zprint_ret);
  OP(ZERO, 0x04, znop);
  if(zversion <= 4) OP(ZERO, 0x05, zsave);
  if(zversion <= 4) OP(ZERO, 0x06, zrestore);
  OP(ZERO, 0x07, zrestart);
  OP(ZERO, 0x08, zret_popped);
  if(zversion <= 4) OP(ZERO, 0x09, zpop);
  else              OP(ZERO, 0x09, zcatch);
  OP(ZERO, 0x0a, zquit);
  OP(ZERO, 0x0b, znew_line);
  if     (zversion == 3) OP(ZERO, 0x0c, zshow_status);
  else if(zversion >= 4) OP(ZERO, 0x0c, znop); /* §15: Technically illegal in V4+, but a V5 Wishbringer accidentally uses this opcode. */
  if(zversion >= 3) OP(ZERO, 0x0d, zverify);
  if(zversion >= 5) OP(ZERO, 0x0e, zextended);
  if(zversion >= 5) OP(ZERO, 0x0f, zpiracy);

  OP(ONE, 0x00, zjz);
  OP(ONE, 0x01, zget_sibling);
  OP(ONE, 0x02, zget_child);
  OP(ONE, 0x03, zget_parent);
  OP(ONE, 0x04, zget_prop_len);
  OP(ONE, 0x05, zinc);
  OP(ONE, 0x06, zdec);
  OP(ONE, 0x07, zprint_addr);
  if(zversion >= 4) OP(ONE, 0x08, zcall_1s);
  OP(ONE, 0x09, zremove_obj);
  OP(ONE, 0x0a, zprint_obj);
  OP(ONE, 0x0b, zret);
  OP(ONE, 0x0c, zjump);
  OP(ONE, 0x0d, zprint_paddr);
  OP(ONE, 0x0e, zload);
  if(zversion <= 4) OP(ONE, 0x0f, znot);
  else              OP(ONE, 0x0f, zcall_1n);

  OP(TWO, 0x01, zje);
  OP(TWO, 0x02, zjl);
  OP(TWO, 0x03, zjg);
  OP(TWO, 0x04, zdec_chk);
  OP(TWO, 0x05, zinc_chk);
  OP(TWO, 0x06, zjin);
  OP(TWO, 0x07, ztest);
  OP(TWO, 0x08, zor);
  OP(TWO, 0x09, zand);
  OP(TWO, 0x0a, ztest_attr);
  OP(TWO, 0x0b, zset_attr);
  OP(TWO, 0x0c, zclear_attr);
  OP(TWO, 0x0d, zstore);
  OP(TWO, 0x0e, zinsert_obj);
  OP(TWO, 0x0f, zloadw);
  OP(TWO, 0x10, zloadb);
  OP(TWO, 0x11, zget_prop);
  OP(TWO, 0x12, zget_prop_addr);
  OP(TWO, 0x13, zget_next_prop);
  OP(TWO, 0x14, zadd);
  OP(TWO, 0x15, zsub);
  OP(TWO, 0x16, zmul);
  OP(TWO, 0x17, zdiv);
  OP(TWO, 0x18, zmod);
  if(zversion >= 4) OP(TWO, 0x19, zcall_2s);
  if(zversion >= 5) OP(TWO, 0x1a, zcall_2n);
  if(zversion >= 5) OP(TWO, 0x1b, zset_colour);
  if(zversion >= 5) OP(TWO, 0x1c, zthrow);

  OP(VAR, 0x00, zcall);
  OP(VAR, 0x01, zstorew);
  OP(VAR, 0x02, zstoreb);
  OP(VAR, 0x03, zput_prop);
  OP(VAR, 0x04, zread);
  OP(VAR, 0x05, zprint_char);
  OP(VAR, 0x06, zprint_num);
  OP(VAR, 0x07, zrandom);
  OP(VAR, 0x08, zpush);
  OP(VAR, 0x09, zpull);
  if(zversion >= 3) OP(VAR, 0x0a, zsplit_window);
  if(zversion >= 3) OP(VAR, 0x0b, zset_window);
  if(zversion >= 4) OP(VAR, 0x0c, zcall_vs2);
  if(zversion >= 4) OP(VAR, 0x0d, zerase_window);
  if(zversion >= 4) OP(VAR, 0x0e, zerase_line);
  if(zversion >= 4) OP(VAR, 0x0f, zset_cursor);
  if(zversion >= 4) OP(VAR, 0x10, zget_cursor);
  if(zversion >= 4) OP(VAR, 0x11, zset_text_style);
  if(zversion >= 4) OP(VAR, 0x12, znop); /* XXX buffer_mode */
  if(zversion >= 3) OP(VAR, 0x13, zoutput_stream);
  if(zversion >= 3) OP(VAR, 0x14, zinput_stream);
  if(zversion >= 3) OP(VAR, 0x15, zsound_effect);
  if(zversion >= 4) OP(VAR, 0x16, zread_char);
  if(zversion >= 4) OP(VAR, 0x17, zscan_table);
  if(zversion >= 5) OP(VAR, 0x18, znot);
  if(zversion >= 5) OP(VAR, 0x19, zcall_vn);
  if(zversion >= 5) OP(VAR, 0x1a, zcall_vn2);
  if(zversion >= 5) OP(VAR, 0x1b, ztokenise);
  if(zversion >= 5) OP(VAR, 0x1c, zencode_text);
  if(zversion >= 5) OP(VAR, 0x1d, zcopy_table);
  if(zversion >= 5) OP(VAR, 0x1e, zprint_table);
  if(zversion >= 5) OP(VAR, 0x1f, zcheck_arg_count);

  if(zversion >= 5) OP(EXT, 0x00, zsave5);
  if(zversion >= 5) OP(EXT, 0x01, zrestore5);
  if(zversion >= 5) OP(EXT, 0x02, zlog_shift);
  if(zversion >= 5) OP(EXT, 0x03, zart_shift);
  if(zversion >= 5) OP(EXT, 0x04, zset_font);
  if(zversion >= 6) OP(EXT, 0x05, znop); /* XXX draw_picture */
  if(zversion >= 6) OP(EXT, 0x06, zpicture_data);
  if(zversion >= 6) OP(EXT, 0x07, znop); /* XXX erase_picture */
  if(zversion >= 6) OP(EXT, 0x08, znop); /* XXX set_margins */
  if(zversion >= 5) OP(EXT, 0x09, zsave_undo);
  if(zversion >= 5) OP(EXT, 0x0a, zrestore_undo);
  if(zversion >= 5) OP(EXT, 0x0b, zprint_unicode);
  if(zversion >= 5) OP(EXT, 0x0c, zcheck_unicode);
  if(zversion >= 5) OP(EXT, 0x0d, zset_true_colour);
  if(zversion >= 6) OP(EXT, 0x10, znop); /* XXX move_window */
  if(zversion >= 6) OP(EXT, 0x11, znop); /* XXX window_size */
  if(zversion >= 6) OP(EXT, 0x12, znop); /* XXX window_style */
  if(zversion >= 6) OP(EXT, 0x13, zget_wind_prop);
  if(zversion >= 6) OP(EXT, 0x14, znop); /* XXX scroll_window */
  if(zversion >= 6) OP(EXT, 0x15, zpop_stack);
  if(zversion >= 6) OP(EXT, 0x16, znop); /* XXX read_mouse */
  if(zversion >= 6) OP(EXT, 0x17, znop); /* XXX mouse_window */
  if(zversion >= 6) OP(EXT, 0x18, zpush_stack);
  if(zversion >= 6) OP(EXT, 0x19, znop); /* XXX put_wind_prop */
  if(zversion >= 6) OP(EXT, 0x1a, zprint_form);
  if(zversion >= 6) OP(EXT, 0x1b, zmake_menu);
  if(zversion >= 6) OP(EXT, 0x1c, znop); /* XXX picture_table */
  if(zversion >= 6) OP(EXT, 0x1d, zbuffer_screen);

  /* Zoom extensions. */
  OP(EXT, 0x80, zstart_timer);
  OP(EXT, 0x81, zstop_timer);
  OP(EXT, 0x82, zread_timer);
  OP(EXT, 0x83, zprint_timer);
#undef OP
}

void process_instructions(void)
{
  if(njumps <= ++ilevel)
  {
    jumps = realloc(jumps, ++njumps * sizeof *jumps);
    if(jumps == NULL) die("unable to allocate memory for jump buffer");
  }

  switch(setjmp(jumps[ilevel]))
  {
    case 1: /* Normal break from interrupt. */
      return;
    case 2: /* Special break: a restart was requested. */
      {
        /* §6.1.3: Flags2 is preserved on a restart. */
        uint16_t flags2 = WORD(0x10);

        process_story();

        STORE_WORD(0x10, flags2);
      }
      break;
  }

  while(1)
  {
    uint8_t opcode;

#if defined(ZTERP_GLK) && defined(ZTERP_GLK_TICK)
    glk_tick();
#endif

    ZPC(pc);

    opcode = BYTE(pc++);

    /* long 2OP */
    if(opcode < 0x80)
    {
      znargs = 2;

      if(opcode & 0x40) zargs[0] = variable(BYTE(pc++));
      else              zargs[0] = BYTE(pc++);

      if(opcode & 0x20) zargs[1] = variable(BYTE(pc++));
      else              zargs[1] = BYTE(pc++);

      op_call(TWO, opcode & 0x1f);
    }

    /* short 1OP */
    else if(opcode < 0xb0)
    {
      znargs = 1;

      if(opcode < 0x90) /* large constant */
      {
        zargs[0] = WORD(pc);
        pc += 2;
      }
      else if(opcode < 0xa0) /* small constant */
      {
        zargs[0] = BYTE(pc++);
      }
      else /* variable */
      {
        zargs[0] = variable(BYTE(pc++));
      }

      op_call(ONE, opcode & 0x0f);
    }

    /* short 0OP (plus EXT) */
    else if(opcode < 0xc0)
    {
      znargs = 0;

      op_call(ZERO, opcode & 0x0f);
    }

    /* variable 2OP */
    else if(opcode < 0xe0)
    {
      znargs = 0;

      decode_var(BYTE(pc++));

      op_call(TWO, opcode & 0x1f);
    }

    /* Double variable VAR */
    else if(opcode == 0xec || opcode == 0xfa)
    {
      uint8_t types1, types2;

      znargs = 0;

      types1 = BYTE(pc++);
      types2 = BYTE(pc++);
      decode_var(types1);
      decode_var(types2);

      op_call(VAR, opcode & 0x1f);
    }

    /* variable VAR */
    else
    {
      znargs = 0;

      read_pc = pc - 1;

      decode_var(BYTE(pc++));

      op_call(VAR, opcode & 0x1f);
    }
  }
}
