#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 "symbol.h"
#include "area.h"
#include "asm_code.h"

/*****************************************************************
 * area_t
 */

area_t area_destroy(area_t area)
{
  if (area != NULL) {
    if (area->symbols != NULL) {
      while (objlist_extract_head(area->symbols) != NULL)
	;
    }

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

  return NULL;
}

area_t area_create(c_type_t type)
{
  area_t area;

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

  area->type = type;
  area->align = 1;
  area->offset = 0;
  area->size = 0;
  area->size_correct = -1;
  area->size_init = 0;
  area->symbols = objlist_create(NULL);

  return area;

err:
  if (area != NULL)
    area_destroy(area);
  return NULL;
}

void area_print_simple(area_t area, int indent)
{
  objentry_t entry;
  symbol_t symbol;

  indent_print(indent);
  printf("AREA: (%s, align: %d, size: %d)\n",
	 type_get_word(area->type), area->align, area->size);

  indent++;
  for (entry = objlist_get_head(area->symbols);
       !objlist_is_end(area->symbols, entry);
       entry = objentry_get_next(entry)) {
    symbol = objentry_get_obj(entry);
    symbol_print_simple(symbol, indent);
  }
}

void area_print(area_t area, int indent)
{
  objentry_t entry;
  symbol_t symbol;

  indent_print(indent);
  printf("AREA: (%s, align: %d, offset: %d, size: %d, size_correct: %d, size_init: %d)\n",
	 type_get_word(area->type), area->align, area->offset, area->size,
	 area->size_correct, area->size_init);

  indent++;
  for (entry = objlist_get_head(area->symbols);
       !objlist_is_end(area->symbols, entry);
       entry = objentry_get_next(entry)) {
    symbol = objentry_get_obj(entry);
    symbol_print(symbol, indent);
  }
}

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

symbol_t area_search_symbol(area_t area, char *name)
{
  objentry_t entry;
  symbol_t symbol;

  for (entry = objlist_get_head(area->symbols);
       !objlist_is_end(area->symbols, entry);
       entry = objentry_get_next(entry)) {
    symbol = objentry_get_obj(entry);
    if ((symbol->name != NULL) && !strcmp(symbol->name, name))
      return symbol;
  }

  return NULL;
}

symbol_t area_insert_symbol(area_t area, symbol_t symbol)
{
  int size, align_size, word_size;
  model_t model;

  if (area == NULL)
    return NULL;

  if (symbol->model == NULL)
    return NULL;

  objlist_insert_obj_tail(area->symbols, symbol, symbol_free);

  size = model_get_size(symbol->model);
  word_size = asm_info_word_size();

  if (symbol->flags & SYMBOL_FLAG_BITFIELD) {
    if (symbol->param.variable.bitfield.bits > area->bitfield.bits) {
      area->bitfield.bits = 0;
    } else {
      if ((symbol->name == NULL) &&
	  (symbol->param.variable.bitfield.bits == 0))
	symbol->param.variable.bitfield.bits = area->bitfield.bits;
      symbol->location.type = area->type;
      symbol->location.base.area = area;
      symbol->location.offset = area->bitfield.offset;
      goto ret;
    }
  }

  if (area->type == C_TYPE_UNION) {
    if (size < 0)
      return NULL;
    symbol->location.type = area->type;
    symbol->location.base.area = area;
    symbol->location.offset = area->offset;

    model = symbol->model;
    if (symbol->type == C_TYPE_LABEL) {
      model = model_copy(model);
      model_del_modifier(model);
    }
    align_size = model_get_align(model);
    if (align_size < 0)
      return NULL;
    if (align_size > area->align)
      area->align = align_size;

    align_size = ((size + area->align - 1) / area->align) * area->align;
    area->size = ((area->size + area->align - 1) / area->align) * area->align;
    if (area->size < align_size)
      area->size = align_size;
  } else if (symbol->flags & SYMBOL_FLAG_FUNCBLOCK) {
    symbol->location.type = area->type;
    symbol->location.base.area = area;
    symbol->location.offset = 0;
  } else {
    if (size < 0)
      return NULL;

    model = symbol->model;
    if (symbol->type == C_TYPE_LABEL) {
      model = model_copy(model);
      model_del_modifier(model);
    }
    align_size = model_get_align(model);
    if (align_size < 0)
      return NULL;
    if (align_size > area->align)
      area->align = align_size;

    area->offset = ((area->offset + align_size - 1) / align_size) * align_size;
    symbol->location.type = area->type;
    symbol->location.base.area = area;
    symbol->location.offset = area->offset;

    area->offset += size;
    area->size = ((area->offset + area->align - 1) / area->align) * area->align;
  }

  if (!(symbol->flags & SYMBOL_FLAG_TEMPORARY) && (symbol->value != NULL)) {
    if (area->size_init < area->size)
      area->size_init = ((area->size + word_size - 1) / word_size) * word_size;
  }

  if (symbol->flags & SYMBOL_FLAG_BITFIELD) {
    area->bitfield.bits = size * 8;
    area->bitfield.shift = 0;
    area->bitfield.offset = symbol->location.offset;
  }

ret:
  if (symbol->flags & SYMBOL_FLAG_BITFIELD) {
    if (symbol->param.variable.bitfield.bits > area->bitfield.bits)
      ERREXIT(1);
    symbol->param.variable.bitfield.shift = area->bitfield.shift;
    if (area->type == C_TYPE_UNION) {
      /* none */
    } else {
      area->bitfield.bits  -= symbol->param.variable.bitfield.bits;
      area->bitfield.shift += symbol->param.variable.bitfield.bits;
    }
  }

  symbol->flags |= SYMBOL_FLAG_LOCATED;

  return symbol;
}
