
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <orc/orc.h>
#include <orc/orcbytecode.h>
#include <orc/orcutils-private.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


void bytecode_append_code (OrcBytecode *bytecode, int code);
void bytecode_append_int (OrcBytecode *bytecode, int value);
void bytecode_append_uint32 (OrcBytecode *bytecode, orc_uint32 value);
void bytecode_append_uint64 (OrcBytecode *bytecode, orc_uint64 value);
void bytecode_append_string (OrcBytecode *bytecode, char *s);


OrcBytecode *
orc_bytecode_new (void)
{
  OrcBytecode *bytecode;

  bytecode = orc_malloc (sizeof(OrcBytecode));
  memset (bytecode, 0, sizeof(OrcBytecode));

  bytecode->alloc_len = 256;
  bytecode->bytecode = orc_malloc(bytecode->alloc_len);

  return bytecode;
}

void
orc_bytecode_free (OrcBytecode *bytecode)
{
  free (bytecode->bytecode);
  free (bytecode);
}

OrcBytecode *
orc_bytecode_from_program (OrcProgram *p)
{
  OrcBytecode *bytecode = orc_bytecode_new ();
  int i;
  OrcVariable *var;
  OrcOpcodeSet *opcode_set;

  opcode_set = orc_opcode_set_get ("sys");

  bytecode_append_code (bytecode, ORC_BC_BEGIN_FUNCTION);

  if (p->constant_n != 0) {
    bytecode_append_code (bytecode, ORC_BC_SET_CONSTANT_N);
    bytecode_append_int (bytecode, p->constant_n);
  }
  if (p->n_multiple != 0) {
    bytecode_append_code (bytecode, ORC_BC_SET_N_MULTIPLE);
    bytecode_append_int (bytecode, p->n_multiple);
  }
  if (p->n_minimum != 0) {
    bytecode_append_code (bytecode, ORC_BC_SET_N_MINIMUM);
    bytecode_append_int (bytecode, p->n_minimum);
  }
  if (p->n_maximum != 0) {
    bytecode_append_code (bytecode, ORC_BC_SET_N_MAXIMUM);
    bytecode_append_int (bytecode, p->n_maximum);
  }
  if (p->is_2d) {
    bytecode_append_code (bytecode, ORC_BC_SET_2D);
    if (p->constant_m != 0) {
      bytecode_append_code (bytecode, ORC_BC_SET_CONSTANT_M);
      bytecode_append_int (bytecode, p->constant_m);
    }
  }
  if (p->name) {
    bytecode_append_code (bytecode, ORC_BC_SET_NAME);
    bytecode_append_string (bytecode, p->name);
  }
#if 0
  /* if (!is_inline) { */
  if (p->backup_function) {
    bytecode_append_code (bytecode, ORC_BC_SET_BACKUP_FUNCTION);
    bytecode_pointer (bytecode, p->backup_function);
  }
#endif
  for(i=0;i<4;i++){
    var = &p->vars[ORC_VAR_D1 + i];
    if (var->size) {
      bytecode_append_code (bytecode, ORC_BC_ADD_DESTINATION);
      bytecode_append_int (bytecode, var->size);
      bytecode_append_int (bytecode, var->alignment);
    }
  }
  for(i=0;i<8;i++){
    var = &p->vars[ORC_VAR_S1 + i];
    if (var->size) {
      bytecode_append_code (bytecode, ORC_BC_ADD_SOURCE);
      bytecode_append_int (bytecode, var->size);
      bytecode_append_int (bytecode, var->alignment);
    }
  }
  for(i=0;i<4;i++){
    var = &p->vars[ORC_VAR_A1 + i];
    if (var->size) {
      bytecode_append_code (bytecode, ORC_BC_ADD_ACCUMULATOR);
      bytecode_append_int (bytecode, var->size);
      /* bytecode_append_int (bytecode, var->alignment); */
    }
  }
  for(i=0;i<8;i++){
    var = &p->vars[ORC_VAR_C1 + i];
    if (var->size == 0) continue;
    if (var->size <= 4) {
      bytecode_append_code (bytecode, ORC_BC_ADD_CONSTANT);
      bytecode_append_int (bytecode, var->size);
      bytecode_append_uint32 (bytecode, (orc_uint32)var->value.i);
    } else if (var->size > 4) {
      bytecode_append_code (bytecode, ORC_BC_ADD_CONSTANT_INT64);
      bytecode_append_int (bytecode, var->size);
      bytecode_append_uint64 (bytecode, (orc_uint64)var->value.i);
    }
  }
  for(i=0;i<8;i++){
    var = &p->vars[ORC_VAR_P1 + i];
    if (var->size) {
      switch (var->param_type) {
        case ORC_PARAM_TYPE_INT:
          bytecode_append_code (bytecode, ORC_BC_ADD_PARAMETER);
          break;
        case ORC_PARAM_TYPE_FLOAT:
          bytecode_append_code (bytecode, ORC_BC_ADD_PARAMETER_FLOAT);
          break;
        case ORC_PARAM_TYPE_INT64:
          bytecode_append_code (bytecode, ORC_BC_ADD_PARAMETER_INT64);
          break;
        case ORC_PARAM_TYPE_DOUBLE:
          bytecode_append_code (bytecode, ORC_BC_ADD_PARAMETER_INT64);
          break;
        default:
          ORC_ASSERT(0);
          break;
      }
      bytecode_append_int (bytecode, var->size);
    }
  }
  for(i=0;i<16;i++){
    var = &p->vars[ORC_VAR_T1 + i];
    if (var->size) {
      bytecode_append_code (bytecode, ORC_BC_ADD_TEMPORARY);
      bytecode_append_int (bytecode, var->size);
    }
  }

  for(i=0;i<p->n_insns;i++){
    OrcInstruction *insn = p->insns + i;

    if (insn->flags) {
      bytecode_append_code (bytecode, ORC_BC_INSTRUCTION_FLAGS);
      bytecode_append_int (bytecode, insn->flags);
    }
    bytecode_append_code (bytecode, (insn->opcode - opcode_set->opcodes) + 32);
    if (insn->opcode->dest_size[0] != 0) {
      bytecode_append_int (bytecode, insn->dest_args[0]);
    }
    if (insn->opcode->dest_size[1] != 0) {
      bytecode_append_int (bytecode, insn->dest_args[1]);
    }
    if (insn->opcode->src_size[0] != 0) {
      bytecode_append_int (bytecode, insn->src_args[0]);
    }
    if (insn->opcode->src_size[1] != 0) {
      bytecode_append_int (bytecode, insn->src_args[1]);
    }
    if (insn->opcode->src_size[2] != 0) {
      bytecode_append_int (bytecode, insn->src_args[2]);
    }
  }

  bytecode_append_code (bytecode, ORC_BC_END_FUNCTION);
  bytecode_append_code (bytecode, ORC_BC_END);

  return bytecode;
}

void
bytecode_append_byte (OrcBytecode *bytecode, int byte)
{
  if (bytecode->length >= bytecode->alloc_len) {
    bytecode->alloc_len += 256;
    bytecode->bytecode = orc_realloc (bytecode->bytecode, bytecode->alloc_len);
  }
  bytecode->bytecode[bytecode->length] = byte;
  bytecode->length++;
}

void
bytecode_append_code (OrcBytecode *bytecode, int code)
{
  bytecode_append_byte (bytecode, code);
#if 0
  OrcOpcodeSet *opcode_set = orc_opcode_set_get ("sys");

  fprintf(bytecode, "\n  ");
  if (code >= 32) {
    fprintf(bytecode, "ORC_BC_%s, ", opcode_set->opcodes[code-32].name);
  } else {
    static char *codes[32] = {
      "END",
      "BEGIN_FUNCTION",
      "END_FUNCTION",
      "SET_CONSTANT_N",
      "SET_N_MULTIPLE",
      "SET_N_MINIMUM",
      "SET_N_MAXIMUM",
      "SET_2D",
      "SET_CONSTANT_M",
      "SET_NAME",
      "SET_BACKUP_FUNCTION",
      "ADD_DESTINATION",
      "ADD_SOURCE",
      "ADD_ACCUMULATOR",
      "ADD_CONSTANT",
      "ADD_CONSTANT_INT64",
      "ADD_PARAMETER",
      "ADD_PARAMETER_FLOAT",
      "ADD_PARAMETER_INT64",
      "ADD_PARAMETER_DOUBLE",
      "ADD_TEMPORARY",
      "RESERVED_21",
      "RESERVED_22",
      "RESERVED_23",
      "RESERVED_24",
      "RESERVED_25",
      "RESERVED_26",
      "RESERVED_27",
      "RESERVED_28",
      "RESERVED_29",
      "RESERVED_30",
      "RESERVED_31"
    };

    fprintf(bytecode, "ORC_BC_%s, ", codes[code]);
  }
#endif
}

void
bytecode_append_int (OrcBytecode *bytecode, int value)
{
  ORC_ASSERT(value >= 0);

  if (value < 255) {
    bytecode_append_byte (bytecode, value);
  } else if (value < 65535) {
    bytecode_append_byte (bytecode, 255);
    bytecode_append_byte (bytecode, value & 0xff);
    bytecode_append_byte (bytecode, value >> 8);
  } else {
    ORC_ASSERT(0);
  }
}

void
bytecode_append_uint32 (OrcBytecode *bytecode, orc_uint32 value)
{
  bytecode_append_byte (bytecode, value & 0xff);
  bytecode_append_byte (bytecode, (value >> 8) & 0xff);
  bytecode_append_byte (bytecode, (value >> 16) & 0xff);
  bytecode_append_byte (bytecode, (value >> 24) & 0xff);

}

void
bytecode_append_uint64 (OrcBytecode *bytecode, orc_uint64 value)
{
  bytecode_append_byte (bytecode, value & 0xff);
  bytecode_append_byte (bytecode, (value >> 8) & 0xff);
  bytecode_append_byte (bytecode, (value >> 16) & 0xff);
  bytecode_append_byte (bytecode, (value >> 24) & 0xff);
  bytecode_append_byte (bytecode, (value >> 32) & 0xff);
  bytecode_append_byte (bytecode, (value >> 40) & 0xff);
  bytecode_append_byte (bytecode, (value >> 48) & 0xff);
  bytecode_append_byte (bytecode, (value >> 56) & 0xff);

}

void
bytecode_append_string (OrcBytecode *bytecode, char *s)
{
  int i;
  int len = strlen(s);
  bytecode_append_int (bytecode, len);
  for(i=0;i<len;i++){
    bytecode_append_byte (bytecode, s[i]);
  }
}

typedef struct _OrcBytecodeParse OrcBytecodeParse;
struct _OrcBytecodeParse {
  const orc_uint8 *bytecode;
  int parse_offset;
  int function_start;
  int code_start;
};

static int
orc_bytecode_parse_get_byte (OrcBytecodeParse *parse)
{
  int value;
  value = parse->bytecode[parse->parse_offset];
  parse->parse_offset++;
  return value;
}

static int
orc_bytecode_parse_get_int (OrcBytecodeParse *parse)
{
  int value;

  value = orc_bytecode_parse_get_byte(parse);
  if (value == 255) {
    value = orc_bytecode_parse_get_byte(parse);
    value |= orc_bytecode_parse_get_byte(parse) << 8;
  }

  return value;
}

static char *
orc_bytecode_parse_get_string (OrcBytecodeParse *parse)
{
  int len;
  int i;
  char *s;

  len = orc_bytecode_parse_get_int (parse);
  s = orc_malloc (len + 1);
  for(i=0;i<len;i++){
    s[i] = orc_bytecode_parse_get_byte (parse);
  }
  s[i] = 0;

  return s;
}

static orc_uint32
orc_bytecode_parse_get_uint32 (OrcBytecodeParse *parse)
{
  orc_uint32 value;
  value = orc_bytecode_parse_get_byte (parse);
  value |= ((orc_uint32)orc_bytecode_parse_get_byte (parse)) << 8;
  value |= ((orc_uint32)orc_bytecode_parse_get_byte (parse)) << 16;
  value |= ((orc_uint32)orc_bytecode_parse_get_byte (parse)) << 24;
  return value;
}

static orc_uint64
orc_bytecode_parse_get_uint64 (OrcBytecodeParse *parse)
{
  orc_uint64 value;

  value = ((orc_uint64)orc_bytecode_parse_get_byte (parse));
  value |= ((orc_uint64)orc_bytecode_parse_get_byte (parse)) << 8;
  value |= ((orc_uint64)orc_bytecode_parse_get_byte (parse)) << 16;
  value |= ((orc_uint64)orc_bytecode_parse_get_byte (parse)) << 24;
  value |= ((orc_uint64)orc_bytecode_parse_get_byte (parse)) << 32;
  value |= ((orc_uint64)orc_bytecode_parse_get_byte (parse)) << 40;
  value |= ((orc_uint64)orc_bytecode_parse_get_byte (parse)) << 48;
  value |= ((orc_uint64)orc_bytecode_parse_get_byte (parse)) << 56;

  return value;
}

int
orc_bytecode_parse_function (OrcProgram *program, const orc_uint8 *bytecode)
{
  OrcBytecodeParse _parse;
  OrcBytecodeParse *parse = &_parse;
  /* int in_function = FALSE; */
  int bc;
  int size;
  int alignment;
  OrcOpcodeSet *opcode_set;
  int instruction_flags = 0;

  memset (parse, 0, sizeof(*parse));
  parse->bytecode = bytecode;

  opcode_set = orc_opcode_set_get ("sys");

  while (1) {
    bc = orc_bytecode_parse_get_int (parse);
    if (bc < ORC_BC_absb) {
      switch (bc) {
        case ORC_BC_END:
          /* FIXME this is technically an error */
          return 0;
        case ORC_BC_BEGIN_FUNCTION:
          /* in_function = TRUE; */
          break;
        case ORC_BC_END_FUNCTION:
          return 0;
        case ORC_BC_SET_CONSTANT_N:
          program->constant_n = orc_bytecode_parse_get_int (parse);
          break;
        case ORC_BC_SET_N_MULTIPLE:
          program->n_multiple = orc_bytecode_parse_get_int (parse);
          break;
        case ORC_BC_SET_N_MINIMUM:
          program->n_minimum = orc_bytecode_parse_get_int (parse);
          break;
        case ORC_BC_SET_N_MAXIMUM:
          program->n_maximum = orc_bytecode_parse_get_int (parse);
          break;
        case ORC_BC_SET_2D:
          program->is_2d = TRUE;
          break;
        case ORC_BC_SET_CONSTANT_M:
          program->constant_m = orc_bytecode_parse_get_int (parse);
          break;
        case ORC_BC_SET_NAME:
          if (program->name) {
            free (program->name);
          }
          program->name = orc_bytecode_parse_get_string (parse);
          break;
        case ORC_BC_SET_BACKUP_FUNCTION:
          /* FIXME error */
          break;
        case ORC_BC_ADD_DESTINATION:
          size = orc_bytecode_parse_get_int (parse);
          alignment = orc_bytecode_parse_get_int (parse);
          orc_program_add_destination_full (program, size, "d", "unknown",
              alignment);
          break;
        case ORC_BC_ADD_SOURCE:
          size = orc_bytecode_parse_get_int (parse);
          alignment = orc_bytecode_parse_get_int (parse);
          orc_program_add_source_full (program, size, "s", "unknown",
              alignment);
          break;
        case ORC_BC_ADD_ACCUMULATOR:
          size = orc_bytecode_parse_get_int (parse);
          orc_program_add_accumulator (program, size, "a");
          break;
        case ORC_BC_ADD_CONSTANT:
          {
            orc_uint32 value;
            size = orc_bytecode_parse_get_int (parse);
            value = orc_bytecode_parse_get_uint32 (parse);
            orc_program_add_constant (program, size, value, "c");
          }
          break;
        case ORC_BC_ADD_CONSTANT_INT64:
          {
            orc_uint64 value;
            size = orc_bytecode_parse_get_int (parse);
            value = orc_bytecode_parse_get_uint64 (parse);
            orc_program_add_constant_int64 (program, size, value, "c");
          }
          break;
        case ORC_BC_ADD_PARAMETER:
          size = orc_bytecode_parse_get_int (parse);
          orc_program_add_parameter (program, size, "p");
          break;
        case ORC_BC_ADD_PARAMETER_FLOAT:
          size = orc_bytecode_parse_get_int (parse);
          orc_program_add_parameter_float (program, size, "p");
          break;
        case ORC_BC_ADD_PARAMETER_INT64:
          size = orc_bytecode_parse_get_int (parse);
          orc_program_add_parameter_int64 (program, size, "p");
          break;
        case ORC_BC_ADD_PARAMETER_DOUBLE:
          size = orc_bytecode_parse_get_int (parse);
          orc_program_add_parameter_double (program, size, "p");
          break;
        case ORC_BC_ADD_TEMPORARY:
          size = orc_bytecode_parse_get_int (parse);
          orc_program_add_temporary (program, size, "t");
          break;
        case ORC_BC_INSTRUCTION_FLAGS:
          instruction_flags = orc_bytecode_parse_get_int (parse);
          break;
        default:
          break;
      }
    } else {
      OrcInstruction *insn;

      insn = program->insns + program->n_insns;

      insn->opcode = opcode_set->opcodes + (bc - 32);
      if (insn->opcode->dest_size[0] != 0) {
        insn->dest_args[0] = orc_bytecode_parse_get_int (parse);
      }
      if (insn->opcode->dest_size[1] != 0) {
        insn->dest_args[1] = orc_bytecode_parse_get_int (parse);
      }
      if (insn->opcode->src_size[0] != 0) {
        insn->src_args[0] = orc_bytecode_parse_get_int (parse);
      }
      if (insn->opcode->src_size[1] != 0) {
        insn->src_args[1] = orc_bytecode_parse_get_int (parse);
      }
      if (insn->opcode->src_size[2] != 0) {
        insn->src_args[2] = orc_bytecode_parse_get_int (parse);
      }
      insn->flags = instruction_flags;
      instruction_flags = 0;

      program->n_insns++;
    }
  }
}

