#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 "value.h"
#include "enum.h"

/*****************************************************************
 * enum_t
 */

enum_t enum_destroy(enum_t _enum)
{
  if (_enum != NULL) {
    if (_enum->name != NULL)
      free(_enum->name);
    if (_enum->words != NULL)
      objlist_destroy(_enum->words);

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

  return NULL;
}

static void enum_free(void *p)
{
  enum_destroy((enum_t)p);
}

enum_t enum_create(char *name, objlist_t words)
{
  enum_t _enum;
  char n[32];
  static int id = 0;

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

  if (objentry_init(&_enum->entry, _enum, 0, enum_free) == NULL)
    goto err;

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

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

  _enum->id = id++;
  _enum->words = words;

  return _enum;

err:
  if (_enum != NULL)
    enum_destroy(_enum);
  return NULL;
}

static void enum_print_words(objlist_t words, int indent)
{
  objentry_t entry;
  symbol_t word;

  if (words == NULL)
    return;

  for (entry = objlist_get_head(words);
       !objlist_is_end(words, entry);
       entry = objentry_get_next(entry)) {
    word = objentry_get_obj(entry);
    symbol_print(word, indent);
  }
}

void enum_print(enum_t _enum, int indent)
{
  indent_print(indent);
  printf("ENUM: %s (id:0x%x)\n", _enum->name, _enum->id);
  indent++;
  enum_print_words(_enum->words, indent);
}

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

enum_t enum_list_search(objlist_t list, char *name)
{
  objentry_t entry;
  enum_t _enum;

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

  return NULL;
}

symbol_t enum_search_word(enum_t _enum, char *name)
{
  objentry_t entry;
  symbol_t word;

  for (entry = objlist_get_head(_enum->words);
       !objlist_is_end(_enum->words, entry);
       entry = objentry_get_next(entry)) {
    word = objentry_get_obj(entry);
    if (!strcmp(word->name, name))
      return word;
  }

  return NULL;
}

symbol_t enum_list_search_symbol(objlist_t list, char *name)
{
  objentry_t entry;
  enum_t _enum;
  symbol_t symbol;

  for (entry = objlist_get_head(list);
       !objlist_is_end(list, entry);
       entry = objentry_get_next(entry)) {
    _enum = objentry_get_obj(entry);
    symbol = enum_search_word(_enum, name);
    if (symbol != NULL)
      return symbol;
  }

  return NULL;
}

symbol_t enum_list_search_symbol_defines(objlist_t defines_list, char *name)
{
  objentry_t entry;
  defines_t defines;
  symbol_t symbol;

  for (entry = objlist_get_head(defines_list);
       !objlist_is_end(defines_list, entry);
       entry = objentry_get_next(entry)) {
    defines = objentry_get_obj(entry);
    symbol = enum_list_search_symbol(&defines->enum_list, name);
    if (symbol != NULL)
      return symbol;
  }

  return NULL;
}

static objlist_t read_words(enum_t _enum, objlist_t syntax_list, objlist_t defines_list)
{
  syntax_t syntax;
  symbol_t symbol;
  symbol_t word = NULL;
  int number = 0;

  _enum->words = objlist_create(NULL);

  while ((syntax = objlist_extract_syntax_head(syntax_list)) != NULL) {
    saveline(syntax->line);
    switch (syntax->type) {
    case C_TYPE_WORD:
      word = symbol_create(C_TYPE_VARIABLE, loadline(), C_TYPE_STATIC,
			   syntax->obj.word, NULL, 0, C_TYPE_NUMBER);
      word->value->obj.number = number++;
      word->flags |= SYMBOL_FLAG_READONLY;
      word->model = model_create(C_TYPE_INT);
      if (symbol_list_search(_enum->words, word->name) != NULL)
	ERREXIT(1);
      objlist_insert_tail(_enum->words, &word->entry);
      syntax_destroy(syntax);
      break;
    case C_TYPE_COMMA:
      word = NULL;
      syntax_destroy(syntax);
      break;
    case C_TYPE_EQ:
      if (word == NULL)
	ERREXIT(1);
      symbol = value_read(syntax_list, defines_list, NULL, NULL, NULL,
			  NULL, C_TYPE_COMMA, NULL, 1, 0);
      if ((symbol == NULL) || (symbol->value == NULL) ||
	  (symbol->value->type != C_TYPE_NUMBER))
	ERREXIT(1);
      number = symbol->value->obj.number;
      word->value->obj.number = number++;
      syntax_destroy(syntax);
      word = NULL;
      break;
    default:
      ERREXIT(1);
    }
  }

  objlist_destroy(syntax_list);

  return _enum->words;
}

int enum_normalize(syntax_t syntax, objlist_t syntax_list,
		   objlist_t defines_list)
{
  objentry_t entry;
  syntax_t s;
  enum_t _enum = NULL;

  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) {
    _enum = defines_list_search_enum_name(defines_list, s->obj.word);
    if (_enum == NULL) {
      _enum = enum_create(s->obj.word, NULL);
      defines_list_insert_enum(defines_list, _enum);
    }

    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 (_enum == NULL) {
      _enum = enum_create(NULL, NULL);
      defines_list_insert_enum(defines_list, _enum);
    }
    _enum->words = read_words(_enum, s->obj.words, defines_list);
    s->obj.words = NULL;

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

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

  return 0;
}
