#include "pa.h"

uint8_t *
tag_uint32(Pulse_Tag *tag, uint32_t val)
{
   uint8_t *ret;

   ret = tag->data + tag->size;
   *ret = PA_TAG_U32;
   val = htonl(val);
   memcpy(ret + 1, &val, sizeof(val));
   ret += PA_TAG_SIZE_U32;
   tag->size = ret - tag->data;
   return ret;
}

uint8_t *
untag_uint32(Pulse_Tag *tag, uint32_t *val)
{
   uint8_t *ret;

   ret = tag->data + tag->size;
   if ((*ret != PA_TAG_U32) && (*ret != PA_TAG_VOLUME)) return 0;
   memcpy(val, ret + 1, sizeof(uint32_t));
   *val = ntohl(*val);
   ret += PA_TAG_SIZE_U32;
   tag->size = ret - tag->data;
   return ret;
}

uint8_t *
tag_bool(Pulse_Tag *tag, Eina_Bool val)
{
   uint8_t *ret;

   ret = tag->data + tag->size;
   *ret = (uint8_t)(val ? PA_TAG_BOOLEAN_TRUE : PA_TAG_BOOLEAN_FALSE);
   ret += PA_TAG_SIZE_BOOLEAN;
   tag->size = ret - tag->data;
   return ret;
}

uint8_t *
untag_bool(Pulse_Tag *tag, Eina_Bool *val)
{
   uint8_t *ret;

   ret = tag->data + tag->size;
   if ((*ret != PA_TAG_BOOLEAN_TRUE) && (*ret != PA_TAG_BOOLEAN_FALSE)) return 0;
   *val = !!(*ret == PA_TAG_BOOLEAN_TRUE) ? EINA_TRUE : EINA_FALSE;
   ret += PA_TAG_SIZE_BOOLEAN;
   tag->size = ret - tag->data;
   return ret;
}

uint8_t *
tag_string(Pulse_Tag *tag, const char *val)
{
   uint8_t *ret;

   ret = tag->data + tag->size;
   if (val)
     {
        *ret = PA_TAG_STRING;
        strcpy((char*)ret + 1, val);
        ret += PA_TAG_SIZE_STRING + strlen(val);
        tag->size = ret - tag->data;
     }
   else
     *ret = PA_TAG_STRING_NULL, tag->size++;
   return ret;
}

uint8_t *
untag_string(Pulse_Tag *tag, const char **val)
{
   uint8_t *ret;

   ret = tag->data + tag->size;
   switch (*ret)
     {
      case PA_TAG_STRING:
        eina_stringshare_replace(val, (char*)ret + 1);
        ret += PA_TAG_SIZE_STRING + strlen(*val);
        break;
      case PA_TAG_STRING_NULL:
        *val = NULL;
        ret += PA_TAG_SIZE_STRING_NULL;
        break;
      default:
        return 0;
     }
   tag->size = ret - tag->data;
   return ret;
}

uint8_t *
untag_sample(Pulse_Tag *tag, pa_sample_spec *spec)
{
   uint8_t *ret;

   ret = tag->data + tag->size;
   if (*ret != PA_TAG_SAMPLE_SPEC) return 0;

   spec->format = ret[1];
   spec->channels = ret[2];
   memcpy(&spec->rate, ret + 3, sizeof(spec->rate));
   spec->rate = ntohl(spec->rate);
   ret += PA_TAG_SIZE_SAMPLE_SPEC, tag->size += PA_TAG_SIZE_SAMPLE_SPEC;
   return ret;
}

uint8_t *
untag_channel_map(Pulse_Tag *tag, pa_channel_map *map)
{
   uint8_t *ret;
   unsigned int x;

   ret = tag->data + tag->size;
   if (*ret != PA_TAG_CHANNEL_MAP) return 0;

   map->channels = ret[1];
   if (map->channels > PA_CHANNELS_MAX) return 0;
   if (map->channels + 2 + tag->size > tag->dsize) return 0;

   for (ret += 2, x = 0; x < map->channels; ret++, x++)
     map->map[x] = (int8_t)*ret;

   tag->size = ret - tag->data;
   return ret;
}

uint8_t *
untag_usec(Pulse_Tag *tag, uint64_t *val)
{
   uint8_t *ret;
   uint32_t tmp;

   ret = tag->data + tag->size;
   if (*ret != PA_TAG_USEC) return 0;

   memcpy(&tmp, ret + 1, 4);
   *val = (uint64_t)ntohl(tmp) << 32;
   memcpy(&tmp, ret + 5, 4);
   *val |= (uint64_t)ntohl(tmp);
   ret += PA_TAG_SIZE_USEC;
   tag->size = ret - tag->data;
   return ret;
}

uint8_t *
tag_arbitrary(Pulse_Tag *tag, const void *val, uint32_t size)
{
   uint8_t *ret;
   uint32_t tmp;

   ret = tag->data + tag->size;
   *ret = PA_TAG_ARBITRARY;
   tmp = htonl(size);
   memcpy(ret + 1, &tmp, sizeof(uint32_t));
   memcpy(ret + PA_TAG_SIZE_U32, val, size);
   ret += PA_TAG_SIZE_ARBITRARY + size;
   tag->size = ret - tag->data;
   return ret;
}

uint8_t *
untag_arbitrary(Pulse_Tag *tag, Eina_Binbuf **val)
{
   uint8_t *ret;
   uint32_t len;

   if (!untag_uint32(tag, &len)) return 0;
   ret = tag->data + tag->size;
   if (*ret != PA_TAG_ARBITRARY) return 0;
   ret += PA_TAG_SIZE_ARBITRARY;

   *val = eina_binbuf_new();
   eina_binbuf_append_length(*val, ret, len);
   ret += len;
   tag->size = ret - tag->data;
   return ret;
}

uint8_t *
tag_simple_init(Pulse *conn, Pulse_Tag *tag, uint32_t val, PA_Tag type)
{
   switch (type)
     {
      case PA_TAG_U32:
         tag_uint32(tag, val);
         return tag_uint32(tag, conn->tag_count++);
      default:
        break;
     }
   return NULL;
}

static Eina_Bool
tag_proplist_foreach(const Eina_Hash *h __UNUSED__, const char *key, const char *val, Pulse_Tag *tag)
{
   size_t size;

   tag_string(tag, key);
   size = strlen(val) + 1;
   tag_uint32(tag, size);
   tag_arbitrary(tag, val, size);
   return EINA_TRUE;
}

uint8_t *
tag_proplist(Pulse_Tag *tag)
{
   uint8_t *ret;

   ret = tag->data + tag->size;
   *ret = PA_TAG_PROPLIST, tag->size++;

   eina_hash_foreach(tag->props, (Eina_Hash_Foreach)tag_proplist_foreach, tag);
   return tag_string(tag, NULL);
}

uint8_t *
untag_proplist(Pulse_Tag *tag, Eina_Hash **props)
{
   uint8_t *ret;

   ret = tag->data + tag->size;
   if (*ret != PA_TAG_PROPLIST) return 0;

   *props = eina_hash_string_superfast_new((Eina_Free_Cb)eina_binbuf_free);
   for (++tag->size; *ret != PA_TAG_STRING_NULL && tag->size < tag->dsize - 1;)
     {
        const char *key = NULL;
        Eina_Binbuf *val;
        EINA_SAFETY_ON_FALSE_GOTO(untag_string(tag, &key), error);
        EINA_SAFETY_ON_FALSE_GOTO(untag_arbitrary(tag, &val), error);
#if 0
        {
           char buf[128];
           snprintf(buf, sizeof(buf), "key='%%s', val='%%.%zus'", eina_binbuf_length_get(val));
           DBG(buf, key, eina_binbuf_string_get(val));
        }
#endif
        eina_hash_add(*props, key, val);
        eina_stringshare_del(key);
        ret = tag->data + tag->size;
     }
   tag->size++;
   return ++ret;
error:
   eina_hash_free(*props);
   return 0;
}

uint8_t *
tag_volume(Pulse_Tag *tag, uint8_t channels, double vol)
{
   uint32_t pa_vol;
   uint8_t *ret, x;

   if (vol <= 0.0) pa_vol = PA_VOLUME_MUTED;
   else pa_vol = ((vol * PA_VOLUME_NORM) - (PA_VOLUME_NORM / 2)) / 100;
   pa_vol = htonl(pa_vol);
   ret = tag->data + tag->size;
   *ret++ = PA_TAG_CVOLUME;
   *ret++ = channels;
   for (x = 0; x < channels; x++, ret += sizeof(pa_vol))
     memcpy(ret, &pa_vol, sizeof(pa_vol));
   tag->size = ret - tag->data;
   return ret;
}

uint8_t *
tag_cvol(Pulse_Tag *tag, pa_cvolume *c)
{
   uint8_t *ret, x;
   uint32_t pa_vol;

   ret = tag->data + tag->size;
   *ret++ = PA_TAG_CVOLUME;
   *ret++ = c->channels;
   for (x = 0; x < c->channels; x++, ret += sizeof(pa_vol))
     {
        pa_vol = htonl(c->values[x]);
        memcpy(ret, &pa_vol, sizeof(pa_vol));
     }
   tag->size = ret - tag->data;
   return ret;
}

uint8_t *
untag_cvol(Pulse_Tag *tag, pa_cvolume *cvol)
{
   uint32_t pa_vol;
   uint8_t *ret, x;

   ret = tag->data + tag->size;
   if (*ret != PA_TAG_CVOLUME) return 0;
   cvol->channels = ret[1];
   for (x = 0, ret += 2; x < cvol->channels; x++, ret += sizeof(pa_vol))
     {
        memcpy(&pa_vol, ret, sizeof(pa_vol));
        cvol->values[x] = ntohl(pa_vol);
     }

   tag->size = ret - tag->data;
   return ret;
}

void
tag_finish(Pulse_Tag *tag)
{
   EINA_SAFETY_ON_NULL_RETURN(tag);
   tag->header[PA_PSTREAM_DESCRIPTOR_CHANNEL] = htonl((uint32_t) -1);
   tag->header[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl(tag->dsize);
}

void
pulse_tag_free(Pulse_Tag *tag)
{
   if (!tag) return;
   free(tag->data);
   if (tag->props) eina_hash_free(tag->props);
   free(tag);
}
