#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <ctype.h>
#include <Eina.h>
#include "eo_parser.h"
#include "eolian_database.h"
#include "eolian_priv.h"

void
database_object_add(Eolian_Unit *unit, const Eolian_Object *obj)
{
   /* object storage */
   eina_hash_add(unit->objects, obj->name, obj);
   eina_hash_add(unit->state->staging.unit.objects, obj->name, obj);
   eina_hash_set(unit->state->staging.objects_f, obj->file, eina_list_append
                 ((Eina_List *)eina_hash_find(unit->state->staging.objects_f, obj->file), obj));
}

EOLIAN_API Eolian_Object_Type
eolian_object_type_get(const Eolian_Object *obj)
{
   if (!obj) return EOLIAN_OBJECT_UNKNOWN;
   return obj->type;
}

EOLIAN_API const Eolian_Unit *
eolian_object_unit_get(const Eolian_Object *obj)
{
   if (!obj) return NULL;
   return obj->unit;
}

EOLIAN_API const char *
eolian_object_file_get(const Eolian_Object *obj)
{
   if (!obj) return NULL;
   return obj->file;
}

EOLIAN_API int
eolian_object_line_get(const Eolian_Object *obj)
{
   if (!obj) return 0;
   return obj->line;
}

EOLIAN_API int
eolian_object_column_get(const Eolian_Object *obj)
{
   if (!obj) return 0;
   return obj->column;
}

EOLIAN_API const char *
eolian_object_name_get(const Eolian_Object *obj)
{
   if (!obj) return NULL;
   return obj->name;
}

EOLIAN_API const char *
eolian_object_c_name_get(const Eolian_Object *obj)
{
   if (!obj) return NULL;
   return obj->c_name;
}

typedef struct _Eolian_Namespace_List
{
   Eina_Iterator itr;
   char *curp;
} Eolian_Namespace_List;

static Eina_Bool
_nmsp_iterator_next(Eolian_Namespace_List *it, void **data)
{
   if (!it || !it->curp)
     return EINA_FALSE;

   char *ndot = strchr(it->curp, '.');
   if (!ndot)
     return EINA_FALSE;

   *ndot = '\0';
   if (data) *data = it->curp;
   it->curp = ndot + 1;
   return EINA_TRUE;
}

static void *
_nmsp_container_get(Eina_Iterator *it EINA_UNUSED)
{
   return NULL;
}

EOLIAN_API const char *
eolian_object_short_name_get(const Eolian_Object *obj)
{
   if (!obj || !obj->name) return NULL;
   const char *ldot = strrchr(obj->name, '.');
   if (ldot)
     return ldot + 1;
   return obj->name;
}

EOLIAN_API Eina_Iterator *
eolian_object_namespaces_get(const Eolian_Object *obj)
{
   if (!obj || !obj->name || !strchr(obj->name, '.')) return NULL;

   size_t nstrl = strlen(obj->name) + 1;
   size_t anstrl = nstrl - 1 - (nstrl - 1) % sizeof(void *) + sizeof(void *);

   Eolian_Namespace_List *it = malloc(sizeof(Eolian_Namespace_List) + anstrl);
   memset(&it->itr, 0, sizeof(Eina_Iterator));
   it->curp = (char *)(it + 1);
   memcpy(it->curp, obj->name, nstrl);

   EINA_MAGIC_SET(&it->itr, EINA_MAGIC_ITERATOR);
   it->itr.version = EINA_ITERATOR_VERSION;
   it->itr.next = FUNC_ITERATOR_NEXT(_nmsp_iterator_next);
   it->itr.get_container = _nmsp_container_get;
   it->itr.free = FUNC_ITERATOR_FREE(free);
   return &it->itr;
}

EOLIAN_API Eina_Bool
eolian_object_is_beta(const Eolian_Object *obj)
{
   if (!obj) return EINA_FALSE;
   return obj->is_beta;
}

void database_doc_del(Eolian_Documentation *doc)
{
   if (!doc) return;
   eina_stringshare_del(doc->summary);
   eina_stringshare_del(doc->description);
   eina_stringshare_del(doc->since);
   eina_list_free(doc->ref_dbg);
   free(doc);
}

EOLIAN_API const char *
eolian_documentation_summary_get(const Eolian_Documentation *doc)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(doc, NULL);
   return doc->summary;
}

EOLIAN_API const char *
eolian_documentation_description_get(const Eolian_Documentation *doc)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(doc, NULL);
   return doc->description;
}

EOLIAN_API const char *
eolian_documentation_since_get(const Eolian_Documentation *doc)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(doc, NULL);
   return doc->since;
}

EOLIAN_API Eina_List *
eolian_documentation_string_split(const char *doc)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(doc, NULL);
   if (!doc[0])
     return NULL;
   const char *sep = strstr(doc, "\n\n");
   Eina_List *ret = NULL;
   for (;;)
     {
        Eina_Strbuf *buf = eina_strbuf_new();
        if (sep)
          eina_strbuf_append_length(buf, doc, sep - doc);
        else
          eina_strbuf_append(buf, doc);
        eina_strbuf_trim(buf);
        if (eina_strbuf_length_get(buf))
          ret = eina_list_append(ret, eina_strbuf_string_steal(buf));
        eina_strbuf_free(buf);
        if (!sep)
          break;
        doc = sep + 2;
        sep = strstr(doc, "\n\n");
     }
   return ret;
}

static Eina_Bool
_skip_ref_word(const char **doc)
{
   if (((*doc)[0] != '_') && !isalpha((*doc)[0]))
     return EINA_FALSE;

   while (((*doc)[0] == '_') || isalnum((*doc)[0]))
     ++*doc;

   return EINA_TRUE;
}

/* this make sure the format is correct at least, it cannot verify the
 * correctness of the reference itself (but Eolian will do it in its
 * lexer, so there is nothing to worry about; all references are guaranteed
 * to be right
 */
static Eolian_Doc_Token_Type
_get_ref_token(const char *doc, const char **doc_end)
{
   /* not a ref at all, for convenience */
   if (doc[0] != '@')
     return EOLIAN_DOC_TOKEN_UNKNOWN;

   ++doc;

   Eina_Bool is_event = (doc[0] == '[');
   if (is_event)
     ++doc;

   if (_skip_ref_word(&doc))
     {
        while (doc[0] == '.')
          {
             ++doc;
             if (!_skip_ref_word(&doc))
               {
                  --doc;
                  break;
               }
          }
        if (is_event) while (doc[0] == ',')
          {
             ++doc;
             if (!_skip_ref_word(&doc))
               {
                  --doc;
                  break;
               }
          }
     }
   else
     return EOLIAN_DOC_TOKEN_UNKNOWN;

   if (is_event)
     {
        if (doc[0] != ']')
          return EOLIAN_DOC_TOKEN_UNKNOWN;
        ++doc;
     }

   if (doc_end)
     *doc_end = doc;

   /* got a reference */
   return EOLIAN_DOC_TOKEN_REF;
}

EOLIAN_API const char *
eolian_documentation_tokenize(const char *doc, Eolian_Doc_Token *ret)
{
   /* token is used for statekeeping, so force it */
   EINA_SAFETY_ON_NULL_RETURN_VAL(ret, NULL);

   /* we've reached the end or invalid input */
   if (!doc || !doc[0])
     {
        ret->text = ret->text_end = NULL;
        ret->type = EOLIAN_DOC_TOKEN_UNKNOWN;
        return NULL;
     }

   Eina_Bool cont = (ret->type != EOLIAN_DOC_TOKEN_UNKNOWN);

   /* we can only check notes etc at beginning of parsing */
   if (cont)
     goto mloop;

#define CMP_MARK_NOTE(doc, note) !strncmp(doc, note ": ", sizeof(note) + 1)

   /* different types of notes */
   if (CMP_MARK_NOTE(doc, "Note"))
     {
        ret->text = doc;
        ret->text_end = doc + sizeof("Note:");
        ret->type = EOLIAN_DOC_TOKEN_MARK_NOTE;
        return ret->text_end;
     }
   else if (CMP_MARK_NOTE(doc, "Warning"))
     {
        ret->text = doc;
        ret->text_end = doc + sizeof("Warning:");
        ret->type = EOLIAN_DOC_TOKEN_MARK_WARNING;
        return ret->text_end;
     }
   else if (CMP_MARK_NOTE(doc, "Remark"))
     {
        ret->text = doc;
        ret->text_end = doc + sizeof("Remark:");
        ret->type = EOLIAN_DOC_TOKEN_MARK_REMARK;
        return ret->text_end;
     }
   else if (CMP_MARK_NOTE(doc, "TODO"))
     {
        ret->text = doc;
        ret->text_end = doc + sizeof("TODO:");
        ret->type = EOLIAN_DOC_TOKEN_MARK_TODO;
        return ret->text_end;
     }

#undef CMP_MARK_NOTE

mloop:
   /* monospace markup ($foo) */
   if ((doc[0] == '$') && ((doc[1] == '_') || isalpha(doc[1])))
     {
        ret->text = ++doc;
        ret->text_end = ret->text;
        while ((ret->text_end[0] == '_') || isalnum(ret->text_end[0]))
          ++ret->text_end;
        ret->type = EOLIAN_DOC_TOKEN_MARKUP_MONOSPACE;
        return ret->text_end;
     }

   /* complex monospace markup ($[...]) */
   if ((doc[0] == '$') && (doc[1] == '['))
     {
        doc += 2;
        ret->text = doc;
        ret->text_end = ret->text;
        while ((ret->text_end[0] != '\0') && (ret->text_end[0] != ']') &&
               (ret->text_end[0] != '\n'))
          {
             /* escape: skip backslash */
             if ((ret->text_end[0] == '\\') && (ret->text_end[1] != '\0') &&
                 (ret->text_end[1] != '\n'))
               ++ret->text_end;
             ++ret->text_end;
          }
        ret->type = EOLIAN_DOC_TOKEN_MARKUP_MONOSPACE;
        /* return past the ending bracket as that's markup syntax */
        if (ret->text_end[0] == ']')
          return ret->text_end + 1;
        return ret->text_end;
     }

   /* references */
   Eolian_Doc_Token_Type rtp = _get_ref_token(doc, &ret->text_end);
   if (rtp != EOLIAN_DOC_TOKEN_UNKNOWN)
     {
        ret->text = doc + 1;
        ret->type = rtp;
        return ret->text_end;
     }

   const char *schr = doc, *pschr = NULL;
   /* keep finding potential tokens until a suitable one is found
    * terminate text token there (it also means next token can directly
    * be tested for event/monospace)
    */
   while ((schr = strpbrk(schr, "@$")))
     {
        /* escape sequences */
        if ((schr != doc) && (schr[-1] == '\\'))
          {
             schr += 1;
             continue;
          }
        /* monospace markup */
        if ((schr[0] == '$') && (
            (schr[1] == '_') || (schr[1] == '[') || isalpha(schr[1])))
          {
             pschr = schr;
             break;
          }
        /* references */
        if (_get_ref_token(schr, NULL) != EOLIAN_DOC_TOKEN_UNKNOWN)
          {
             pschr = schr;
             break;
          }
        /* nothing, keep matching text from next char on */
        schr += 1;
     }

   /* figure out where we actually end */
   ret->text = doc;
   ret->text_end = pschr ? pschr : (doc + strlen(doc));
   ret->type = EOLIAN_DOC_TOKEN_TEXT;
   return ret->text_end;
}

EOLIAN_API void
eolian_doc_token_init(Eolian_Doc_Token *tok)
{
   if (!tok)
     return;
   tok->type = EOLIAN_DOC_TOKEN_UNKNOWN;
   tok->text = tok->text_end = NULL;
}

EOLIAN_API Eolian_Doc_Token_Type
eolian_doc_token_type_get(const Eolian_Doc_Token *tok)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(tok, EOLIAN_DOC_TOKEN_UNKNOWN);
   return tok->type;
}

EOLIAN_API char *
eolian_doc_token_text_get(const Eolian_Doc_Token *tok)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(tok, NULL);
   if (tok->type == EOLIAN_DOC_TOKEN_UNKNOWN)
     return NULL;
   Eina_Strbuf *buf = eina_strbuf_new();
   for (const char *p = tok->text; p != tok->text_end; ++p)
     {
        if (*p == '\\') ++p;
        if (p != tok->text_end)
          eina_strbuf_append_char(buf, *p);
     }
   char *ptr = eina_strbuf_string_steal(buf);
   eina_strbuf_free(buf);
   return ptr;
}

static Eolian_Object_Type
_resolve_event(char *name, const Eolian_Unit *unit1, const Eolian_Unit *unit2,
               const Eolian_Object **data1, const Eolian_Object **data2)
{
   /* never trust the user */
   if (name[0] == ',')
     return EOLIAN_OBJECT_UNKNOWN;

   char *evname = strrchr(name, '.');
   if (!evname)
     return EOLIAN_OBJECT_UNKNOWN;

   *evname++ = '\0';
   const Eolian_Class *cl = eolian_unit_class_by_name_get(unit1, name);
   if (!cl && unit2)
     cl = eolian_unit_class_by_name_get(unit2, name);
   if (!cl)
     return EOLIAN_OBJECT_UNKNOWN;

   const Eolian_Event *ev = eolian_class_event_by_name_get(cl, evname);
   if (!ev)
     return EOLIAN_OBJECT_UNKNOWN;

   if (data1) *data1 = &cl->base;
   if (data2) *data2 = &ev->base;
   return EOLIAN_OBJECT_EVENT;
}

Eolian_Object_Type
database_doc_token_ref_resolve(const Eolian_Doc_Token *tok,
                               const Eolian_Unit *unit1, const Eolian_Unit *unit2,
                               const Eolian_Object **data1, const Eolian_Object **data2)
{
   if (tok->type != EOLIAN_DOC_TOKEN_REF)
     return EOLIAN_OBJECT_UNKNOWN;

   size_t nlen = tok->text_end - tok->text;

   /* events are handled separately */
   if (tok->text[0] == '[')
     {
        /* strip brackets */
        size_t elen = nlen - 2;
        char *ename = alloca(elen + 1);
        memcpy(ename, tok->text + 1, elen);
        ename[elen] = '\0';
        return _resolve_event(ename, unit1, unit2, data1, data2);
     }

   char *name = alloca(nlen + 1);
   memcpy(name, tok->text, nlen);
   name[nlen] = '\0';

   const Eolian_Object *decl = eolian_unit_object_by_name_get(unit1, name);
   if (!decl && unit2)
     decl = eolian_unit_object_by_name_get(unit2, name);
   if (decl)
     {
       if (data1) *data1 = decl;
       Eolian_Object_Type tp = eolian_object_type_get(decl);
       switch (tp)
         {
          case EOLIAN_OBJECT_CLASS:
          case EOLIAN_OBJECT_TYPEDECL:
          case EOLIAN_OBJECT_CONSTANT:
          case EOLIAN_OBJECT_ERROR:
            /* we only allow certain types to be referenced */
            return tp;
          default:
            return EOLIAN_OBJECT_UNKNOWN;
         }
     }

   /* from here it can only be a function, a struct field or an enum field */

   char *suffix = strrchr(name, '.');
   /* no suffix, therefore invalid */
   if (!suffix)
     return EOLIAN_OBJECT_UNKNOWN;

   /* name will terminate before suffix, suffix will be standalone */
   *suffix++ = '\0';

   /* try a struct field */
   const Eolian_Typedecl *tpd = eolian_unit_struct_by_name_get(unit1, name);
   if (!tpd && unit2)
     tpd = eolian_unit_struct_by_name_get(unit2, name);
   if (tpd)
     {
        const Eolian_Struct_Type_Field *fld = eolian_typedecl_struct_field_get(tpd, suffix);
        /* field itself is invalid */
        if (!fld)
          return EOLIAN_OBJECT_UNKNOWN;
        if (data1) *data1 = &tpd->base;
        if (data2) *data2 = &fld->base;
        return EOLIAN_OBJECT_STRUCT_FIELD;
     }

   /* try an enum field */
   tpd = eolian_unit_enum_by_name_get(unit1, name);
   if (!tpd && unit2)
     tpd = eolian_unit_enum_by_name_get(unit2, name);
   if (tpd)
     {
        const Eolian_Enum_Type_Field *fld = eolian_typedecl_enum_field_get(tpd, suffix);
        /* field itself is invalid */
        if (!fld)
          return EOLIAN_OBJECT_UNKNOWN;
        if (data1) *data1 = &tpd->base;
        if (data2) *data2 = &fld->base;
        return EOLIAN_OBJECT_ENUM_FIELD;
     }

   /* now it can only be a function or invalid */

   Eolian_Function_Type ftype = EOLIAN_UNRESOLVED;
   if (!strcmp(suffix, "get"))
     ftype = EOLIAN_PROP_GET;
   else if (!strcmp(suffix, "set"))
     ftype = EOLIAN_PROP_SET;

   if (ftype != EOLIAN_UNRESOLVED)
     {
        suffix = strrchr(name, '.');
        /* wrong suffix, therefore invalid */
        if (!suffix)
          return EOLIAN_OBJECT_UNKNOWN;
        /* re-terminate */
        *suffix++ = '\0';
     }

   const Eolian_Class *cl = eolian_unit_class_by_name_get(unit1, name);
   if (!cl && unit2)
     cl = eolian_unit_class_by_name_get(unit2, name);
   if (!cl)
     return EOLIAN_OBJECT_UNKNOWN;

   const Eolian_Function *fid = eolian_class_function_by_name_get(cl, suffix, ftype);
   if (!fid)
     return EOLIAN_OBJECT_UNKNOWN;

   /* got a func */
   if (data1) *data1 = &cl->base;
   if (data2) *data2 = &fid->base;
   return EOLIAN_OBJECT_FUNCTION;
}

EOLIAN_API Eolian_Object_Type
eolian_doc_token_ref_resolve(const Eolian_Doc_Token *tok, const Eolian_State *state,
                             const Eolian_Object **data, const Eolian_Object **data2)
{
   return database_doc_token_ref_resolve(tok, (const Eolian_Unit *)state, NULL, data, data2);
}

void
database_unit_init(Eolian_State *state, Eolian_Unit *unit, const char *file)
{
   unit->file  = eina_stringshare_ref(file);
   unit->state = state;

   unit->children   = eina_hash_stringshared_new(NULL);
   unit->classes    = eina_hash_stringshared_new(EINA_FREE_CB(database_class_del));
   unit->constants  = eina_hash_stringshared_new(EINA_FREE_CB(database_constant_del));
   unit->errors     = eina_hash_stringshared_new(EINA_FREE_CB(database_error_del));
   unit->aliases    = eina_hash_stringshared_new(EINA_FREE_CB(database_typedecl_del));
   unit->structs    = eina_hash_stringshared_new(EINA_FREE_CB(database_typedecl_del));
   unit->enums      = eina_hash_stringshared_new(EINA_FREE_CB(database_typedecl_del));
   unit->objects    = eina_hash_stringshared_new(NULL);

   /* baseline version; support for higher featurelevel must be specified explicitly */
   unit->version    = 1;
}

static void
_unit_contents_del(Eolian_Unit *unit)
{
   eina_stringshare_del(unit->file);
   eina_hash_free(unit->children);
   eina_hash_free(unit->classes);
   eina_hash_free(unit->constants);
   eina_hash_free(unit->errors);
   eina_hash_free(unit->aliases);
   eina_hash_free(unit->structs);
   eina_hash_free(unit->enums);
   eina_hash_free(unit->objects);
}

void
database_unit_del(Eolian_Unit *unit)
{
   if (!unit)
     return;

   _unit_contents_del(unit);
   free(unit);
}

static Eina_Bool
_hashlist_free_cb(const Eina_Hash *hash EINA_UNUSED,
                  const void *key EINA_UNUSED,
                  void *data, void *fdata EINA_UNUSED)
{
   eina_list_free((Eina_List *)data);
   return EINA_TRUE;
}

static void
_hashlist_free(Eina_Hash *h)
{
   eina_hash_foreach(h, _hashlist_free_cb, NULL);
   eina_hash_free(h);
}

static void
_hashlist_free_buckets(Eina_Hash *h)
{
   eina_hash_foreach(h, _hashlist_free_cb, NULL);
   eina_hash_free_buckets(h);
}

static void
_state_area_init(Eolian_State *state, Eolian_State_Area *a)
{
   database_unit_init(state, &a->unit, NULL);

   a->units = eina_hash_stringshared_new(NULL);

   a->classes_f   = eina_hash_stringshared_new(NULL);
   a->aliases_f   = eina_hash_stringshared_new(NULL);
   a->structs_f   = eina_hash_stringshared_new(NULL);
   a->enums_f     = eina_hash_stringshared_new(NULL);
   a->constants_f = eina_hash_stringshared_new(NULL);
   a->errors_f    = eina_hash_stringshared_new(NULL);
   a->objects_f   = eina_hash_stringshared_new(NULL);
}

static Eina_Bool
_ulist_free_cb(const Eina_Hash *hash EINA_UNUSED,
               const void *key EINA_UNUSED,
               void *data, void *fdata EINA_UNUSED)
{
   database_unit_del((Eolian_Unit *)data);
   return EINA_TRUE;
}

static void
_state_area_contents_del(Eolian_State_Area *a)
{
   _unit_contents_del(&a->unit);

   eina_hash_foreach(a->units, _ulist_free_cb, NULL);
   eina_hash_free(a->units);

   eina_hash_free(a->classes_f);
   _hashlist_free(a->aliases_f);
   _hashlist_free(a->structs_f);
   _hashlist_free(a->enums_f);
   _hashlist_free(a->constants_f);
   _hashlist_free(a->errors_f);
   _hashlist_free(a->objects_f);
}

static void
_default_panic_cb(const Eolian_State *state EINA_UNUSED, const char *msg)
{
   _eolian_log(msg);
}

static void
_default_error_cb(const Eolian_Object *obj, const char *msg, void *data EINA_UNUSED)
{
   if (obj)
     _eolian_log_line(obj->file, obj->line, obj->column, msg);
   else
     _eolian_log(msg);
}

EOLIAN_API Eolian_State *
eolian_state_new(void)
{
   Eolian_State *state = calloc(1, sizeof(Eolian_State));
   if (!state)
     return NULL;

   state->panic = _default_panic_cb;

   if (setjmp(state->jmp_env))
     {
        state->panic(state, state->panic_msg);
        eina_stringshare_del(state->panic_msg);
        exit(EXIT_FAILURE);
     }

   state->error = _default_error_cb;

   _state_area_init(state, &state->main);
   _state_area_init(state, &state->staging);

   state->filenames_eo  = eina_hash_string_small_new(free);
   state->filenames_eot = eina_hash_string_small_new(free);

   state->defer = eina_hash_string_small_new(NULL);

   return state;
}

EOLIAN_API void
eolian_state_free(Eolian_State *state)
{
   if (!state)
     return;

   _state_area_contents_del(&state->main);
   _state_area_contents_del(&state->staging);

   eina_hash_free(state->filenames_eo);
   eina_hash_free(state->filenames_eot);

   eina_hash_free(state->defer);

   free(state);
}

EOLIAN_API Eolian_Panic_Cb
eolian_state_panic_cb_set(Eolian_State *state, Eolian_Panic_Cb cb)
{
   Eolian_Panic_Cb old_cb = state->panic;
   state->panic = cb ? cb : _default_panic_cb;
   return old_cb;
}

EOLIAN_API Eolian_Error_Cb
eolian_state_error_cb_set(Eolian_State *state, Eolian_Error_Cb cb)
{
   Eolian_Error_Cb old_cb = state->error;
   state->error = cb ? cb : _default_error_cb;
   return old_cb;
}

EOLIAN_API void *
eolian_state_error_data_set(Eolian_State *state, void *data)
{
   void *old_data = state->error_data;
   state->error_data = data;
   return old_data;
}

#define EO_SUFFIX ".eo"
#define EOT_SUFFIX ".eot"

static char *
join_path(const char *path, const char *file)
{
   Eina_Strbuf *buf = eina_strbuf_new();
   char *ret;

   eina_strbuf_append(buf, path);
   eina_strbuf_append_char(buf, '/');
   eina_strbuf_append(buf, file);

   ret = eina_file_path_sanitize(eina_strbuf_string_get(buf));
   eina_strbuf_free(buf);
   return ret;
}

typedef struct _Scan_State
{
   Eolian_State *eos;
   Eina_Bool succ;
} Scan_State;

static void
_scan_cb(const char *name, const char *path, void *data)
{
   Scan_State *sst = data;
   Eina_Bool is_eo = eina_str_has_suffix(name, EO_SUFFIX);
   if (!is_eo && !eina_str_has_suffix(name, EOT_SUFFIX)) return;
   Eina_Hash *fh = is_eo ? sst->eos->filenames_eo : sst->eos->filenames_eot;
   const char *origpath = eina_hash_find(fh, name);
   char *newpath = join_path(path, name);
   if (origpath)
     {
        if (strcmp(origpath, newpath))
          {
            eolian_state_log(sst->eos,
                             "conflicting paths for '%s': '%s' -> '%s'",
                             name, origpath, newpath);
            sst->succ = EINA_FALSE;
          }
        free(newpath);
     }
   else
     eina_hash_add(fh, name, newpath);
}

EOLIAN_API Eina_Bool
eolian_state_directory_add(Eolian_State *state, const char *dir)
{
   if (!dir || !state) return EINA_FALSE;
   Scan_State sst = { state, EINA_TRUE };
   return eina_file_dir_list(dir, EINA_TRUE, _scan_cb, &sst) && sst.succ;
}

EOLIAN_API Eina_Bool
eolian_state_system_directory_add(Eolian_State *state)
{
   Eina_Bool ret;
   Eina_Strbuf *buf = eina_strbuf_new();
   eina_strbuf_append(buf, eina_prefix_data_get(_eolian_prefix));
   eina_strbuf_append(buf, "/include");
   ret = eolian_state_directory_add(state, eina_strbuf_string_get(buf));
   eina_strbuf_free(buf);
   return ret;
}

EOLIAN_API Eina_Iterator *
eolian_state_eot_files_get(const Eolian_State *state)
{
   if (!state) return NULL;
   return eina_hash_iterator_key_new(state->filenames_eot);
}

EOLIAN_API Eina_Iterator *
eolian_state_eo_files_get(const Eolian_State *state)
{
   if (!state) return NULL;
   return eina_hash_iterator_key_new(state->filenames_eo);
}

EOLIAN_API Eina_Iterator *
eolian_state_eot_file_paths_get(const Eolian_State *state)
{
   if (!state) return NULL;
   return eina_hash_iterator_data_new(state->filenames_eot);
}

EOLIAN_API Eina_Iterator *
eolian_state_eo_file_paths_get(const Eolian_State *state)
{
   if (!state) return NULL;
   return eina_hash_iterator_data_new(state->filenames_eo);
}

static Eolian_Unit *
_eolian_file_parse_nodep(Eolian_Unit *parent, const char *filename)
{
   Eina_Bool is_eo;
   is_eo = eina_str_has_suffix(filename, EO_SUFFIX);
   if (!is_eo && !eina_str_has_suffix(filename, EOT_SUFFIX))
     {
        eolian_state_log(parent->state,
                         "file '%s' doesn't have a correct extension",
                         filename);
        return NULL;
     }
   const char *eopath = eina_hash_find(is_eo ? parent->state->filenames_eo : parent->state->filenames_eot, filename);
   if (!eopath)
     {
        eolian_state_log(parent->state,
                         "file '%s' is not registered in the database",
                         filename);
        return NULL;
     }
   return eo_parser_database_fill(parent, eopath, !is_eo);
}

static void
_state_clean(Eolian_State *state)
{
   eina_hash_free_buckets(state->defer);

   Eolian_State_Area *st = &state->staging;

   Eolian_Unit *stu = &st->unit;
   eina_hash_free_buckets(stu->classes);
   eina_hash_free_buckets(stu->constants);
   eina_hash_free_buckets(stu->aliases);
   eina_hash_free_buckets(stu->structs);
   eina_hash_free_buckets(stu->enums);
   eina_hash_free_buckets(stu->objects);

   eina_hash_foreach(st->units, _ulist_free_cb, NULL);
   eina_hash_free_buckets(st->units);

   eina_hash_free_buckets(st->classes_f);

   _hashlist_free_buckets(st->aliases_f);
   _hashlist_free_buckets(st->structs_f);
   _hashlist_free_buckets(st->enums_f);
   _hashlist_free_buckets(st->constants_f);
   _hashlist_free_buckets(st->objects_f);
}

void
database_defer(Eolian_State *state, const char *fname, Eina_Bool isdep)
{
   void *nval = (void *)((size_t)isdep + 1);
   size_t found = (size_t)eina_hash_find(state->defer, fname);
   /* add if not found or upgrade to dep if requested */
   if (!found)
     eina_hash_add(state->defer, fname, nval);
   else if ((found <= 1) && isdep)
     eina_hash_set(state->defer, fname, nval);
}

static Eina_Bool _parse_deferred(Eolian_Unit *parent);
static void _merge_units(Eolian_Unit *unit);

typedef struct _Defer_Data
{
   Eolian_Unit *parent;
   Eina_Bool succ;
} Defer_Data;

static Eina_Bool
_defer_hash_cb(const Eina_Hash *hash EINA_UNUSED, const void *key,
               void *data, void *fdata)
{
   Defer_Data *d = fdata;
   Eina_Bool alone = ((size_t)data <= 1);
   Eolian_Unit *parent = d->parent;
   /* not a dependency; parse standalone */
   if (alone)
     parent = &parent->state->staging.unit;
   Eolian_Unit *pdep = _eolian_file_parse_nodep(parent, key);
   d->succ = (pdep && _parse_deferred(pdep));
   /* standalone-parsed stuff forms its own dependency trees,
    * so we have to merge the units manually and separately
    */
   if (d->succ && alone)
     _merge_units(pdep);
   return d->succ;
}

static Eina_Bool
_parse_deferred(Eolian_Unit *parent)
{
   Eina_Hash *defer = parent->state->defer;
   if (!eina_hash_population(defer))
     return EINA_TRUE;
   /* clean room for more deps for later parsing */
   parent->state->defer = eina_hash_string_small_new(NULL);
   Defer_Data d = { parent, EINA_FALSE };
   eina_hash_foreach(defer, _defer_hash_cb, &d);
   if (!d.succ)
     eina_hash_free_buckets(parent->state->defer);
   eina_hash_free(defer);
   return d.succ;
}

static Eina_Bool
_merge_unit_cb(const Eina_Hash *hash EINA_UNUSED,
               const void *key, void *data, void *fdata)
{
   Eina_Hash *dest = fdata;
   if (!eina_hash_find(dest, key))
     {
        eina_hash_add(dest, key, data);
        eolian_object_ref((Eolian_Object *)data);
     }
   return EINA_TRUE;
}

static Eina_Bool
_merge_unit_cb_noref(const Eina_Hash *hash EINA_UNUSED,
               const void *key, void *data, void *fdata)
{
   Eina_Hash *dest = fdata;
   if (!eina_hash_find(dest, key))
     eina_hash_add(dest, key, data);
   return EINA_TRUE;
}

static void
_merge_unit(Eolian_Unit *dest, Eolian_Unit *src)
{
   eina_hash_foreach(src->classes, _merge_unit_cb, dest->classes);
   eina_hash_foreach(src->constants, _merge_unit_cb, dest->constants);
   eina_hash_foreach(src->aliases, _merge_unit_cb, dest->aliases);
   eina_hash_foreach(src->structs, _merge_unit_cb, dest->structs);
   eina_hash_foreach(src->enums, _merge_unit_cb, dest->enums);
   eina_hash_foreach(src->objects, _merge_unit_cb_noref, dest->objects);
}

typedef struct _Merge_Data
{
   Eina_Hash *cycles;
   Eolian_Unit *unit;
} Merge_Data;

static Eina_Bool
_merge_units_cb(const Eina_Hash *hash EINA_UNUSED,
                const void *key EINA_UNUSED, void *data, void *fdata)
{
   Merge_Data *mdata = fdata;
   Merge_Data imdata = { mdata->cycles, data };
   if (!eina_hash_find(imdata.cycles, &imdata.unit))
     {
        eina_hash_add(imdata.cycles, &imdata.unit, imdata.unit);
        eina_hash_foreach(imdata.unit->children, _merge_units_cb, &imdata);
     }
   _merge_unit(mdata->unit, imdata.unit);
   return EINA_TRUE;
}


static void
_merge_units(Eolian_Unit *unit)
{
   Merge_Data mdata = { eina_hash_pointer_new(NULL), unit };
   eina_hash_foreach(unit->children, _merge_units_cb, &mdata);
   eina_hash_free(mdata.cycles);
}

static Eina_Bool
_merge_staging_cb(const Eina_Hash *hash EINA_UNUSED,
                  const void *key, void *data, void *fdata)
{
   eina_hash_add((Eina_Hash *)fdata, key, data);
   return EINA_TRUE;
}

static void
_merge_staging(Eolian_State *state)
{
   Eolian_State_Area *amain = &state->main, *staging = &state->staging;
   _merge_unit(&amain->unit, &staging->unit);

   eina_hash_foreach(staging->units, _merge_staging_cb, amain->units);
   eina_hash_free_buckets(staging->units);

#define EOLIAN_STAGING_MERGE_LIST(name) \
   eina_hash_foreach(staging->name##_f, _merge_staging_cb, amain->name##_f); \
   eina_hash_free_buckets(staging->name##_f);

   EOLIAN_STAGING_MERGE_LIST(classes);
   EOLIAN_STAGING_MERGE_LIST(aliases);
   EOLIAN_STAGING_MERGE_LIST(structs);
   EOLIAN_STAGING_MERGE_LIST(enums);
   EOLIAN_STAGING_MERGE_LIST(constants);
   EOLIAN_STAGING_MERGE_LIST(objects);

#undef EOLIAN_STAGING_MERGE_LIST

   _state_clean(state);
}

EOLIAN_API const Eolian_Unit *
eolian_state_file_parse(Eolian_State *state, const char *filename)
{
   if (!state)
     return NULL;

   _state_clean(state);
   Eolian_Unit *ret = _eolian_file_parse_nodep(&state->staging.unit, filename);
   if (!ret)
     return NULL;
   if (!_parse_deferred(ret))
     return NULL;
   _merge_units(ret);
   if (!database_validate(&state->staging.unit))
     return NULL;
   _merge_staging(state);
   return ret;
}

EOLIAN_API const Eolian_Unit *
eolian_state_file_path_parse(Eolian_State *state, const char *filepath)
{
   const Eolian_Unit *unit;
   if (!state)
     return NULL;

   char *mpath = strdup(filepath);
   if (!mpath)
     return NULL;

   char *fname = strrchr(mpath, '/');
   if (fname && strrchr(fname, '\\'))
     fname = strrchr(fname, '\\');

   const char *toscan = mpath;
   if (!fname)
     {
        toscan = ".";
        fname = mpath;
     }
   else
     *fname++ = '\0';

   if (!eolian_state_directory_add(state, toscan))
     {
        eolian_state_log(state, "could not scan directory '%s'", toscan);
        free(mpath);
        return NULL;
     }
   unit = eolian_state_file_parse(state, fname);
   free(mpath);
   return unit;
}

typedef struct _Parse_Data
{
   Eolian_State *state;
   Eina_Bool ret;
} Parse_Data;

static Eina_Bool _tfile_parse(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata)
{
   Parse_Data *pd = fdata;
   Eolian_Unit *unit = NULL;
   if (pd->ret)
     unit = eo_parser_database_fill(&pd->state->staging.unit, data, EINA_TRUE);
   pd->ret = !!unit;
   if (pd->ret) pd->ret = _parse_deferred(unit);
   if (pd->ret) _merge_units(unit);
   return pd->ret;
}

EOLIAN_API Eina_Bool
eolian_state_all_eot_files_parse(Eolian_State *state)
{
   Parse_Data pd = { state, EINA_TRUE };

   if (!state)
     return EINA_FALSE;

   _state_clean(state);
   eina_hash_foreach(state->filenames_eot, _tfile_parse, &pd);

   if (pd.ret && !database_validate(&state->staging.unit))
     return EINA_FALSE;

   _merge_staging(state);

   return pd.ret;
}

static Eina_Bool _file_parse(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata)
{
   Parse_Data *pd = fdata;
   Eolian_Unit *unit = NULL;
   if (pd->ret)
     unit = eo_parser_database_fill(&pd->state->staging.unit, data, EINA_FALSE);
   pd->ret = !!unit;
   if (pd->ret) pd->ret = _parse_deferred(unit);
   if (pd->ret) _merge_units(unit);
   return pd->ret;
}

EOLIAN_API Eina_Bool
eolian_state_all_eo_files_parse(Eolian_State *state)
{
   Parse_Data pd = { state, EINA_TRUE };

   if (!state)
     return EINA_FALSE;

   _state_clean(state);
   eina_hash_foreach(state->filenames_eo, _file_parse, &pd);

   if (pd.ret && !database_validate(&state->staging.unit))
     return EINA_FALSE;

   _merge_staging(state);

   return pd.ret;
}

EOLIAN_API Eina_Bool
eolian_state_check(const Eolian_State *state)
{
   return database_check(state);
}

EOLIAN_API const Eolian_Unit *
eolian_state_unit_by_file_get(const Eolian_State *state, const char *file_name)
{
   if (!state) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(file_name);
   Eolian_Unit *unit = eina_hash_find(state->main.units, shr);
   eina_stringshare_del(shr);
   return unit;
}

EOLIAN_API Eina_Iterator *
eolian_state_units_get(const Eolian_State *state)
{
   if (!state) return NULL;
   return eina_hash_iterator_data_new(state->main.units);
}

EOLIAN_API Eina_Iterator *
eolian_state_objects_by_file_get(const Eolian_State *state, const char *file_name)
{
   if (!state) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(file_name);
   Eina_List *l = eina_hash_find(state->main.objects_f, shr);
   eina_stringshare_del(shr);
   if (!l) return NULL;
   return eina_list_iterator_new(l);
}

EOLIAN_API const Eolian_Class *
eolian_state_class_by_file_get(const Eolian_State *state, const char *file_name)
{
   if (!state) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(file_name);
   Eolian_Class *cl = eina_hash_find(state->main.classes_f, shr);
   eina_stringshare_del(shr);
   return cl;
}

EOLIAN_API Eina_Iterator *
eolian_state_constants_by_file_get(const Eolian_State *state, const char *file_name)
{
   if (!state) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(file_name);
   Eina_List *l = eina_hash_find(state->main.constants_f, shr);
   eina_stringshare_del(shr);
   if (!l) return NULL;
   return eina_list_iterator_new(l);
}

EOLIAN_API Eina_Iterator *
eolian_state_errors_by_file_get(const Eolian_State *state, const char *file_name)
{
   if (!state) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(file_name);
   Eina_List *l = eina_hash_find(state->main.errors_f, shr);
   eina_stringshare_del(shr);
   if (!l) return NULL;
   return eina_list_iterator_new(l);
}

EOLIAN_API Eina_Iterator *
eolian_state_aliases_by_file_get(const Eolian_State *state, const char *file_name)
{
   if (!state) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(file_name);
   Eina_List *l = eina_hash_find(state->main.aliases_f, shr);
   eina_stringshare_del(shr);
   if (!l) return NULL;
   return eina_list_iterator_new(l);
}

EOLIAN_API Eina_Iterator *
eolian_state_structs_by_file_get(const Eolian_State *state, const char *file_name)
{
   if (!state) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(file_name);
   Eina_List *l = eina_hash_find(state->main.structs_f, shr);
   eina_stringshare_del(shr);
   if (!l) return NULL;
   return eina_list_iterator_new(l);
}

EOLIAN_API Eina_Iterator *
eolian_state_enums_by_file_get(const Eolian_State *state, const char *file_name)
{
   if (!state) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(file_name);
   Eina_List *l = eina_hash_find(state->main.enums_f, shr);
   eina_stringshare_del(shr);
   if (!l) return NULL;
   return eina_list_iterator_new(l);
}

EOLIAN_API const Eolian_State *
eolian_unit_state_get(const Eolian_Unit *unit)
{
   if (!unit) return NULL;
   return unit->state;
}

EOLIAN_API Eina_Iterator *
eolian_unit_children_get(const Eolian_Unit *unit)
{
   if (!unit) return NULL;
   return eina_hash_iterator_data_new(unit->children);
}

EOLIAN_API const char *
eolian_unit_file_get(const Eolian_Unit *unit)
{
   if (!unit) return NULL;
   return unit->file;
}

EOLIAN_API const char *
eolian_unit_file_path_get(const Eolian_Unit *unit)
{
   if (!unit || !unit->file) return NULL;
   Eina_Bool is_eo = eina_str_has_suffix(unit->file, EO_SUFFIX);
   return eina_hash_find(is_eo
     ? unit->state->filenames_eo
     : unit->state->filenames_eot, unit->file);
}

EOLIAN_API unsigned short
eolian_unit_version_get(const Eolian_Unit *unit)
{
   if (!unit) return 0;
   return unit->version;
}

EOLIAN_API const Eolian_Object *
eolian_unit_object_by_name_get(const Eolian_Unit *unit, const char *name)
{
   if (!unit) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(name);
   Eolian_Object *o = eina_hash_find(unit->objects, shr);
   eina_stringshare_del(shr);
   return o;
}

EOLIAN_API Eina_Iterator *eolian_unit_objects_get(const Eolian_Unit *unit)
{
   return (unit ? eina_hash_iterator_data_new(unit->objects) : NULL);
}

EOLIAN_API const Eolian_Class *
eolian_unit_class_by_name_get(const Eolian_Unit *unit, const char *class_name)
{
   if (!unit) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(class_name);
   Eolian_Class *cl = eina_hash_find(unit->classes, shr);
   eina_stringshare_del(shr);
   return cl;
}

EOLIAN_API Eina_Iterator *
eolian_unit_classes_get(const Eolian_Unit *unit)
{
   return (unit ? eina_hash_iterator_data_new(unit->classes) : NULL);
}

EOLIAN_API const Eolian_Constant *
eolian_unit_constant_by_name_get(const Eolian_Unit *unit, const char *name)
{
   if (!unit) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(name);
   Eolian_Constant *v = eina_hash_find(unit->constants, shr);
   eina_stringshare_del(shr);
   return v;
}

EOLIAN_API const Eolian_Error *
eolian_unit_error_by_name_get(const Eolian_Unit *unit, const char *name)
{
   if (!unit) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(name);
   Eolian_Error *v = eina_hash_find(unit->errors, shr);
   eina_stringshare_del(shr);
   return v;
}

EOLIAN_API Eina_Iterator *
eolian_unit_constants_get(const Eolian_Unit *unit)
{
   return (unit ? eina_hash_iterator_data_new(unit->constants) : NULL);
}

EOLIAN_API Eina_Iterator *
eolian_unit_errors_get(const Eolian_Unit *unit)
{
   return (unit ? eina_hash_iterator_data_new(unit->errors) : NULL);
}

EOLIAN_API const Eolian_Typedecl *
eolian_unit_alias_by_name_get(const Eolian_Unit *unit, const char *name)
{
   if (!unit) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(name);
   Eolian_Typedecl *tp = eina_hash_find(unit->aliases, shr);
   eina_stringshare_del(shr);
   if (!tp) return NULL;
   return tp;
}

EOLIAN_API const Eolian_Typedecl *
eolian_unit_struct_by_name_get(const Eolian_Unit *unit, const char *name)
{
   if (!unit) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(name);
   Eolian_Typedecl *tp = eina_hash_find(unit->structs, shr);
   eina_stringshare_del(shr);
   if (!tp) return NULL;
   return tp;
}

EOLIAN_API const Eolian_Typedecl *
eolian_unit_enum_by_name_get(const Eolian_Unit *unit, const char *name)
{
   if (!unit) return NULL;
   Eina_Stringshare *shr = eina_stringshare_add(name);
   Eolian_Typedecl *tp = eina_hash_find(unit->enums, shr);
   eina_stringshare_del(shr);
   if (!tp) return NULL;
   return tp;
}

EOLIAN_API Eina_Iterator *
eolian_unit_aliases_get(const Eolian_Unit *unit)
{
   return (unit ? eina_hash_iterator_data_new(unit->aliases) : NULL);
}

EOLIAN_API Eina_Iterator *
eolian_unit_structs_get(const Eolian_Unit *unit)
{
   return (unit ? eina_hash_iterator_data_new(unit->structs) : NULL);
}

EOLIAN_API Eina_Iterator *
eolian_unit_enums_get(const Eolian_Unit *unit)
{
   return (unit ? eina_hash_iterator_data_new(unit->enums) : NULL);
}

EOLIAN_API const char *
eolian_error_message_get(const Eolian_Error *err)
{
   if (!err) return NULL;
   return err->msg;
}

EOLIAN_API Eina_Bool
eolian_error_is_extern(const Eolian_Error *err)
{
   if (!err) return EINA_FALSE;
   return err->is_extern;
}

EOLIAN_API const Eolian_Documentation *
eolian_error_documentation_get(const Eolian_Error *err)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(err, NULL);
   return err->doc;
}

void
database_error_del(Eolian_Error *err)
{
   if (!err || eolian_object_unref(&err->base)) return;
   eina_stringshare_del(err->msg);
   database_doc_del(err->doc);
   free(err);
}

void
database_error_add(Eolian_Unit *unit, Eolian_Error *err)
{
   EOLIAN_OBJECT_ADD(unit, err->base.name, err, errors);
   eina_hash_set(unit->state->staging.errors_f, err->base.file, eina_list_append
                ((Eina_List*)eina_hash_find(unit->state->staging.errors_f, err->base.file),
                err));
   database_object_add(unit, &err->base);
}

char *
database_class_to_filename(const char *cname)
{
   char *ret;
   Eina_Strbuf *strbuf = eina_strbuf_new();
   eina_strbuf_append(strbuf, cname);
   eina_strbuf_replace_all(strbuf, ".", "_");
   eina_strbuf_append(strbuf, ".eo");

   ret = eina_strbuf_string_steal(strbuf);
   eina_strbuf_free(strbuf);

   eina_str_tolower(&ret);

   return ret;
}
