#include "pa.h"

static Pulse_Server_Info *
deserialize_server_info(Pulse *conn, Pulse_Tag *tag)
{
   Pulse_Server_Info *ev;
   pa_sample_spec spec;

   ev = calloc(1, sizeof(Pulse_Server_Info));
   ev->conn = conn;
   EINA_SAFETY_ON_NULL_RETURN_VAL(ev, NULL);
   EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &ev->name), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &ev->version), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &ev->username), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &ev->hostname), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_sample(tag, &spec), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &ev->default_sink), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &ev->default_source), error);

   return ev;
error:
   pulse_server_info_free(ev);
   return NULL;
}

static void
deserialize_sinks_watcher(Pulse *conn, Pulse_Tag *tag)
{
   pa_subscription_event_type_t e;
   uint32_t idx;

   EINA_SAFETY_ON_FALSE_RETURN(untag_uint32(tag, &e));
   EINA_SAFETY_ON_FALSE_RETURN(untag_uint32(tag, &idx));

   if (e & PA_SUBSCRIPTION_EVENT_CHANGE)
     {
        Pulse_Sink *sink;

        sink = eina_hash_find(pulse_sinks, &idx);
        if (sink)
          {
             if (pulse_sink_get(conn, idx))
               sink->update = EINA_TRUE;
          }
        else
          {
             sink = eina_hash_find(pulse_sources, &idx);
             if (!sink) return;
             if (pulse_source_get(conn, idx))
               sink->update = EINA_TRUE;
          }
     }
}

static Pulse_Sink *
deserialize_sink(Pulse *conn __UNUSED__, Pulse_Tag *tag, Eina_Bool source)
{
   Pulse_Sink *sink = NULL;
   Eina_Bool mute, exist;
   pa_sample_spec spec;
   uint32_t owner_module, monitor_source, flags, base_volume, state, n_volume_steps, card, n_ports;
   uint64_t latency, configured_latency;
   const char *monitor_source_name, *driver;
   Eina_Hash *props = NULL;
   unsigned int x;
   Pulse_Sink_Port_Info *pi = NULL;

   monitor_source_name = driver = NULL;
   EINA_SAFETY_ON_FALSE_GOTO(untag_uint32(tag, &x), error);
   if (source && pulse_sources)
     sink = eina_hash_find(pulse_sources, &x);
   else if ((!source) && pulse_sinks)
     sink = eina_hash_find(pulse_sinks, &x);
   exist = !!sink;
   if (!sink) sink = calloc(1, sizeof(Pulse_Sink));
   sink->index = x;
   EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &sink->name), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &sink->description), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_sample(tag, &spec), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_channel_map(tag, &sink->channel_map), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_uint32(tag, &owner_module), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_cvol(tag, &sink->volume), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_bool(tag, &mute), error);
   sink->mute = !!mute;
   EINA_SAFETY_ON_FALSE_GOTO(untag_uint32(tag, &monitor_source), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &monitor_source_name), error);
   eina_stringshare_del(monitor_source_name);
   EINA_SAFETY_ON_FALSE_GOTO(untag_usec(tag, &latency), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &driver), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_uint32(tag, &flags), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_proplist(tag, &props), error);
   eina_hash_free(props);
   EINA_SAFETY_ON_FALSE_GOTO(untag_usec(tag, &configured_latency), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_uint32(tag, &base_volume), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_uint32(tag, &state), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_uint32(tag, &n_volume_steps), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_uint32(tag, &card), error);
   EINA_SAFETY_ON_FALSE_GOTO(untag_uint32(tag, &n_ports), error);

   for (x = 0; x < n_ports; x++)
     {
        pi = calloc(1, sizeof(Pulse_Sink_Port_Info));
        EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &pi->name), error);
        EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &pi->description), error);
        EINA_SAFETY_ON_FALSE_GOTO(untag_uint32(tag, &pi->priority), error);
        sink->ports = eina_list_append(sink->ports, pi);
        pi = NULL;
     }
   EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &sink->active_port), error);
   if (exist)
     ecore_event_add(PULSE_EVENT_CHANGE, sink, pulse_fake_free, NULL);
   else
     {
        if (source && (!pulse_sources))
          pulse_sources = eina_hash_int32_new((Eina_Free_Cb)pulse_sink_free);
        else if ((!source) && (!pulse_sinks))
          pulse_sinks = eina_hash_int32_new((Eina_Free_Cb)pulse_sink_free);
        eina_hash_add(source ? pulse_sources : pulse_sinks, (uintptr_t*)&sink->index, sink);
     }
   return sink;
error:
   if (pi)
     {
        if (pi->name) eina_stringshare_del(pi->name);
        if (pi->description) eina_stringshare_del(pi->description);
        free(pi);
        pi = NULL;
     }
   pulse_sink_free(sink);
   eina_hash_free(props);
   return NULL;
}

Eina_Bool
deserialize_tag(Pulse *conn, PA_Commands command, Pulse_Tag *tag)
{
   Pulse_Cb cb;
   void *ev = (!command) ? NULL : PULSE_SUCCESS;

   cb = eina_hash_find(conn->tag_cbs, &tag->tag_count);
   if (command == PA_COMMAND_SUBSCRIBE)
     conn->watching = EINA_TRUE;
   switch (command)
     {
      case PA_COMMAND_GET_SERVER_INFO:
        if (!cb) return EINA_TRUE;
        ev = NULL;
        ev = deserialize_server_info(conn, tag);
        break;
      case PA_COMMAND_GET_SINK_INFO_LIST:
      case PA_COMMAND_GET_SOURCE_INFO_LIST:
        if (!cb) return EINA_TRUE;
        ev = NULL;
        while (tag->size < tag->dsize - PA_TAG_SIZE_STRING_NULL)
          {
             Pulse_Sink *sink;

             sink = deserialize_sink(conn, tag, (command == PA_COMMAND_GET_SOURCE_INFO_LIST));
             if (!sink)
               {
                  EINA_LIST_FREE(ev, sink)
                    pulse_sink_free(sink);
                  break;
               }
             if (cb) ev = eina_list_append(ev, sink);
          }
        break;
      case PA_COMMAND_GET_SINK_INFO:
      case PA_COMMAND_GET_SOURCE_INFO:
        if ((!cb) && (!conn->watching)) return EINA_TRUE;
        ev = deserialize_sink(conn, tag, (command == PA_COMMAND_GET_SOURCE_INFO));
        break;
      case 0:
        deserialize_sinks_watcher(conn, tag);
        return EINA_TRUE;
      default:
        break;
     }
   if (!cb) return EINA_TRUE;
   eina_hash_del_by_key(conn->tag_cbs, &tag->tag_count);
   cb(conn, tag->tag_count, ev);

   return EINA_TRUE;
}
