
#ifndef variable_c_INCLUDED 
#define variable_c_INCLUDED

#include <stdio.h>

#include "tracelog.h"
#include "fizmo.h"
#include "stack.h"
#include "zpu.h"
#include "i18n.h"
#include "config.h"
#include "splint.h"


/*@dependent@*/ uint16_t *local_variable_storage_index;
uint8_t number_of_locals_active;


void set_variable(uint8_t variable_number, uint16_t data)
{

  if (variable_number == 0)
  {
    if (stack_words_from_active_routine == MAXIMUM_STACK_ENTRIES_PER_ROUTINE)
      i18n_translate_and_exit(
          i18n_fizmo_MAXIMUM_NUMBER_OF_STACK_ENTRIES_PER_ROUTINE_P0D_EXCEEDED,
          -1,
          (long int)MAXIMUM_STACK_ENTRIES_PER_ROUTINE);

    z_stack_push_word(data);
    stack_words_from_active_routine++;
  }
  else if (variable_number < 0x10)
  {
    if (variable_number > number_of_locals_active)
      i18n_translate_and_exit(
          i18n_fizmo_TRYING_TO_STORE_VARIABLE_L_P0D_BUT_ONLY_P1D_VARIABLES_ACTIVE,
          -1,
         (long int)variable_number-1,
         (long int)number_of_locals_active);

    variable_number--;
    TRACE_LOG("Storing %x in L0%x.\n", data, variable_number);
    local_variable_storage_index[variable_number] = data;
  }
  else
  {
    variable_number -= 0x10;
    TRACE_LOG("Setting global variable G%02x to %x.\n", variable_number, data);
    store_word(
        /*@-nullderef@*/ active_z_story->global_variables /*@-nullderef@*/
        +(variable_number*2),
        data);
  }
}


uint16_t get_variable(uint8_t variable_number)
{
  uint16_t result;

  if (variable_number == 0)
  {
    if (stack_words_from_active_routine == 0)
    {
      if (bool_equal(skip_active_routines_stack_check_warning, false))
        i18n_translate_and_exit(
            i18n_fizmo_NOT_ENOUGH_STACK_WORDS_FROM_LOCAL_ROUTINE_ON_STACK, -1);
      else
        return 0;
    }

    result = z_stack_pull_word();
    stack_words_from_active_routine--;
    return result;
  }
  else if (variable_number < 0x10)
  {
    if (variable_number > number_of_locals_active)
      i18n_translate_and_exit(
          i18n_fizmo_TRYING_TO_STORE_VARIABLE_L_P0D_BUT_ONLY_P1D_VARIABLES_ACTIVE,
          -1,
          (long int)variable_number-1,
          (long int)number_of_locals_active);

    variable_number--;
    result = local_variable_storage_index[variable_number];
    TRACE_LOG("Reading %x from L0%x.\n", result, variable_number);
    return result;
  }
  else
  {
    variable_number -= 0x10;
    result = load_word(active_z_story->global_variables+(variable_number*2));
    TRACE_LOG("Reading %x from global variable G%02x.\n",
        result, variable_number);
    return result;
  }
}


void opcode_pull(void)
{
  uint16_t value = 0;

  TRACE_LOG("Opcode: PULL.\n");
  TRACE_LOG("Pulling to variable %x.\n", op[0]);

  if (
      (stack_words_from_active_routine == 0)
      &&
      (bool_equal(skip_active_routines_stack_check_warning, false))
     )
    i18n_translate_and_exit(
        i18n_fizmo_NOT_ENOUGH_STACK_WORDS_FROM_LOCAL_ROUTINE_ON_STACK, -1);
  else
  {
    value = z_stack_pull_word();
    stack_words_from_active_routine--;
  }

  if (ver == 6)
  {
    // FIXME: Implement op[0] as game stack
    i18n_translate_and_exit(i18n_fizmo_NOT_YET_IMPLEMENTED, -1);

    //(void)read_z_result_variable();
    //set_variable(z_res_var, value);
  }
  else
  {
    set_variable(
        op[0],
        /*@-usedef@*/ value /*@-usedef@*/ );
  }
}


void opcode_push(void)
{
  TRACE_LOG("Opcode: PUSH.\n");
  TRACE_LOG("Pushing %x to stack.\n", op[0]);

  if (stack_words_from_active_routine == MAXIMUM_STACK_ENTRIES_PER_ROUTINE)
    i18n_translate_and_exit(
        i18n_fizmo_MAXIMUM_NUMBER_OF_STACK_ENTRIES_PER_ROUTINE_P0D_EXCEEDED,
        -1,
        (long int)MAXIMUM_STACK_ENTRIES_PER_ROUTINE);

  z_stack_push_word(op[0]);
  stack_words_from_active_routine++;
}


void opcode_loadb(void)
{
  uint8_t *address = z_mem + op[0] + op[1];

  TRACE_LOG("Opcode: LOADB.\n");

  read_z_result_variable();

  if (address > active_z_story->static_memory_end)
  {
    TRACE_LOG("Trying to loadb from %x which is above static memory.",
        op[0] + op[1]);
    set_variable(z_res_var, 0);
  }
  else
  {
    TRACE_LOG("Loading %x at %x[%x] = %x to var %x.\n",
        *address,
        op[0],
        op[1],
        op[0] + op[1],
        z_res_var);
    set_variable(z_res_var, *address);
  }
}


void opcode_store(void)
{
  TRACE_LOG("Opcode: STORE.\n");
  TRACE_LOG("Writing %x to variable %x.\n", op[1], op[0]);

  set_variable(op[0], op[1]);
}


void opcode_storew(void)
{
  uint8_t *address = z_mem + op[0] + op[1]*2;

  TRACE_LOG("Opcode: STOREW.\n");

  if (address > active_z_story->dynamic_memory_end)
  {
    TRACE_LOG("Trying to storew to %x which is above dynamic memory.",
        op[0] + op[1]*2);
  }
  else
  {
    TRACE_LOG("Storing %x at %x + %x*2 = %x.\n",
        op[2], op[0], op[1], op[0]+op[1]*2);
    store_word(z_mem + op[0] + op[1]*2, op[2]);
  }
}


void opcode_loadw(void)
{
  uint16_t value;
  uint8_t *address = z_mem + op[0] + op[1]*2;

  TRACE_LOG("Opcode: LOADW.\n");

  read_z_result_variable();

  if (address > active_z_story->static_memory_end)
  {
    TRACE_LOG("ERROR: Trying to loadw from %x which is above static memory.\n",
        op[0] + op[1]*2);
    set_variable(z_res_var, 0);
  }
  else
  {
    value = load_word(address);
    TRACE_LOG("Loading %x at %x + %x*2 = %x to var %x.\n",
        value, op[0], op[1], op[0]+op[1]*2, z_res_var);
    set_variable(z_res_var, value);
  }
}


void opcode_inc(void)
{
  int16_t value;
  
  TRACE_LOG("Opcode: INC.\n");
  value = (int16_t)get_variable(op[0]);
  TRACE_LOG("Incrementing variable %d from %d to %d.\n", op[0], value, value+1);

  set_variable(op[0], (uint16_t)(value + 1));
}


void opcode_storeb(void)
{
  uint8_t *address = z_mem + op[0] + op[1];

  TRACE_LOG("Opcode: STOREB.\n");

  if (address > active_z_story->dynamic_memory_end)
  {
    TRACE_LOG("Trying to storeb to %x which is above dynamic memory.",
        op[0] + op[1]);
  }
  else
  {
    TRACE_LOG("Storing $%x at $%x + $%x = %x.\n",
        op[2], op[0], op[1], op[0]+op[1]);

    *(z_mem + op[0] + op[1]) = op[2];
  }
}


void opcode_dec(void)
{
  int16_t value;
  
  TRACE_LOG("Opcode: DEC.\n");
  value = (int16_t)get_variable(op[0]);
  TRACE_LOG("Decrementing variable %d from %d to %d.\n",
      op[0], value, value-1);

  set_variable(op[0], (uint16_t)(value - 1));
}


void opcode_load(void)
{
  TRACE_LOG("Opcode: LOAD.\n");

  read_z_result_variable();
  
  TRACE_LOG("Loading variable with code %d to variable with code %d.\n",
      op[0], z_res_var);

  set_variable(z_res_var, get_variable(op[0]));
}


void opcode_pop(void)
{
  TRACE_LOG("Opcode: POP.\n");

  if (stack_words_from_active_routine == 0)
    i18n_translate_and_exit(
        i18n_fizmo_NOT_ENOUGH_STACK_WORDS_FROM_LOCAL_ROUTINE_ON_STACK, -1);

  (void)z_stack_pull_word();
  stack_words_from_active_routine--;
}


void opcode_check_arg_count(void)
{
  TRACE_LOG("Opcode: CHECK_ARG_COUNT.\n");

  evaluate_branch(
      (uint8_t)
      ( ((int16_t)op[0]) <= ((int16_t)number_of_locals_from_function_call)
        ? 1 : 0) );
}

#endif /* variable_c_INCLUDED */

