
#ifndef EOLIAN_CXX_STD_PARAMETERS_GENERATOR_HH
#define EOLIAN_CXX_STD_PARAMETERS_GENERATOR_HH

#include <iosfwd>

#include "tab.hh"
#include "eo_types.hh"
#include "type_generator.hh"

namespace efl { namespace eolian { namespace grammar {

struct
parameter_forward
{
   eolian_type_instance const& _type;
   std::string const& _name;
   parameter_forward(eolian_type_instance const& type, std::string const& name)
     : _type(type)
     , _name(name)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, parameter_forward const& x)
{
   if (type_is_callback(x._type))
     {
        out << "std::forward<" << template_parameter_type(x._type, x._name) << ">(" << x._name << ")";
     }
   else
     out << x._name;
   return out;
}

struct param_data
{
   std::ostream& out;
   unsigned pos;
   eolian_type_instance const& type;
   std::string const& name;
   int cb_idx;
   bool is_cb;
   param_data(std::ostream& out_, unsigned pos_, eolian_type_instance const& type_, std::string const& name_, int cb_idx_)
      : out(out_)
      , pos(pos_)
      , type(type_)
      , name(name_)
      , cb_idx(cb_idx_)
      , is_cb(cb_idx_ >= 0)
   {}
};

template <typename T>
struct
_parameters_cxx_generic
{
   parameters_container_type const& _params;
   T _fparam;
   _parameters_cxx_generic(parameters_container_type const& params, T fparam)
     : _params(params)
     , _fparam(fparam)
   {}
};

template <typename T>
std::ostream&
operator<<(std::ostream& out, _parameters_cxx_generic<T> const& x)
{
   int cb_idx = 0u;
   auto first = x._params.cbegin(),
     last = x._params.cend();
   for (auto it = first; it != last; ++it)
     {
        if (type_is_callback((*it).type) && it+1 != last)
          {
             x._fparam(param_data(out, it - first, (*it).type, (*it).name, cb_idx++));
             ++it; // skip next.
          }
        else
          x._fparam(param_data(out, it - first, (*it).type, (*it).name, -1));
     }
   return out;
}

template <typename T>
_parameters_cxx_generic<T>
parameters_cxx_generic(parameters_container_type const& params, T fparam)
{
   return _parameters_cxx_generic<T>(params, fparam);
}

struct
callback_tmp
{
   std::string const& _name;
   callback_tmp(std::string const& name)
     : _name(name)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, callback_tmp const& x)
{
   return out << "_tmp_" << x._name;
}

struct
callback_parameter_free_ev_add
{
   std::string const& _eo_raw_expr;
   eolian_type_instance const& _type;
   std::string const& _name;
   callback_parameter_free_ev_add(std::string const& eo_raw_expr, eolian_type_instance const& type, std::string const& name)
     : _eo_raw_expr(eo_raw_expr)
     , _type(type)
     , _name(name)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, callback_parameter_free_ev_add const& x)
{
   out << "eo_do(" << x._eo_raw_expr
       << ", eo_event_callback_add(EO_BASE_EVENT_DEL, &::efl::eolian::free_callback_calback<"
       << parameter_no_ref_type(x._type, x._name) << ">, "
       << callback_tmp(x._name) << "));";
   return out;
}

struct
callbacks_heap_alloc
{
   std::string const& _eo_raw_expr;
   parameters_container_type const& _params;
   bool _is_static_func;
   int _tab;
   callbacks_heap_alloc(std::string const& eo_raw_expr, parameters_container_type const& params, bool is_static_func, int tab)
     : _eo_raw_expr(eo_raw_expr)
     , _params(params)
     , _is_static_func(is_static_func)
     , _tab(tab)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, callbacks_heap_alloc const& x)
{
   auto first = x._params.cbegin(), last = x._params.cend();
   for (auto it = first; it != last; ++it)
     {
        auto type = (*it).type;
        auto name = (*it).name;
        if (type_is_callback(type) && it+1 != last)
          {

             out << tab(x._tab) << parameter_remove_reference_typedef(type, name) << endl
                 << tab(x._tab) << parameter_no_ref_type(type, name) << "* "
                 << callback_tmp(name) << " = ";

            if (!x._is_static_func)
              {
                 out << "new " << parameter_no_ref_type(type, name)
                     << "(std::forward< "
                     << template_parameter_type(type, name) << " >(" << name << "));" << endl
                     << tab(x._tab)
                     << callback_parameter_free_ev_add(x._eo_raw_expr, type, name)
                     << endl << endl;
              }
            else
              {
                 out << "::efl::eolian::alloc_static_callback< "
                     << parameter_no_ref_type(type, name) << " >(std::forward< "
                     << template_parameter_type(type, name) << " >(" << name << "));" << endl;
              }
             ++it; // skip next.
          }
     }
   return out;
}

struct
template_parameters_declaration
{
   parameters_container_type const& _params;
   int _tab;
   template_parameters_declaration(parameters_container_type const& params, int tab)
     : _params(params)
     , _tab(tab)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, template_parameters_declaration const& x)
{
   if (parameters_count_callbacks(x._params) == 0)
     return out;

   bool comma = false;
   out << tab(x._tab) << "template <";
   auto first = x._params.cbegin(), last = x._params.cend();
   for (auto it = first; it != last; ++it)
     {
        if (type_is_callback((*it).type) && it+1 != last)
          {
             if (comma)
               out << ", ";
             else
               comma = true;
             out << "typename " << template_parameter_type((*it).type, (*it).name);
             ++it; // skip next.
          }
     }
   return out << ">" << endl;
}

struct
parameters_declaration
{
   parameters_container_type const& _params;
   parameters_declaration(parameters_container_type const& params)
     : _params(params)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, parameters_declaration const& x)
{
   auto first = x._params.cbegin(),
     last = x._params.cend();
   for (auto it = first; it != last; ++it)
     {
        if (it != first)
          out << ", ";
        // TODO What to do when callback happens in the middle of parameters
        //      and does not have a following userdata pointer ?
        if (type_is_callback((*it).type) && it+1 != last)
          {
            out << template_parameter_type((*it).type, (*it).name) << " && " << (*it).name;
            ++it; // skip next.
          }
        else
          out << reinterpret_type((*it).type) << " " << (*it).name;
     }
   return out;
}

struct
parameters_c_declaration
{
   parameters_container_type const& _params;
   parameters_c_declaration(parameters_container_type const& params)
     : _params(params)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, parameters_c_declaration const& x)
{
   auto first = x._params.cbegin(),
     last = x._params.cend();
   for (auto it = first; it != last; ++it)
     {
        if (it != first)
          out << ", ";
        out << c_type(it->type) << " " << (*it).name;
     }
   return out;
}

struct
parameters_names
{
   parameters_container_type const& _params;
   parameters_names(parameters_container_type const& params)
     : _params(params)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, parameters_names const& x)
{
   auto first = x._params.cbegin(),
        last = x._params.cend();
   for (auto it = first; it != last; ++it)
     {
        if (it != first)
          out << ", ";

        out << it->name;

        if (type_is_callback(it->type) && it+1 != last)
          ++it; // skip next.
     }
   return out;
}

struct
parameters_c_names
{
   parameters_container_type const& _params;
   parameters_c_names(parameters_container_type const& params)
     : _params(params)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, parameters_c_names const& x)
{
   auto first = x._params.cbegin(),
        last = x._params.cend();
   for (auto it = first; it != last; ++it)
     {
        if (it != first)
          out << ", ";
        out << it->name;
     }
   return out;
}

struct
parameters_types
{
   parameters_container_type const& _params;
   parameters_types(parameters_container_type const& params)
     : _params(params)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, parameters_types const& x)
{
   parameters_container_type::const_iterator it,
     first = x._params.begin(),
     last = x._params.end();
   for (it = first; it != last; ++it)
     {
        if(it != first) out << ", ";
        out << reinterpret_type((*it).type);
     }
   return out;
}

struct
parameters_c_list
{
   parameters_container_type const& _params;
   parameters_c_list(parameters_container_type const& params)
     : _params(params)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, parameters_c_list const& x)
{
   auto first = x._params.cbegin(), last = x._params.cend();
   for (auto it = first; it != last; ++it)
     {
        if (it != first)
          out << ", ";
        out << it->name;
     }
   return out;
}

struct
parameters_cxx_list
{
   parameters_container_type const& _params;
   parameters_cxx_list(parameters_container_type const& params)
     : _params(params)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, parameters_cxx_list const& x)
{
   auto first = x._params.cbegin(), last = x._params.cend();
   for (auto it = first; it != last; ++it)
     {
        if (it != first)
          out << ", ";
        out << to_cxx(it->type, it->name);
     }
   return out;
}

struct
parameters_forward
{
   parameters_container_type const& _params;
   parameters_forward(parameters_container_type const& params)
     : _params(params)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, parameters_forward const& x)
{
   auto first = x._params.cbegin(), last = x._params.cend();
   for (auto it = first; it != last; ++it)
     {
        if (it != first)
          out << ", ";
        out << parameter_forward((*it).type, (*it).name);
        if (type_is_callback((*it).type) && it+1 != last)
          {
             ++it; // skip next.
          }
     }
   return out;
}

struct
parameters_forward_to_c
{
   parameters_container_type const& _params;
   parameters_forward_to_c(parameters_container_type const& params)
     : _params(params)
   {}
};

inline std::ostream&
operator<<(std::ostream& out, parameters_forward_to_c const& x)
{
   auto first = x._params.cbegin(), last = x._params.cend();
   for (auto it = first; it != last; ++it)
     {
        if (it != first)
          out << ", ";
        if (type_is_callback((*it).type))
          {
             // TODO: Is it correct to simple not translate the callback type
             //       when it is the last paramenter?
             if(it + 1 != last)
               {
                  out << to_c((*it).type, (*it).name) << ", " << callback_tmp((*it).name);
                  ++it; // skip next
               }
             else
               out << (*it).name;
          }
        else
          out << to_c((*it).type, (*it).name);
     }
   return out;
}

} } } // namespace efl { namespace eolian { namespace grammar {


#endif // EOLIAN_CXX_STD_PARAMETERS_GENERATOR_HH
