#include "e.h"
#include "e_mod_config.h"
#include "e_mod_main.h"
#include "emix.h"

typedef struct _Emix_Config
{
   const char *backend;
   int notify;
   int mute;

   int save;
   const char *save_sink;

   Eina_List *sinks;
   Eina_List *sources;

   emix_config_backend_changed cb;
   const void *userdata;
} Emix_Config;

typedef struct _Emix_Config_Sink
{
   const char *name;
   Eina_List *ports;
   int mute;
   int volume;
} Emix_Config_Sink;

typedef struct _Emix_Config_Source
{
   const char *name;
   int mute;
   int volume;
} Emix_Config_Source;

typedef struct _Emix_Config_Port
{
   const char *name;
   int active;
} Emix_Config_Port;


struct _E_Config_Dialog_Data
{
   Emix_Config config;
   Evas_Object *list;
   Evas_Object *sources;
};

static E_Config_DD *cd, *c_portd, *c_sinkd, *c_sourced;
static Emix_Config *_config = NULL;

static void
_emix_config_dd_new(void)
{
   c_portd = E_CONFIG_DD_NEW("Emix_Config_Port", Emix_Config_Port);
   E_CONFIG_VAL(c_portd, Emix_Config_Port, name, STR);
   E_CONFIG_VAL(c_portd, Emix_Config_Port, active, INT);

   c_sinkd = E_CONFIG_DD_NEW("Emix_Config_Sink", Emix_Config_Sink);
   E_CONFIG_VAL(c_sinkd, Emix_Config_Sink, name, STR);
   E_CONFIG_LIST(c_sinkd, Emix_Config_Sink, ports, c_portd);
   E_CONFIG_VAL(c_sinkd, Emix_Config_Sink, mute, INT);
   E_CONFIG_VAL(c_sinkd, Emix_Config_Sink, volume, INT);

   c_sourced = E_CONFIG_DD_NEW("Emix_Config_Source", Emix_Config_Source);
   E_CONFIG_VAL(c_sourced, Emix_Config_Source, name, STR);
   E_CONFIG_VAL(c_sourced, Emix_Config_Source, mute, INT);
   E_CONFIG_VAL(c_sourced, Emix_Config_Source, volume, INT);

   cd = E_CONFIG_DD_NEW("Emix_Config", Emix_Config);

   E_CONFIG_VAL(cd, Emix_Config, backend, STR);
   E_CONFIG_VAL(cd, Emix_Config, notify, INT);
   E_CONFIG_VAL(cd, Emix_Config, mute, INT);

   E_CONFIG_VAL(cd, Emix_Config, save, INT);
   E_CONFIG_VAL(cd, Emix_Config, save_sink, STR);

   E_CONFIG_LIST(cd, Emix_Config, sinks, c_sinkd);
   E_CONFIG_LIST(cd, Emix_Config, sources, c_sourced);
}

const char *
emix_config_backend_get(void)
{
   return _config->backend;
}

void
emix_config_backend_set(const char *backend)
{
   eina_stringshare_replace(&_config->backend, backend);
   e_config_domain_save("module.emix", cd, _config);
}

Eina_Bool
emix_config_notify_get(void)
{
   return _config->notify;
}

Eina_Bool
emix_config_desklock_mute_get(void)
{
   return _config->mute;
}

static void
_config_set(Emix_Config *config)
{
   if ((config->backend) && (_config->backend != config->backend))
     eina_stringshare_replace(&_config->backend, config->backend);

   _config->notify = config->notify;
   _config->mute = config->mute;

   if (config->save == 0) _config->save = -1;
   else if (config->save == 1) _config->save = 1;

   DBG("SAVING CONFIG %s %d %d", _config->backend, config->notify,
       config->mute);
   e_config_domain_save("module.emix", cd, config);
}

void
emix_config_init(emix_config_backend_changed cb, const void *userdata)
{
   const Eina_List *backends, *l;
   const char *s;

   EINA_SAFETY_ON_FALSE_RETURN(emix_init());
   _emix_config_dd_new();
   _config = e_config_domain_load("module.emix", cd);
   if (!_config)
     {
        _config = E_NEW(Emix_Config, 1);
        backends = emix_backends_available();
        // prefer pulseaudio as a packend if it exists as this is generally
        // more useful, and a superset of ALSA. so if pulse is there, alsa
        // is too - so choosing alsa if pulse is available is wrong (as a
        // default) and leads to brokenness. in the case pulse is not
        // around, alsa will then work
        EINA_LIST_FOREACH(backends, l, s)
          {
             if (!strcmp(s, "PULSEAUDIO"))
               {
                  _config->backend = eina_stringshare_add(s);
                  break;
               }
          }
        if (!_config->backend)
          {
             if (backends)
               _config->backend = eina_stringshare_add(backends->data);
          }
     }

   if (_config->save == 0) _config->save = 1;

   _config->cb = cb;
   _config->userdata = userdata;
   DBG("Config loaded, backend to use: %s", _config->backend);
}

void
emix_config_shutdown(void)
{
   Emix_Config_Sink *sink;
   Emix_Config_Port *port;
   Emix_Config_Source *source;

   E_CONFIG_DD_FREE(cd);
   E_CONFIG_DD_FREE(c_sourced);
   E_CONFIG_DD_FREE(c_sinkd);
   E_CONFIG_DD_FREE(c_portd);
   if (_config->backend) eina_stringshare_del(_config->backend);
   if (_config->save_sink) eina_stringshare_del(_config->save_sink);
   EINA_LIST_FREE(_config->sinks, sink)
     {
        if (sink->name) eina_stringshare_del(sink->name);
        EINA_LIST_FREE(sink->ports, port)
          {
             if (port->name) eina_stringshare_del(port->name);
             free(port);
          }
        free(sink);
     }
   EINA_LIST_FREE(_config->sources, source)
     {
        if (source->name) eina_stringshare_del(source->name);
        free(source);
     }
   free(_config);
   emix_shutdown();
}

void
emix_config_save(void)
{
   if ((_config) && (cd))
     e_config_domain_save("module.emix", cd, _config);
}

Eina_Bool
emix_config_save_get(void)
{
   if (_config->save == 1) return EINA_TRUE;
   return EINA_FALSE;
}

void
emix_config_save_state_get(void)
{
   Emix_Config_Sink *sink;
   Emix_Config_Port *port;
   Emix_Config_Source *source;

   Eina_List *sinks, *sources, *l, *ll;
   Emix_Sink *emsink;
   Emix_Source *emsource;
   Emix_Port *emport;

   EINA_LIST_FREE(_config->sinks, sink)
     {
        if (sink->name) eina_stringshare_del(sink->name);
        EINA_LIST_FREE(sink->ports, port)
          {
             if (port->name) eina_stringshare_del(port->name);
             free(port);
          }
        free(sink);
     }
   EINA_LIST_FREE(_config->sources, source)
     {
        if (source->name) eina_stringshare_del(source->name);
        free(source);
     }

   sinks = (Eina_List *)emix_sinks_get();
   EINA_LIST_FOREACH(sinks, l, emsink)
     {
        if (!emsink->name) continue;
        sink = calloc(1, sizeof(Emix_Config_Sink));
        if (sink)
          {
             sink->name = eina_stringshare_add(emsink->name);
             EINA_LIST_FOREACH(emsink->ports, ll, emport)
               {
                  if (!emport->name) continue;
                  port = calloc(1, sizeof(Emix_Config_Port));
                  if (port)
                    {
                       port->name = eina_stringshare_add(emport->name);
                       port->active = emport->active;
                       sink->ports = eina_list_append(sink->ports, port);
                    }
               }
             if (emsink->volume.channel_count > 0)
               sink->volume = emsink->volume.volumes[0];
             sink->mute = emsink->mute;
             _config->sinks = eina_list_append(_config->sinks, sink);
          }
     }
   sources = (Eina_List *)emix_sources_get();
   EINA_LIST_FOREACH(sources, ll, emsource)
     {
        if (!emsource->name) continue;
        source = calloc(1, sizeof(Emix_Config_Source));
        if (source)
          {
             source->name = eina_stringshare_add(emsource->name);
             if (emsource->volume.channel_count > 0)
               source->volume = emsource->volume.volumes[0];
             source->mute = emsource->mute;
             _config->sources = eina_list_append(_config->sources, source);
          }
     }
}

static Emix_Sink *
_find_sink(const char *name)
{
   Eina_List *sinks, *l;
   Emix_Sink *emsink;

   sinks = (Eina_List *)emix_sinks_get();
   EINA_LIST_FOREACH(sinks, l, emsink)
     {
        if (!emsink->name) continue;
        if (!strcmp(emsink->name, name)) return emsink;
     }
   return NULL;
}

static Emix_Port *
_find_port(Emix_Sink *sink, const char *name)
{
   Eina_List *l;
   Emix_Port *emport;

   EINA_LIST_FOREACH(sink->ports, l, emport)
     {
        if (!emport->name) continue;
        if (!strcmp(emport->name, name)) return emport;
     }
   return NULL;
}

static Emix_Source *
_find_source(const char *name)
{
   Eina_List *sources, *l;
   Emix_Source *emsource;

   sources = (Eina_List *)emix_sources_get();
   EINA_LIST_FOREACH(sources, l, emsource)
     {
        if (!emsource->name) continue;
        if (!strcmp(emsource->name, name)) return emsource;
     }
   return NULL;
}

void
emix_config_save_state_restore(void)
{
   Emix_Config_Sink *sink;
   Emix_Config_Port *port;
   Emix_Config_Source *source;
   Emix_Sink *emsink;
   Emix_Source *emsource;
   Emix_Port *emport;
   Eina_List *l, *ll;
   Emix_Volume v;
   unsigned int i;

   EINA_LIST_FOREACH(_config->sinks, l, sink)
     {
        if (!sink->name) continue;
        emsink = _find_sink(sink->name);
        if (!emsink) continue;
        v.volumes = calloc(emsink->volume.channel_count, sizeof(int));
        if (v.volumes)
          {
             v.channel_count = emsink->volume.channel_count;
             for (i = 0; i < v.channel_count; i++)
               v.volumes[i] = sink->volume;
             emix_sink_volume_set(emsink, &v);
             free(v.volumes);
          }
        emix_sink_mute_set(emsink, sink->mute);
        EINA_LIST_FOREACH(sink->ports, ll, port)
          {
             if (!port->name) continue;
             if (port->active)
               {
                  emport = _find_port(emsink, port->name);
                  if (emport) emix_sink_port_set(emsink, emport);
                  break;
               }
          }
     }
   EINA_LIST_FOREACH(_config->sources, l, source)
     {
        if (!source->name) continue;
        emsource = _find_source(source->name);
        if (!emsource) continue;
        v.volumes = calloc(emsource->volume.channel_count, sizeof(int));
        if (v.volumes)
          {
             v.channel_count = emsource->volume.channel_count;
             for (i = 0; i < v.channel_count; i++)
               v.volumes[i] = source->volume;
             emix_source_volume_set(emsource, &v);
             free(v.volumes);
          }
        emix_source_mute_set(emsource, source->mute);
     }
}

const char *
emix_config_save_sink_get(void)
{
   return _config->save_sink;
}

void
emix_config_save_sink_set(const char *sink)
{
   eina_stringshare_replace(&(_config->save_sink), sink);
   if (_config->save == 1) e_config_save_queue();
}

static void*
_create_data(E_Config_Dialog *cfg EINA_UNUSED)
{
   E_Config_Dialog_Data *d;

   d = E_NEW(E_Config_Dialog_Data, 1);
   d->config.backend = eina_stringshare_add(_config->backend);
   d->config.notify = _config->notify;
   d->config.mute = _config->mute;

   if (_config->save == -1) d->config.save = 0;
   else d->config.save = 1;

   return d;
}

static void
_free_data(E_Config_Dialog *c EINA_UNUSED, E_Config_Dialog_Data *cf)
{
   eina_stringshare_del(cf->config.backend);
   free(cf);
}

static Evas_Object *
_basic_create_widgets(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas,
                      E_Config_Dialog_Data *cfdata)
{
   Evas_Object *o, *l;
   const Eina_List *node;
   char *name;
   int i = 0;

   o = e_widget_list_add(evas, 0, 0);

   l = e_widget_check_add(evas, "Notify on volume change", &cfdata->config.notify);
   e_widget_list_object_append(o, l, 0, 0, 0);

   l = e_widget_check_add(evas, "Mute on lock", &cfdata->config.mute);
   e_widget_list_object_append(o, l, 0, 0, 0);

   l = e_widget_check_add(evas, "Remember", &cfdata->config.save);
   e_widget_list_object_append(o, l, 0, 0, 0);

   l = e_widget_label_add(evas, "Backend to use:");
   e_widget_list_object_append(o, l, 0, 0, 0);

   cfdata->list = l = e_widget_ilist_add(evas, 0, 0, NULL);
   e_widget_ilist_multi_select_set(l, EINA_FALSE);
   e_widget_size_min_set(l, 100, 100);
   EINA_LIST_FOREACH(emix_backends_available(), node, name)
     {
        e_widget_ilist_append(l, NULL, name, NULL, NULL, NULL);
        i ++;
        if (_config->backend && !strcmp(_config->backend, name))
          e_widget_ilist_selected_set(l, i);
     }
   e_widget_ilist_go(l);
   e_widget_ilist_thaw(l);
   e_widget_list_object_append(o, l, 1, 1, 0);

   return o;
}

static int
_basic_apply_data(E_Config_Dialog *cfd EINA_UNUSED,
                  E_Config_Dialog_Data *cfdata)
{
   char *new_backend = eina_list_nth(
                            emix_backends_available(),
                            e_widget_ilist_selected_get(cfdata->list));

   eina_stringshare_replace(&cfdata->config.backend, new_backend);

   _config_set(&cfdata->config);
   if (_config->cb)
      _config->cb(new_backend, (void *)_config->userdata);
   return 1;
}

E_Config_Dialog*
emix_config_popup_new(Evas_Object *comp, const char *p EINA_UNUSED)
{
   E_Config_Dialog *cfd;
   E_Config_Dialog_View *v;

   if (e_config_dialog_find("E", "windows/emix"))
      return NULL;

   v = E_NEW(E_Config_Dialog_View, 1);
   v->create_cfdata = _create_data;
   v->free_cfdata = _free_data;
   v->basic.apply_cfdata = _basic_apply_data;
   v->basic.create_widgets = _basic_create_widgets;

   cfd = e_config_dialog_new(comp,
                             "Emix Configuration",
                             "E", "windows/emix",
                             NULL,
                             0, v, NULL);
   return cfd;
}
