#include "main.h"
#include "docs.h"

extern char* _eolian_api_symbol;

static const char *
_get_add_star(Eolian_Function_Type ftype, Eolian_Parameter_Direction pdir)
{
   if (ftype == EOLIAN_PROP_GET)
     return "*";
   if ((pdir == EOLIAN_PARAMETER_OUT) || (pdir == EOLIAN_PARAMETER_INOUT))
     return "*";
   return "";
}

static int
_gen_param(Eina_Strbuf *buf, Eolian_Function_Parameter *pr,
           Eolian_Function_Type ftype, int *rpid)
{
   const Eolian_Type *prt = eolian_parameter_type_get(pr);
   const Eolian_Typedecl *ptd = eolian_type_typedecl_get(prt);
   const char *prn = eolian_parameter_name_get(pr);
   Eina_Stringshare *prtn = eolian_parameter_c_type_get(pr, EINA_FALSE);

   if (ptd && (eolian_typedecl_type_get(ptd) == EOLIAN_TYPEDECL_FUNCTION_POINTER))
     {
        eina_strbuf_append_printf(buf, "void *%s_data, %s %s, Eina_Free_Cb %s_free_cb",
                                  prn, prtn, prn, prn);
        eina_stringshare_del(prtn);
        *rpid = 1;
        return 3;
     }

   eina_strbuf_append(buf, prtn);
   if (prtn[strlen(prtn) - 1] != '*')
     eina_strbuf_append_char(buf, ' ');
   eina_strbuf_append(buf, _get_add_star(ftype, eolian_parameter_direction_get(pr)));
   eina_strbuf_append(buf, prn);
   eina_stringshare_del(prtn);
   if (eolian_parameter_is_move(pr))
     eina_strbuf_append(buf, " EFL_TRANSFER_OWNERSHIP");
   *rpid = 0;
   return 1;
}

void
eo_gen_params(Eina_Iterator *itr, Eina_Strbuf *buf,
              Eina_Strbuf **flagbuf, int *nidx, Eolian_Function_Type ftype)
{
   Eolian_Function_Parameter *pr;
   EINA_ITERATOR_FOREACH(itr, pr)
     {
        int rpid = 0;
        if (*nidx)
          eina_strbuf_append(buf, ", ");
        *nidx += _gen_param(buf, pr, ftype, &rpid);

        /* Keep the logic for when there's a better way to emit NONNULL */
#if 0
        if (!eolian_parameter_is_nonull(pr) || !flagbuf)
          continue;

        if (!*flagbuf)
          {
             *flagbuf = eina_strbuf_new();
             eina_strbuf_append_printf(*flagbuf, " EINA_ARG_NONNULL(%d", *nidx - rpid);
          }
        else
          eina_strbuf_append_printf(*flagbuf, ", %d", *nidx - rpid);
#else
        (void)flagbuf;
#endif
     }
   eina_iterator_free(itr);
}

static void
_gen_func(const Eolian_State *state, const Eolian_Function *fid,
          Eolian_Function_Type ftype, Eina_Strbuf *buf, char *cnameu)
{
   Eina_Stringshare *fcn = eolian_function_full_c_name_get(fid, ftype);
   if (!fcn)
     return;

   Eina_Bool var_as_ret = EINA_FALSE;
   const Eolian_Type *rtp = eolian_function_return_type_get(fid, ftype);
   Eina_Bool return_move = eolian_function_return_is_move(fid, ftype);
   Eina_Stringshare *rtps = NULL;
   if (ftype == EOLIAN_PROP_GET && !rtp)
     {
        void *d1, *d2;
        Eina_Iterator *itr = eolian_property_values_get(fid, ftype);
        if (eina_iterator_next(itr, &d1) && !eina_iterator_next(itr, &d2))
          {
             Eolian_Function_Parameter *pr = (Eolian_Function_Parameter *)d1;
             rtp = eolian_parameter_type_get(pr);
             return_move = eolian_parameter_is_move(pr);
             rtps = eolian_parameter_c_type_get(pr, EINA_TRUE);
             var_as_ret = EINA_TRUE;
          }
        eina_iterator_free(itr);
     }

   Eolian_Object_Scope fsc = eolian_function_scope_get(fid, ftype);

   if (eolian_function_is_beta(fid))
     eina_strbuf_append(buf, "#ifdef EFL_BETA_API_SUPPORT\n");
   if (fsc == EOLIAN_SCOPE_PROTECTED)
     eina_strbuf_append_printf(buf, "#ifdef %s_PROTECTED\n", cnameu);

   const Eolian_Implement *fimp = eolian_function_implement_get(fid);

   Eina_Bool hasdoc = !!eolian_implement_documentation_get(fimp, ftype);
   if (!hasdoc && ((ftype == EOLIAN_PROP_GET) || (ftype == EOLIAN_PROP_SET)))
     hasdoc = !!eolian_implement_documentation_get(fimp, EOLIAN_PROPERTY);
   if (hasdoc)
     {
        Eina_Strbuf *dbuf = eo_gen_docs_func_gen(state, fid, ftype, 0);
        eina_strbuf_append(buf, eina_strbuf_string_get(dbuf));
        eina_strbuf_append_char(buf, '\n');
        eina_strbuf_free(dbuf);
     }
   eina_strbuf_append_printf(buf, "%s %s_WEAK ", _eolian_api_symbol, _eolian_api_symbol);
   if (rtp)
     {
        if (!rtps)
          rtps = eolian_function_return_c_type_get(fid, ftype);
        eina_strbuf_append(buf, rtps);
        if (rtps[strlen(rtps) - 1] != '*')
          eina_strbuf_append_char(buf, ' ');
     }
   else
     eina_strbuf_append(buf, "void ");

   eina_strbuf_append(buf, fcn);
   eina_stringshare_del(fcn);
   eina_stringshare_del(rtps);

   Eina_Strbuf *flagbuf = NULL;
   int nidx = !eolian_function_is_static(fid);

   eina_strbuf_append_char(buf, '(');
   if (nidx)
     {
        if ((ftype == EOLIAN_PROP_GET) || eolian_function_object_is_const(fid)
            || eolian_function_is_static(fid))
          {
             eina_strbuf_append(buf, "const ");
          }
        eina_strbuf_append(buf, "Eo *obj");
     }

   eo_gen_params(eolian_property_keys_get(fid, ftype), buf, &flagbuf, &nidx, EOLIAN_PROPERTY);

   if (!var_as_ret)
     {
        Eina_Iterator *itr = NULL;
        if (ftype == EOLIAN_PROP_GET || ftype == EOLIAN_PROP_SET)
          itr = eolian_property_values_get(fid, ftype);
        else
          itr = eolian_function_parameters_get(fid);
        eo_gen_params(itr, buf, &flagbuf, &nidx, ftype);
     }

   if (flagbuf)
     eina_strbuf_append_char(flagbuf, ')');

   /* zero-arg funcs in C need void for arguments */
   if (eina_strbuf_string_get(buf)[eina_strbuf_length_get(buf) - 1] == '(')
     eina_strbuf_append(buf, "void");

   eina_strbuf_append(buf, ")");
   if (!eolian_function_return_allow_unused(fid, ftype) || return_move)
     {
        if (!flagbuf)
          flagbuf = eina_strbuf_new();
        eina_strbuf_prepend(flagbuf, " EINA_WARN_UNUSED_RESULT");
     }
   if (return_move)
     eina_strbuf_append(buf, " EFL_TRANSFER_OWNERSHIP");
   if (flagbuf)
     {
        eina_strbuf_append(buf, eina_strbuf_string_get(flagbuf));
        eina_strbuf_free(flagbuf);
     }
   eina_strbuf_append(buf, ";\n");

   if (fsc == EOLIAN_SCOPE_PROTECTED)
     eina_strbuf_append_printf(buf, "#endif\n");
   if (eolian_function_is_beta(fid))
     eina_strbuf_append_printf(buf, "#endif /* EFL_BETA_API_SUPPORT */\n");
}

void
eo_gen_header_gen(const Eolian_State *state, const Eolian_Class *cl,
                  Eina_Strbuf *buf)
{
   if (!cl)
     return;

   Eina_Iterator *itr;
   Eolian_Event *ev;
   char *cnameu = NULL;
   eo_gen_class_names_get(cl, NULL, &cnameu, NULL);

   /* class definition */

   if (eolian_class_is_beta(cl))
     {
        eina_strbuf_append(buf, "#ifdef EFL_BETA_API_SUPPORT\n");
     }
   const Eolian_Documentation *doc = eolian_class_documentation_get(cl);
   if (doc)
     {
        Eina_Strbuf *cdoc = eo_gen_docs_full_gen(state, doc,
           eolian_class_c_name_get(cl), 0);
        if (cdoc)
          {
             eina_strbuf_append(buf, eina_strbuf_string_get(cdoc));
             eina_strbuf_append_char(buf, '\n');
             eina_strbuf_free(cdoc);
          }
     }

   Eina_Stringshare *mname = eolian_class_c_macro_get(cl);
   Eina_Stringshare *gname = eolian_class_c_get_function_name_get(cl);
   eina_strbuf_append_printf(buf, "#define %s %s()\n\n", mname, gname);
   eina_stringshare_del(mname);

   eina_strbuf_append_printf(buf, "%s %s_WEAK const Efl_Class *%s(void) EINA_CONST;\n",
                             _eolian_api_symbol, _eolian_api_symbol, gname);
   eina_stringshare_del(gname);

   /* method section */
   itr = eolian_class_implements_get(cl);
   if (!itr)
     goto events;

   const Eolian_Implement *imp;
   EINA_ITERATOR_FOREACH(itr, imp)
     {
        if (eolian_implement_class_get(imp) != cl)
          continue;
        Eolian_Function_Type ftype = EOLIAN_UNRESOLVED;
        const Eolian_Function *fid = eolian_implement_function_get(imp, &ftype);
        eina_strbuf_append_char(buf, '\n');
         switch (ftype)
          {
           case EOLIAN_PROP_GET:
           case EOLIAN_PROP_SET:
             _gen_func(state, fid, ftype, buf, cnameu);
             break;
           case EOLIAN_PROPERTY:
             _gen_func(state, fid, EOLIAN_PROP_SET, buf, cnameu);
             eina_strbuf_append_char(buf, '\n');
              _gen_func(state, fid, EOLIAN_PROP_GET, buf, cnameu);
             break;
           default:
             _gen_func(state, fid, EOLIAN_METHOD, buf, cnameu);
          }
     }
   eina_iterator_free(itr);

events:
   /* event section */
   itr = eolian_class_events_get(cl);
   EINA_ITERATOR_FOREACH(itr, ev)
     {
        Eina_Stringshare *evn = eolian_event_c_macro_get(ev);
        Eolian_Object_Scope evs = eolian_event_scope_get(ev);

        if (evs == EOLIAN_SCOPE_PRIVATE)
          continue;

        if (eolian_event_is_beta(ev))
          {
             eina_strbuf_append(buf, "#ifdef EFL_BETA_API_SUPPORT\n");
          }
        if (evs == EOLIAN_SCOPE_PROTECTED)
          {
             if (!eolian_event_is_beta(ev))
               eina_strbuf_append_char(buf, '\n');
             eina_strbuf_append_printf(buf, "#ifdef %s_PROTECTED\n", cnameu);
          }

        if (!eolian_event_is_beta(ev) && evs == EOLIAN_SCOPE_PUBLIC)
          eina_strbuf_append_char(buf, '\n');

        eina_strbuf_append_printf(buf, "%s %s_WEAK extern const "
                                  "Efl_Event_Description _%s;\n\n",
                                  _eolian_api_symbol, _eolian_api_symbol, evn);

        Eina_Strbuf *evdbuf = eo_gen_docs_event_gen(state, ev,
           eolian_class_c_name_get(cl));
        eina_strbuf_append(buf, eina_strbuf_string_get(evdbuf));
        eina_strbuf_append_char(buf, '\n');
        eina_strbuf_free(evdbuf);
        eina_strbuf_append_printf(buf, "#define %s (&(_%s))\n", evn, evn);

        if (evs == EOLIAN_SCOPE_PROTECTED)
          eina_strbuf_append(buf, "#endif\n");
        if (eolian_event_is_beta(ev))
          eina_strbuf_append(buf, "#endif /* EFL_BETA_API_SUPPORT */\n");

        eina_stringshare_del(evn);
     }
   eina_iterator_free(itr);

   if (eolian_class_is_beta(cl))
     {
        eina_strbuf_append(buf, "#endif /* EFL_BETA_API_SUPPORT */\n");
     }

   free(cnameu);
}
