
#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>

#include "serialization.h"
#include "type.h"
#include "typelocator.h"
#include "serializationinfo.h"
#include "iterator.h"

#include "../mecha/mecha.h"

dynTstring *
serialization_serializeType (const char *typeName, void *field)
{
    SerializationInfo *info;
    dynTstring *string = NULL;
    bool serialize_result;
    Type *type;

    type = typelocator_getType (typelocator_getLocator(), typeName);

    if (type == NULL)
    {
        logFmsg (CONT_LOG_ERROR, "unknown type: %s", typeName);

        return (NULL);
    }

    info = serializationinfo_new();

    serialize_result = type->serialize (field, info);

    if (serialize_result == true)
    {
        string = serialization_serialize (info);
    }

    serializationinfo_destroy (info);

    /* returning the string if successful, and otherwise, NULL */
    return (string);
}

bool
serialization_deserializeType (const char *typeName, void *field, dynTstring *serializationData)
{
    SerializationInfo *info;
    bool deserialize_result;
    Type *type = typelocator_getType (typelocator_getLocator(), typeName);

    if (type == NULL)
    {
        logFmsg (CONT_LOG_ERROR, "unknown type: %s", typeName);

        return (false);
    }

    /* this should likely be updated to take a dynstring */
    info = serialization_deserialize (serializationData);

    if (info == NULL)
    {
        logFmsg (CONT_LOG_ERROR, "couldn't deserialize %s", typeName);

        serializationinfo_destroy (info);

        return (false);
    }

    deserialize_result = type->deserialize (field, info);

    serializationinfo_destroy (info);

    return (deserialize_result);
}

static const char *
serialization_getStringData (const char *data, char **outData, int *outLen)
{
    char *endptr;
    int len = strtol (data, &endptr, 10);

    if (*endptr != '[')
    {
        logFmsg (CONT_LOG_ERROR, "unknown string: %s", data);

        return (NULL);
    }
    
    *outData = endptr + 1;
    *outLen = len;

    /* 
     * data looks like:
     * nn[ssssssss]
     *
     * where
     *   n = "number"
     *   s = "string"
     * 
     * endptr will be at "[" and the number in "nn" will be the length of
     * "sss", so all we need to do is add 1 to get to "sss", ad len, and add 1
     * to get to the end of the string
     */
    endptr += 1 + len + 1;

    return (endptr);
}

static const char *
serialization_getString (const char *data, dynTstring *string)
{
    const char *endptr;
    char *outData;
    int outLen;

    endptr = serialization_getStringData (data, &outData, &outLen);

    if (endptr == NULL)
    {
        return (NULL);
    }

    dynFappend (string, outData, outLen);

    return (endptr);
}


dynTstring *
serialization_serializeTypeList (const char *typeName, Iterator *iterator)
{
    dynTstring *string = dynFinit();

    dynFsappend (string, "<l>");

    while (iterator_next (iterator) == IteratorSuccess)
    {
        dynTstring *entry_string;
        void *entry;

        entry = iterator_value (iterator);

        entry_string = serialization_serializeType (typeName, &entry);

        dynFappend_print (string, "%d", dynFgetlen (entry_string));
        dynFappend (string, "[", 1);
        dynFappend (string, dynFgetstr (entry_string), dynFgetlen (entry_string));
        dynFappend (string, "]", 1);

        dynFfree (entry_string);
    }

    iterator_destroy (iterator);

    return (string);
}

bool
serialization_deserializeTypeList (const char *typeName, dynTstring *serializationData, DeserializationCallback callback, void *context)
{
    int len = dynFgetlen (serializationData);
    const char *str_loc = dynFgetstr (serializationData);

    if (strncmp ("<l>", str_loc, 3) != 0)
    {
        logFmsg (CONT_LOG_ERROR, "not a list: %s", str_loc);

        return (false);
    }
    else
    {
        str_loc += 3;
    }

    while (str_loc != NULL && ((str_loc - dynFgetstr (serializationData)) < len))
    {
        dynTstring *value = dynFinit();
        void *value_object;

        str_loc = serialization_getString (str_loc, value);

        if (serialization_deserializeType (typeName, &value_object, value) == false)
        {
            logFmsg (CONT_LOG_ERROR, "Couldn't deserialize type %s", typeName);

            dynFfree (value);

            return (false);
        }

        dynFfree (value);

        callback (context, value_object);
    }
    
    if (str_loc == NULL)
    {
        logFmsg (CONT_LOG_ERROR, "error in data: %s", dynFgetstr (serializationData));

        return (false);
    }

    return (true);
}

dynTstring *
serialization_serialize (SerializationInfo *info)
{
    hshTiterator *iter = serializationinfo_getIterator (info); 
    dynTstring *string = dynFinit();

    dynFappend (string, "<si>", 4);

    while (hshFiterator_next (iter) == 1)
    {
        char *key = (char *) hshFiterator_current_key (iter);
        dynTstring *value = (dynTstring *) hshFiterator_current_value (iter);
        int key_len = strlen (key);

        dynFappend_print (string, "%d", key_len);
        dynFappend (string, "[", 1);
        dynFappend (string, key, key_len);
        dynFappend (string, "]", 1);

        dynFappend_print (string, "%d", dynFgetlen (value));
        dynFappend (string, "[", 1);
        dynFappend (string, dynFgetstr (value), dynFgetlen (value));
        dynFappend (string, "]", 1);
    }

    hshFiterator_free (iter);

    return (string);
}

SerializationInfo *
serialization_deserialize (dynTstring *string)
{
    SerializationInfo *info;
    const char *str_loc = dynFgetstr (string);

    if (strncmp ("<si>", str_loc, 4) != 0)
    {
        logFmsg (CONT_LOG_ERROR, "Not a SerializationInfo object: %s", dynFgetstr (string));

        return (NULL);
    }
    else
    {
        str_loc += 4;
    }

    info = serializationinfo_new();

    while (str_loc != NULL && ((unsigned int) (str_loc - dynFgetstr (string)) < dynFgetlen (string)))
    {
        char *key;
        int keyLen;
        dynTstring *value;

        str_loc = serialization_getStringData (str_loc, &key, &keyLen);

        if (str_loc == NULL)
        {
            logFmsg (CONT_LOG_ERROR, "Wrong number of params in: %s", dynFgetstr (string));

            serializationinfo_destroy (info);

            return (NULL);
        }

        value = dynFinit();

        str_loc = serialization_getString (str_loc, value);

        /* 
         * we're passing the value dynTstring to the SerializationInfo object,
         * so we don't need to free it ourselves in this call.
         */
        serializationinfo_addValueData (info, key, keyLen, value);
    }
    
    if (str_loc == NULL)
    {
        logFmsg (CONT_LOG_ERROR, "Error in data: %s", dynFgetstr (string));
        serializationinfo_destroy (info);

        return (NULL);
    }

    return (info);
}

dynTstring *
serialization_serializeList (lstTlist *list)
{
    dynTstring *string = dynFinit();
    lstTlist_elem *elem;

    dynFappend (string, "<l>", 3);

    for (elem = lstFlist_head (list); elem != NULL; elem = lstFlist_next (elem))
    {
        dynTstring *value = lstFlist_data (elem);

        dynFappend_print (string, "%d", dynFgetlen (value));
        dynFappend (string, "[", 1);
        dynFappend (string, dynFgetstr (value), dynFgetlen (value));
        dynFappend (string, "]", 1);
    }

    return (string);
}

lstTlist *
serialization_deserializeList (dynTstring *string)
{
    lstTlist *ser_data;
    int len = dynFgetlen (string);
    const char *str_loc = dynFgetstr (string);

    if (strncmp ("<l>", str_loc, 3) != 0)
    {
        logFmsg (CONT_LOG_ERROR, "Not a list: %s", dynFgetstr (string));

        return (NULL);
    }
    else
    {
        str_loc += 3;
    }

    ser_data = lstFlist_create ((void (*) (void *)) dynFfree);

    while (str_loc != NULL && ((str_loc - dynFgetstr (string)) < len))
    {
        dynTstring *value = dynFinit();

        str_loc = serialization_getString (str_loc, value);

        lstFlist_add_next (ser_data, lstFlist_tail (ser_data), value);
    }
    
    if (str_loc == NULL)
    {
        logFmsg (CONT_LOG_ERROR, "Error in data: %s", dynFgetstr (string));

        lstFlist_free (ser_data);

        return (NULL);
    }

    return (ser_data);
}
