#include <stdio.h>
#include <stdlib.h>

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "config.h"
#include "lib.h"
#include "type.h"
#include "asm_symbol.h"
#include "asm_code.h"
#include "asm_code_lib.h"
#include "asm_code_thumb2.h"

static char *load_by_size(model_t model)
{
  if (model != NULL) {
    switch (model_get_size(model)) {
    case -1: ERREXIT(1);
    case  1: return "ldrb";
    case  2: return "ldrh";
    default: break;
    }
  }
  return "ldr";
}

static char *store_by_size(model_t model)
{
  if (model != NULL) {
    switch (model_get_size(model)) {
    case -1: ERREXIT(1);
    case  1: return "strb";
    case  2: return "strh";
    default: break;
    }
  }
  return "str";
}

static void get_value(FILE *out, long value)
{
  ASM_CODE_OUT(out, "\tmovw\tr0, #0x%lx\n", value & 0xffff);
  ASM_CODE_OUT(out, "\tmovt\tr0, #0x%lx\n", (value >> 16) & 0xffff);
}

static void get_value_r1(FILE *out, long value)
{
  ASM_CODE_OUT(out, "\tmovw\tr1, #0x%lx\n", value & 0xffff);
  ASM_CODE_OUT(out, "\tmovt\tr1, #0x%lx\n", (value >> 16) & 0xffff);
}

static int _adjust(FILE *out, char *op, char *reg, int *sizep, int max)
{
  int n = 0;

  while (*sizep >= max) {
    ASM_CODE_OUT(out, "\t%s\t%s, #0x%x\n", op, reg, max);
    *sizep -= max;
    n++;
  }

  return n;
}

static void _adjust_size(FILE *out, char *op, char *reg, int size, int max)
{
  _adjust(out, op, reg, &size, max);
  if (size)
    ASM_CODE_OUT(out, "\t%s\t%s, #0x%x\n", op, reg, size);
}

static void get_address_stack(FILE *out, int offset)
{
  ASM_CODE_OUT(out, "\tmov\tr0, sp\n");
  _adjust_size(out, "add", "r0", offset, 0xff);
}

static int get_address_stack_r1(FILE *out, int offset)
{
  ASM_CODE_OUT(out, "\tmov\tr1, sp\n");
  _adjust_size(out, "add", "r1", offset, 0xff);
  return 0;
}

static void get_address(FILE *out, char *label)
{
  ASM_CODE_OUT(out, "\tmovw\tr0, #:lower16:%s\n", label);
  ASM_CODE_OUT(out, "\tmovt\tr0, #:upper16:%s\n", label);
}

static void add_address(FILE *out, int offset)
{
  _adjust_size(out, "add", "r0", offset, 0xff);
}

static void get_r1(FILE *out)
{
  ASM_CODE_OUT(out, "\tmov\tr0, r1\n");
}

static void set_r1(FILE *out)
{
  ASM_CODE_OUT(out, "\tmov\tr1, r0\n");
}

static void _load_store(FILE *out, char *op, char *vreg, char *areg, int offset)
{
  int n, max = 0xf0;

  n = _adjust(out, "add", areg, &offset, max);
  ASM_CODE_OUT(out, "\t%s\t%s, [%s, #0x%x]\n", op, vreg, areg, offset);
  offset = n * max;
  _adjust(out, "sub", areg, &offset, max);
}

static void memory_load(FILE *out, int offset, model_t model)
{
  _load_store(out, load_by_size(model), "r0", "r1", offset);
}

static void memory_store(FILE *out, int offset, model_t model)
{
  _load_store(out, store_by_size(model), "r0", "r1", offset);
}

static void stack_load(FILE *out, int offset, model_t model)
{
  _load_store(out, load_by_size(model), "r0", "sp", offset);
}

static void stack_store(FILE *out, int offset, model_t model)
{
  _load_store(out, store_by_size(model), "r0", "sp", offset);
}

static void stack_load_r1(FILE *out, int offset, model_t model)
{
  _load_store(out, load_by_size(model), "r1", "sp", offset);
}

static void stack_store_r1(FILE *out, int offset, model_t model)
{
  _load_store(out, store_by_size(model), "r1", "sp", offset);
}

static void stack_expand(FILE *out, int size)
{
  _adjust_size(out, "sub", "sp", size, 0xf0);
}

static void stack_reduce(FILE *out, int size)
{
  _adjust_size(out, "add", "sp", size, 0xf0);
}

static char *funccall_regs[] = {
  "r0", "r1", "r2", "r3"
};

static void funccall_reg_load(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\tr0, %s\n", funccall_regs[number]);
}

static void funccall_reg_store(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\t%s, r0\n", funccall_regs[number]);
}

static char *tmp_regs[] = {
  "r4", "r5", "r6",
};

static void tmp_reg_load(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\tr0, %s\n", tmp_regs[number]);
}

static void tmp_reg_save(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\t%s, r0\n", tmp_regs[number]);
}

static void tmp_reg_load_r1(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\tr1, %s\n", tmp_regs[number]);
}

static void tmp_reg_save_r1(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\t%s, r1\n", tmp_regs[number]);
}

static int sign_extension_char(FILE *out)
{
  ASM_CODE_OUT(out, "\tlsl\tr0, r0, #24\n");
  ASM_CODE_OUT(out, "\tasr\tr0, r0, #24\n");
  return 0;
}

static int sign_extension_uchar(FILE *out)
{
  ASM_CODE_OUT(out, "\tlsl\tr0, r0, #24\n");
  ASM_CODE_OUT(out, "\tlsr\tr0, r0, #24\n");
  return 0;
}

static int sign_extension_short(FILE *out)
{
  ASM_CODE_OUT(out, "\tlsl\tr0, r0, #16\n");
  ASM_CODE_OUT(out, "\tasr\tr0, r0, #16\n");
  return 0;
}

static int sign_extension_ushort(FILE *out)
{
  ASM_CODE_OUT(out, "\tlsl\tr0, r0, #16\n");
  ASM_CODE_OUT(out, "\tlsr\tr0, r0, #16\n");
  return 0;
}

static int sign_extension_int(FILE *out)
{
  return -1;
}

static int sign_extension_uint(FILE *out)
{
  return -1;
}

static int calc_inv(FILE *out, model_t model, model_t model_arg0)
{
  ASM_CODE_OUT(out, "\tmvn\tr0, r0\n");
  return 0;
}

static int calc_minus(FILE *out, model_t model, model_t model_arg0)
{
  ASM_CODE_OUT(out, "\tneg\tr0, r0\n");
  return 0;
}

static int calc_op1(FILE *out, c_type_t type, model_t model, model_t model_arg0)
{
  return -1;
}

static void calc_add(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tadd\tr0, r1\n");
}

static void calc_sub(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tsub\tr0, r1\n");
}

static void calc_and(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tand\tr0, r1\n");
}

static void calc_or(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\torr\tr0, r1\n");
}

static int calc_xor(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\teor\tr0, r1\n");
  return 0;
}

static int calc_mul(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tmul\tr0, r1\n");
  return 0;
}

static int calc_div(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  return -1; /* use builtin library */
}

static int calc_mod(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  return -1; /* use builtin library */
}

static int calc_llshift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tlsl\tr0, r1\n");
  return 0;
}

static int calc_rashift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tasr\tr0, r1\n");
  return 0;
}

static int calc_rlshift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tlsr\tr0, r1\n");
  return 0;
}

static int calc_op2(FILE *out, c_type_t type, model_t model, model_t model_arg0, model_t model_arg1)
{
  return -1;
}

#define BRANCH_LONG_JUMP

static void _branch(FILE *out, char *label)
{
#ifndef BRANCH_LONG_JUMP
  ASM_CODE_OUT(out, "\tb\t%s\n", label);
#else
  int n;
  n = asm_code_lib_label_alloc();

  ASM_CODE_OUT(out, "\tldr\tr7, %s\n", asm_code_lib_label_name(n));
  ASM_CODE_OUT(out, "\tbx\tr7\n");

  ASM_CODE_OUT(out, "\t.balign\t4\n");
  ASM_CODE_OUT(out, "%s:\n", asm_code_lib_label_name(n));
  ASM_CODE_OUT(out, "\t.word\t%s + 1\n", label); /* +1 for thumb address */
#endif
}

static void branch(FILE *out, char *label)
{
  _branch(out, label);
}

static void _bxx(FILE *out, char *op, char *label)
{
#ifndef BRANCH_LONG_JUMP
  ASM_CODE_OUT(out, "\t%s\t%s\n", op, label);
#else
  int n0, n1;

  n0 = asm_code_lib_label_alloc();
  n1 = asm_code_lib_label_alloc();

  ASM_CODE_OUT(out, "\t%s\t%s\n", op, asm_code_lib_label_name(n0));
  ASM_CODE_OUT(out, "\tb\t%s\n", asm_code_lib_label_name(n1));

  ASM_CODE_OUT(out, "%s:\n", asm_code_lib_label_name(n0));
  _branch(out, label);

  ASM_CODE_OUT(out, "%s:\n", asm_code_lib_label_name(n1));
#endif
}

static int branch_zero(FILE *out, char *label)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, #0\n");
  _bxx(out, "beq", label);
  return 0;
}

static int branch_nzero(FILE *out, char *label)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, #0\n");
  _bxx(out, "bne", label);
  return 0;
}

static int branch_cmp_eq(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, r1\n");
  _bxx(out, "beq", label);
  return 0;
}

static int branch_cmp_ne(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, r1\n");
  _bxx(out, "bne", label);
  return 0;
}

static int branch_cmp_lt(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, r1\n");
  _bxx(out, "blt", label);
  return 0;
}

static int branch_cmp_gt(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, r1\n");
  _bxx(out, "bgt", label);
  return 0;
}

static int branch_cmp_le(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, r1\n");
  _bxx(out, "ble", label);
  return 0;
}

static int branch_cmp_ge(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, r1\n");
  _bxx(out, "bge", label);
  return 0;
}

static int branch_cmp_ult(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, r1\n");
  _bxx(out, "bcc", label);
  return 0;
}

static int branch_cmp_ugt(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, r1\n");
  _bxx(out, "bhi", label);
  return 0;
}

static int branch_cmp_ule(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, r1\n");
  _bxx(out, "bls", label);
  return 0;
}

static int branch_cmp_uge(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\tr0, r1\n");
  _bxx(out, "bcs", label);
  return 0;
}

static int branch_cmp(FILE *out, char *label, c_type_t type, model_t model)
{
  return -1;
}

static int function_call(FILE *out, char *label)
{
  ASM_CODE_OUT(out, "\tbl\t%s\n", label);
  return 0;
}

static int function_call_set(FILE *out)
{
  ASM_CODE_OUT(out, "\tmov\tr7, r0\n");
  return 0;
}

static int function_call_pointer(FILE *out)
{
#if 0 /* unneed */
  ASM_CODE_OUT(out, "\torr\tr7, #1\n"); /* for thumb address */
#endif
  ASM_CODE_OUT(out, "\tblx\tr7\n");
  return 0;
}

static void function_start(FILE *out)
{
  ASM_CODE_OUT(out, "\t.thumb\n");
  ASM_CODE_OUT(out, "\tpush\t{lr}\n");
}

static void function_end(FILE *out)
{
  ASM_CODE_OUT(out, "\tpop\t{r1}\n");
  ASM_CODE_OUT(out, "\tbx\tr1\n");
}

static void function_register_save(FILE *out)
{
  ASM_CODE_OUT(out, "\tpush\t{r4, r5, r6, r7}\n");
}

static void function_register_load(FILE *out)
{
  ASM_CODE_OUT(out, "\tpop\t{r4, r5, r6, r7}\n");
}

struct asm_code_callback asm_code_callback_thumb2 = {
  "%", /* type mark */

  4, /* word size */
  4, /* pointer size */
  4, /* funccall args reg number: r0/r1/r2/r3 (See funccall_regs[]) */
  VALUE_FUNCTION_ARGS_NUM, /* funccall args stack number */
  3, /* tmp reg number: r4/r5/r6 (See tmp_regs[]) */
  VALUE_FUNCTION_ARGS_NUM, /* tmp stack number */
  4, /* function register number: r4/r5/r6/r7 (See function_register_save() */
  1, /* function saveparam number: lr (See function_start() */
  16, /* stack align size */
  0, /* stack correct size */

  get_value,
  get_value_r1,
  get_address_stack,
  get_address_stack_r1,
  get_address,
  add_address,
  get_r1,
  set_r1,

  memory_load,
  memory_store,
  stack_load,
  stack_store,
  stack_load_r1,
  stack_store_r1,
  stack_expand,
  stack_reduce,

  funccall_reg_load,
  funccall_reg_store,
  tmp_reg_load,
  tmp_reg_save,
  tmp_reg_load_r1,
  tmp_reg_save_r1,

  sign_extension_char,
  sign_extension_uchar,
  sign_extension_short,
  sign_extension_ushort,
  sign_extension_int,
  sign_extension_uint,

  calc_inv,
  calc_minus,

  calc_op1,

  calc_add,
  calc_sub,
  calc_and,
  calc_or,
  calc_xor,

  calc_mul,
  calc_div,
  calc_mod,
  calc_llshift,
  calc_rashift,
  calc_rlshift,

  calc_op2,

  branch,
  branch_zero,
  branch_nzero,

  branch_cmp_eq,
  branch_cmp_ne,

  branch_cmp_lt,
  branch_cmp_gt,
  branch_cmp_le,
  branch_cmp_ge,
  branch_cmp_ult,
  branch_cmp_ugt,
  branch_cmp_ule,
  branch_cmp_uge,

  branch_cmp,

  function_call,
  function_call_set,
  function_call_pointer,

  function_start,
  function_end,
  function_register_save,
  function_register_load,
};
