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

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

#include "config.h"
#include "lib.h"
#include "objlist.h"
#include "type.h"
#include "syntax.h"
#include "value.h"
#include "model.h"
#include "area.h"
#include "asm_code.h"

/*****************************************************************
 * model_t
 */

model_t model_destroy(model_t model)
{
  int i;

  if (model != NULL) {
    if (model->type == C_TYPE_FUNCTION) {
      if (model->function.retval != NULL)
	model_destroy(model->function.retval);
      for (i = 0; i < model->function.args.number; i++) {
	if (model->function.args.arg[i] != NULL)
	  model_destroy(model->function.args.arg[i]);
      }
    }

    memset(model, 0, sizeof(*model));
    free(model);
  }

  return NULL;
}

model_t model_create(c_type_t type)
{
  model_t model;

  model = malloc(sizeof(*model));
  if (model == NULL)
    goto err;
  memset(model, 0, sizeof(*model));

  model->type = type;
  model->flags = 0;
  model->modifiers.number = 0;
  model->function.args.number = 0;

  return model;

err:
  if (model != NULL)
    model_destroy(model);
  return NULL;
}

int model_get_modifier_number(model_t model)
{
  return model->modifiers.number;
}

model_modifier_t model_get_modifier_first(model_t model)
{
  if (model->modifiers.number == 0)
    return NULL;
  return &model->modifiers.modifier[0];
}

model_modifier_t model_get_modifier_last(model_t model)
{
  if (model->modifiers.number == 0)
    return NULL;
  return &model->modifiers.modifier[model->modifiers.number - 1];
}

int model_add_modifier_pointer(model_t model)
{
  model_modifier_t m;
  if (model->modifiers.number >= MODEL_MODIFIER_NUM)
    return -1;
  m = &model->modifiers.modifier[model->modifiers.number];
  m->type = C_TYPE_POINTER;
  model->modifiers.number++;
  return model->modifiers.number;
}

int model_add_modifier_array(model_t model, int number)
{
  model_modifier_t m;
  if (model->modifiers.number >= MODEL_MODIFIER_NUM)
    return -1;
  m = &model->modifiers.modifier[model->modifiers.number];
  m->type = C_TYPE_ARRAY;
  m->param.array.number = number;
  model->modifiers.number++;
  return model->modifiers.number;
}

int model_insert_modifier_pointer(model_t model)
{
  model_modifier_t m;
  if (model->modifiers.number >= MODEL_MODIFIER_NUM)
    return -1;
  memmove(&model->modifiers.modifier[1], &model->modifiers.modifier[0],
	  sizeof(model->modifiers.modifier[0]) * model->modifiers.number);
  m = &model->modifiers.modifier[0];
  memset(m, 0, sizeof(*m));
  m->type = C_TYPE_POINTER;
  model->modifiers.number++;
  return model->modifiers.number;
}

int model_insert_modifier_array(model_t model, int number)
{
  model_modifier_t m;
  if (model->modifiers.number >= MODEL_MODIFIER_NUM)
    return -1;
  memmove(&model->modifiers.modifier[1], &model->modifiers.modifier[0],
	  sizeof(model->modifiers.modifier[0]) * model->modifiers.number);
  m = &model->modifiers.modifier[0];
  memset(m, 0, sizeof(*m));
  m->type = C_TYPE_ARRAY;
  m->param.array.number = number;
  model->modifiers.number++;
  return model->modifiers.number;
}

int model_del_modifier(model_t model)
{
  model_modifier_t m;
  if (model->modifiers.number == 0)
    return -1;
  model->modifiers.number--;
  m = &model->modifiers.modifier[model->modifiers.number];
  memset(m, 0, sizeof(*m));
  return model->modifiers.number;
}

int model_is_pointer(model_t model)
{
  if ((model->modifiers.number > 0) &&
      (model_get_modifier_last(model)->type == C_TYPE_POINTER))
    return 1;
  return 0;
}

int model_is_array(model_t model)
{
  if ((model->modifiers.number > 0) &&
      (model_get_modifier_last(model)->type == C_TYPE_ARRAY))
    return 1;
  return 0;
}

int model_is_address(model_t model)
{
  if (model_is_pointer(model) || model_is_array(model))
    return 1;
  return 0;
}

model_t model_add_function_args(model_t model, model_t arg)
{
  int n;

  n = model->function.args.number;
  if (n >= MODEL_FUNCTION_ARGS_NUM)
    return NULL;
  model->function.args.arg[n] = model_copy(arg);
  model->function.args.number++;

  return model->function.args.arg[n];
}

model_t model_copy(model_t model)
{
  model_t new_model, retval, arg;
  int i;

  new_model = model_create(C_TYPE_NONE);
  if (new_model == NULL)
    goto err;

  memcpy(new_model, model, sizeof(*new_model));

  if (model->type == C_TYPE_FUNCTION) {
    memset(&new_model->function, 0, sizeof(new_model->function));
    if (model->function.retval != NULL) {
      retval = model_copy(model->function.retval);
      if (retval == NULL)
	goto err;
      new_model->function.retval = retval;
    }
    new_model->function.args.number = model->function.args.number;
    for (i = 0; i < new_model->function.args.number; i++) {
      if (model->function.args.arg[i] != NULL) {
	arg = model_copy(model->function.args.arg[i]);
	if (arg == NULL)
	  goto err;
	new_model->function.args.arg[i] = arg;
      }
    }
  }

  return new_model;

err:
  if (new_model != NULL)
    model_destroy(new_model);
  return NULL;
}

static int model_cmp_modifier(model_modifier_t modifier1, model_modifier_t modifier2)
{
  if ((modifier1->type != C_TYPE_ARRAY) && (modifier1->type != C_TYPE_POINTER))
    return 1;
  if ((modifier2->type != C_TYPE_ARRAY) && (modifier2->type != C_TYPE_POINTER))
    return 1;

  if ((modifier1->type == C_TYPE_ARRAY) && (modifier2->type == C_TYPE_ARRAY)) {
    if ((modifier1->param.array.number != modifier2->param.array.number) &&
	!(modifier1->param.array.number < 0) && !(modifier2->param.array.number < 0))
      return 1;
  }

  return 0;
}

int model_cmp(model_t model1, model_t model2)
{
  int i;

  if (model1->type != model2->type)
    return 1;
  if (model1->flags != model2->flags)
    return 1;

  switch (model1->type) {
  case C_TYPE_STRUCT:
  case C_TYPE_UNION:
  case C_TYPE_ENUM:
  case C_TYPE_TYPEDEF:
    if (model1->custom.p != model2->custom.p)
      return 1;
    break;
  default:
    break;
  }

  if (model1->modifiers.number != model2->modifiers.number)
    return 1;
  for (i = 0; i < model1->modifiers.number; i++) {
    if (model_cmp_modifier(&model1->modifiers.modifier[i],
			   &model2->modifiers.modifier[i]))
      return 1;
  }

  if (model1->type == C_TYPE_FUNCTION) {
    if (model_cmp(model1->function.retval, model2->function.retval))
      return 1;
    if (model1->function.args.number != model2->function.args.number)
      return 1;
    for (i = 0; i < model1->function.args.number; i++) {
      if (model_cmp(model1->function.args.arg[i], model2->function.args.arg[i]))
	return 1;
    }
  }

  return 0;
}

void model_print(model_t model, int indent)
{
  int i, n, size;

  indent_print(indent);
  printf("MODEL:  ");
  if (model->flags & MODEL_FLAG_UNSIGNED)
    printf("unsigned ");
  printf("%s ", type_get_word(model->type));

  switch (model->type) {
  case C_TYPE_STRUCT:  printf("%s ", model->custom._struct->name);  break;
  case C_TYPE_UNION:   printf("%s ", model->custom._union->name);   break;
  case C_TYPE_ENUM:    printf("%s ", model->custom._enum->name);    break;
  case C_TYPE_TYPEDEF: printf("%s ", model->custom._typedef->name); break;
  default: break;
  }

  for (i = 0; i < model->modifiers.number; i++) {
    switch (model->modifiers.modifier[i].type) {
    case C_TYPE_POINTER:
      printf("*");
      break;
    case C_TYPE_ARRAY:
      n = model->modifiers.modifier[i].param.array.number;
      if (n < 0)
	printf("[]");
      else
	printf("[%d]", model->modifiers.modifier[i].param.array.number);
      break;
    default:
      printf("?");
      break;
    }
  }
  size = model_get_size(model);
  if (size < 0)
    printf(" (size: %s, ", "UNDEF");
  else
    printf(" (size: %d, ", size);
  printf("align: %d, ", model_get_align(model));
  printf("flags:");
  if (model->flags & MODEL_FLAG_UNSIGNED  ) printf(" U");
  if (model->flags & MODEL_FLAG_FUNCVARARG) printf(" V");
  if (model->flags & MODEL_FLAG_TYPETYPE  ) printf(" T");
  if (model->flags & MODEL_FLAG_TYPESYMBOL) printf(" S");
  printf(")\n");

  indent++;

  if (model->type == C_TYPE_FUNCTION) {
    indent_print(indent);
    printf("RETVAL\n");
    model_print(model->function.retval, indent + 1);
    indent_print(indent);
    printf("ARGS\n");
    for (i = 0; i < model->function.args.number; i++) {
      model_print(model->function.args.arg[i], indent + 1);
    }
  }
}

/*****************************************************************
 * library
 */

int model_get_size(model_t model)
{
  int size, i, n;

  switch (model->type) {
  case C_TYPE_CHAR:       size =  1; break;
  case C_TYPE_SHORT:      size =  2; break;
  case C_TYPE_INT:        size =  4; break;
  case C_TYPE_LONG:       size = asm_info_word_size(); break;
  case C_TYPE_LONGLONG:   size =  8; break;
  case C_TYPE_FLOAT:      size =  4; break;
  case C_TYPE_DOUBLE:     size =  8; break;
  case C_TYPE_LONGDOUBLE: size = asm_info_word_size() + 8; break;
  case C_TYPE_VOID:       size =  1; break;
  case C_TYPE_FUNCTION:   size =  1; break;
  case C_TYPE_STRUCT:     size = model->custom._struct->members->size; break;
  case C_TYPE_UNION:      size = model->custom._union->members->size; break;
  case C_TYPE_ENUM:       size =  4; break;
  case C_TYPE_TYPEDEF:    size = model_get_size(model->custom._typedef->model); break;
  default:
    return -1;
  }

  for (i = 0; i < model->modifiers.number; i++) {
    switch (model->modifiers.modifier[i].type) {
    case C_TYPE_POINTER:
      size = asm_info_pointer_size();
      break;
    case C_TYPE_ARRAY:
      n = model->modifiers.modifier[i].param.array.number;
      if (n < 0)
	n = 0;
      if (size < 0)
	return -1;
      size *= n;
      break;
    default:
      break;
    }
  }

  return size;
}

int model_get_pointer_size(model_t model)
{
  int size;

  model = model_copy(model);
  if (model_del_modifier(model) < 0)
    return -1;
  size = model_get_size(model);
  model_destroy(model);

  return size;
}

int model_get_align(model_t model)
{
  if (model->modifiers.number == 0) {
    switch (model->type) {
    case C_TYPE_LONGLONG:   return asm_info_word_size();
    case C_TYPE_DOUBLE:     return asm_info_word_size();
    case C_TYPE_LONGDOUBLE: return asm_info_word_size();
    case C_TYPE_STRUCT:
      return model->custom._struct->members->align;
    case C_TYPE_UNION:
      return model->custom._union->members->align;
    case C_TYPE_TYPEDEF:
      return model_get_align(model->custom._typedef->model);
    default:
      break;
    }
  }

  if (model_is_array(model)) {
    model = model_copy(model);
    if (model_del_modifier(model) < 0)
      return -1;
    return model_get_align(model);
  }

  return model_get_size(model);
}

int model_get_level(model_t model)
{
  int r = 0;
  if (model == NULL)
    return -1;
  if (model_is_address(model))
    return 16;
  switch (model->type) {
  case C_TYPE_CHAR:       r =  0; break;
  case C_TYPE_SHORT:      r =  2; break;
  case C_TYPE_INT:        r =  4; break;
  case C_TYPE_LONG:       r =  6; break;
  case C_TYPE_LONGLONG:   r =  8; break;
  case C_TYPE_FLOAT:      r = 10; break;
  case C_TYPE_DOUBLE:     r = 12; break;
  case C_TYPE_LONGDOUBLE: r = 14; break;
  default: r = 0; break;
  }
  return r + ((model->flags & MODEL_FLAG_UNSIGNED) ? 1 : 0);
}

int model_pointer_to_array(model_t model, int number)
{
  if (model_is_pointer(model)) {
    model_del_modifier(model);
    model_add_modifier_array(model, number);
  }
  return 0;
}

int model_array_to_pointer(model_t model)
{
  if (model_is_array(model)) {
    model_del_modifier(model);
    model_add_modifier_pointer(model);
  }
  return 0;
}

long model_change_number(model_t new_model, model_t old_model, long number)
{
  unsigned long n;

  n = number;
  if (old_model->modifiers.number == 0) {
    if (old_model->flags & MODEL_FLAG_UNSIGNED) {
      switch (old_model->type) {
      case C_TYPE_CHAR:  n = (unsigned char)number;  break;
      case C_TYPE_SHORT: n = (unsigned short)number; break;
      case C_TYPE_INT:
	if (asm_info_word_size() > 4)
	  n = (unsigned int)number;
	break;
      default:
	break;
      }
    } else {
      switch (old_model->type) {
      case C_TYPE_CHAR:  n = (char)number;  break;
      case C_TYPE_SHORT: n = (short)number; break;
      case C_TYPE_INT:
	if (asm_info_word_size() > 4)
	  n = (int)number;
	break;
      default:
	break;
      }
    }
  }

#if 0
  if (new_model->modifiers.number == 0) {
    switch (new_model->type) {
    case C_TYPE_CHAR:  n &= 0xff;   break;
    case C_TYPE_SHORT: n &= 0xffff; break;
    case C_TYPE_INT:
      if (asm_info_word_size() > 4)
	n &= 0xffffffff;
      break;
    default:
      break;
    }
  }
#endif

  return n;
}

model_t model_deploy_typedef(model_t model)
{
  model_t m;
  model_modifier_t modifier;
  int i;

  while (model->type == C_TYPE_TYPEDEF) {
    m = model;
    model = model_copy(model->custom._typedef->model);
    for (i = 0; i < m->modifiers.number; i++) {
      modifier = &m->modifiers.modifier[i];
      switch (modifier->type) {
      case C_TYPE_POINTER:
	model_add_modifier_pointer(model);
	break;
      case C_TYPE_ARRAY:
        model_add_modifier_array(model, modifier->param.array.number);
	break;
      default:
	return NULL;
      }
    }
  }

  return model;
}
