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

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

#include "config.h"
#include "lib.h"
#include "objlist.h"
#include "type.h"
#include "syntax.h"
#include "area.h"
#include "union.h"

/*****************************************************************
 * union_t
 */

union_t union_destroy(union_t _union)
{
  if (_union != NULL) {
    if (_union->name != NULL)
      free(_union->name);
    if (_union->members != NULL)
      area_destroy(_union->members);

    objentry_set_free_func(&_union->entry, NULL);
    if (objentry_done(&_union->entry) != NULL) {
      /* fail to done */
    }
    memset(_union, 0, sizeof(*_union));
    free(_union);
  }

  return NULL;
}

static void union_free(void *p)
{
  union_destroy((union_t)p);
}

union_t union_create(char *name)
{
  union_t _union;
  char n[32];
  static int id = 0;

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

  if (objentry_init(&_union->entry, _union, 0, union_free) == NULL)
    goto err;

  if (name == NULL) {
    sprintf(n, "union.%d", id);
    name = n;
  }

  _union->name = strdup(name);
  if (_union->name == NULL)
    goto err;

  _union->members = area_create(C_TYPE_UNION);
  if (_union->members == NULL)
    goto err;

  _union->id = id++;

  return _union;

err:
  if (_union != NULL)
    union_destroy(_union);
  return NULL;
}

void union_print(union_t _union, int indent)
{
  indent_print(indent);
  printf("UNION: %s (id:0x%x)\n", _union->name, _union->id);
  indent++;
  area_print(_union->members, indent);
}

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

union_t union_list_search(objlist_t list, char *name)
{
  objentry_t entry;
  union_t _union;

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

  return NULL;
}

static int set_members(union_t _union, objlist_t symbols)
{
  objentry_t entry;
  symbol_t symbol;

  while ((entry = objlist_extract_head(symbols)) != NULL) {
    symbol = objentry_get_obj(entry);
    if (area_insert_symbol(_union->members, symbol) == NULL)
      return -1;
  }

  return _union->members->size;
}

static objlist_t read_members(objlist_t syntax_list,
			      objlist_t defines_list, objlist_t symbols,
			      area_t static_area, area_t stack_area)
{
  objlist_t list, words;
  objentry_t entry;
  syntax_t syntax;
  symbol_t symbol;

  list = objlist_create(NULL);

  while ((words = symbol_define_read(syntax_list, NULL, defines_list, symbols,
				     static_area, stack_area)) != NULL) {
    while ((entry = objlist_extract_head(words)) != NULL) {
      symbol = objentry_get_obj(entry);
      if (symbol_list_search(list, symbol->name) != NULL)
	ERREXIT(1);
      objlist_insert_tail(list, entry);
    }
    objlist_destroy(words);
  }

  if ((syntax = objlist_extract_syntax_head(syntax_list)) != NULL)
    ERREXITAT(syntax->line, 1);

  return list;
}

int union_normalize(syntax_t syntax, objlist_t syntax_list,
		    objlist_t defines_list)
{
  objentry_t entry;
  syntax_t s;
  union_t _union = NULL;
  objlist_t symbols;

  saveline(syntax->line);

  entry = objlist_get_head(syntax_list);
  if (objlist_is_end(syntax_list, entry))
    ERREXIT(1);
  s = objentry_get_syntax(entry);

  if (s->type == C_TYPE_WORD) {
    _union = defines_list_search_union_name(defines_list, s->obj.word);
    if (_union == NULL) {
      _union = union_create(s->obj.word);
      defines_list_insert_union(defines_list, _union);
    }

    objlist_extract(syntax_list, &s->entry);
    syntax_destroy(s);

    entry = objlist_get_head(syntax_list);
    if (objlist_is_end(syntax_list, entry))
      s = NULL;
    else
      s = objentry_get_syntax(entry);
  }

  if ((s != NULL) && (s->type == C_TYPE_MBRACKET_IN)) {
    if (_union == NULL) {
      _union = union_create(NULL);
      defines_list_insert_union(defines_list, _union);
    }
    symbols = read_members(s->obj.words, defines_list, NULL, NULL, NULL);
    s->obj.words = NULL;

    objlist_extract(syntax_list, &s->entry);
    syntax_destroy(s);

    if (set_members(_union, symbols) < 0)
      ERREXIT(1);
  }

  if (_union)
    syntax->obj.custom._union = _union;

  return 0;
}
