/*
  Jump
*/

#include "jump.h"

#include "addr.h"
#include "head.h"
#include "interp.h"
#include "mem.h"
#include "page.h"
#include "pc.h"
#include "shared.h"
#include "stack.h"
#include "stop.h"
#include "support.h"
#include "var.h"

#define PROCEDURE       0
#define FUNCTION        0x0100
#define EMPTY_PAGE      0xFFFE

static byte local_params;

static void call(word type)
{
  extern word param_stack[];

  int num_params  = param_stack[0] - 1;
  word packed     = param_stack[1];
  word *param_ptr = &param_stack[2];
  long_word addr  = ad_code_addr(packed);
  int vars;

  if(addr == 0)
  {
    if(type != PROCEDURE)
      store(0);
  }
  else
  {
    stk_push(pc_page());
    stk_push(pc_offset());
    stk_push(type | local_params);
    stk_link();

    set_pc(pg_page(addr), pg_offset(addr));

    local_params = num_params;

    /* Global variables 1 to 15 are Local variables, which
        reside on the stack (and so are local to each procedure). */

    vars = (int) next_byte();
    if(vars > ((int) LOCAL_VARS - 1))
    {
      display((byte *) "Bad loc num");
      vars = LOCAL_VARS - 1;
    }
    if(num_params > vars)
    {
#if 0
      display("Bad optional num");
#endif
      num_params = vars;
    }
    vars -= num_params;
    while(num_params--)
      stk_push(*param_ptr++);
    while(vars--)
      stk_push(0);
  }
}

static void std_gosub(void)
{
  extern word param_stack[];

  int num_params  = param_stack[0] - 1;
  word packed     = param_stack[1];
  word *param_ptr = &param_stack[2];
  long_word addr  = ad_code_addr(packed);

  if(addr == 0)
  {
    store(0);
  }
  else
  {
    int vars;

    stk_push(pc_page());
    stk_push(pc_offset());
    stk_link();

    set_pc(pg_page(addr), pg_offset(addr));

    vars = (int) next_byte();
    while(vars--)
    {
      word parameter = next_word();
      if(--num_params >= 0)
        parameter = *param_ptr++;
      stk_push(parameter);
    }
  }
}

static void adv_gosub(void)
{
  call(FUNCTION);
}

static void std_rtn(word value)
{
  word page, offset;
  stk_unlink();
  offset        = stk_pop();
  page          = stk_pop();

  if(page == EMPTY_PAGE)
  {
    quit();
  }
  else
  {
    set_pc(page, offset);
    store(value);
  }
}

static void adv_rtn(word value)
{
  word type, page, offset;
  
  stk_unlink();
  type          = stk_pop();
  local_params  = (byte) type;
  offset        = stk_pop();
  page          = stk_pop();

  if(page == EMPTY_PAGE)
  {
    quit();
  }
  else
  {
    set_pc(page, offset);
    if(type & FUNCTION)
      store(value);
  }
}

void gosub2(word address)
{
  extern word param_stack[];
  param_stack[0] = 1;
  param_stack[1] = address;
  active_gosub();
}

void gosub4(void)
{
  call(PROCEDURE);
}

void gosub5(word address)
{
  extern word param_stack[];

  param_stack[0] = 1;
  param_stack[1] = address;
  gosub4();
}

void jump(word offset)
{
  move_pc(offset - 2);
}

void ret_true(void)
{
  active_return(1);
}

void ret_false(void)
{
  active_return(0);
}

void ret_value(word result)
{
  byte branch = next_byte();
  word offset = branch & 0x3F;

  /* Bit 6 indicates signed 14 bit branch */
  if((branch & 0x40) == 0)
  {
    /* Get 14 bit quantity */
    offset = make_word(offset, next_byte());
    /* Sign extend from 14 to 16 bits */
    if(offset & 0x2000) offset |= 0xC000;
  }
  else
  {
    /* Unsigned 6 bit branch */
  }
  /* Top bit set implies branch if true */
  if(result == (branch >> 7))
  {
    switch(offset)
    {
      case 0:
        ret_false();
        break;
      case 1:
        ret_true();
        break;
      default:
        jump(offset);
        break;
    }
  }
}

void adv_pop_stack(void)
{
  store(-stk_encode_var());
}

void rts(void)
{
  active_return(stk_pop());
}

void active_gosub(void)
{
  if(hd_five())
    adv_gosub();
  else
    std_gosub();
}

void active_return(word param)
{
  if(hd_five())
    adv_rtn(param);
  else
    std_rtn(param);
}

word special_gosub(word address)
{
  extern word param_stack[];

  word value;
  bool old_stop = stopped();
  set_stop(0);

  stk_push(pc_page());
  set_pc(EMPTY_PAGE, pc_offset());
  param_stack[0] = 1;
  param_stack[1] = address;
  active_gosub();
  execute_opcode();
  value = stk_pop();
  set_pc(stk_pop(), pc_offset());
  set_stop(old_stop);
  return value;
}

void throw_away_stack_frame(word value, word stack_offset)
{
  stk_throw(stack_offset);
  active_return(value);
}

void num_local_params(word num_params)
{
  ret_value(num_params <= (word) local_params);
}
