/*****************************************************************************

            (c) Cambridge Silicon Radio Limited 2010
            All rights reserved and confidential information of CSR

            Refer to LICENSE.txt included with this source for details
            on the license terms.

*****************************************************************************/

#include <linux/module.h>
#include <linux/types.h>
#include <linux/slab.h>
#include "csr_sched.h"
#include "csr_msgconv.h"
#include "csr_macro.h"

static CsrMsgConvEntry *converter;

CsrMsgConvPrimEntry *CsrMsgConvFind(u16 primType)
{
    CsrMsgConvPrimEntry *ptr = NULL;

    if (converter)
    {
        ptr = converter->profile_converters;
        while (ptr)
        {
            if (ptr->primType == primType)
            {
                break;
            }
            else
            {
                ptr = ptr->next;
            }
        }
    }

    return ptr;
}

static const CsrMsgConvMsgEntry *find_msg_converter(CsrMsgConvPrimEntry *ptr, u16 msgType)
{
    const CsrMsgConvMsgEntry *cv = ptr->conv;
    if (ptr->lookupFunc)
    {
        return (const CsrMsgConvMsgEntry *) ptr->lookupFunc((CsrMsgConvMsgEntry *) cv, msgType);
    }

    while (cv)
    {
        if (cv->serFunc == NULL)
        {
            /* We've reached the end of the chain */
            cv = NULL;
            break;
        }

        if (cv->msgType == msgType)
        {
            break;
        }
        else
        {
            cv++;
        }
    }

    return cv;
}

static void *deserialize_data(u16 primType,
    size_t length,
    u8 *data)
{
    CsrMsgConvPrimEntry *ptr;
    u8 *ret;

    ptr = CsrMsgConvFind(primType);

    if (ptr)
    {
        const CsrMsgConvMsgEntry *cv;
        u16 msgId = 0;
        size_t offset = 0;
        CsrUint16Des(&msgId, data, &offset);

        cv = find_msg_converter(ptr, msgId);
        if (cv)
        {
            ret = cv->deserFunc(data, length);
        }
        else
        {
            ret = NULL;
        }
    }
    else
    {
        ret = NULL;
    }

    return ret;
}

static size_t sizeof_message(u16 primType, void *msg)
{
    CsrMsgConvPrimEntry *ptr = CsrMsgConvFind(primType);
    size_t ret;

    if (ptr)
    {
        const CsrMsgConvMsgEntry *cv;
        u16 msgId = *(u16 *) msg;

        cv = find_msg_converter(ptr, msgId);
        if (cv)
        {
            ret = cv->sizeofFunc(msg);
        }
        else
        {
            ret = 0;
        }
    }
    else
    {
        ret = 0;
    }

    return ret;
}

static u8 free_message(u16 primType, u8 *data)
{
    CsrMsgConvPrimEntry *ptr;
    u8 ret;

    ptr = CsrMsgConvFind(primType);

    if (ptr)
    {
        const CsrMsgConvMsgEntry *cv;
        u16 msgId = *(u16 *) data;

        cv = find_msg_converter(ptr, msgId);
        if (cv)
        {
            cv->freeFunc(data);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }
    }
    else
    {
        ret = FALSE;
    }

    return ret;
}

static u8 *serialize_message(u16 primType,
    void *msg,
    size_t *length,
    u8 *buffer)
{
    CsrMsgConvPrimEntry *ptr;
    u8 *ret;

    ptr = CsrMsgConvFind(primType);

    *length = 0;

    if (ptr)
    {
        const CsrMsgConvMsgEntry *cv;

        cv = find_msg_converter(ptr, *(u16 *) msg);
        if (cv)
        {
            ret = cv->serFunc(buffer, length, msg);
        }
        else
        {
            ret = NULL;
        }
    }
    else
    {
        ret = NULL;
    }

    return ret;
}

size_t CsrMsgConvSizeof(u16 primType, void *msg)
{
    return sizeof_message(primType, msg);
}

u8 *CsrMsgConvSerialize(u8 *buffer, size_t maxBufferOffset, size_t *offset, u16 primType, void *msg)
{
    if (converter)
    {
        size_t serializedLength;
        u8 *bufSerialized;
        u8 *bufOffset = &buffer[*offset];
        bufSerialized = converter->serialize_message(primType, msg, &serializedLength, bufOffset);
        *offset += serializedLength;
        return bufSerialized;
    }
    else
    {
        return NULL;
    }
}

/* Insert profile converter at head of converter list. */
void CsrMsgConvInsert(u16 primType, const CsrMsgConvMsgEntry *ce)
{
    CsrMsgConvPrimEntry *pc;
    pc = CsrMsgConvFind(primType);

    if (pc)
    {
        /* Already registered. Do nothing */
    }
    else
    {
        pc = kmalloc(sizeof(*pc), GFP_KERNEL);
        pc->primType = primType;
        pc->conv = ce;
        pc->lookupFunc = NULL;
        pc->next = converter->profile_converters;
        converter->profile_converters = pc;
    }
}
EXPORT_SYMBOL_GPL(CsrMsgConvInsert);

CsrMsgConvMsgEntry *CsrMsgConvFindEntry(u16 primType, u16 msgType)
{
    CsrMsgConvPrimEntry *ptr = CsrMsgConvFind(primType);
    if (ptr)
    {
        return (CsrMsgConvMsgEntry *) find_msg_converter(ptr, msgType);
    }
    return NULL;
}
EXPORT_SYMBOL_GPL(CsrMsgConvFindEntry);

CsrMsgConvMsgEntry *CsrMsgConvFindEntryByMsg(u16 primType, const void *msg)
{
    CsrMsgConvPrimEntry *ptr = CsrMsgConvFind(primType);
    if (ptr && msg)
    {
        u16 msgType = *((u16 *) msg);
        return (CsrMsgConvMsgEntry *) find_msg_converter(ptr, msgType);
    }
    return NULL;
}

void CsrMsgConvCustomLookupRegister(u16 primType, CsrMsgCustomLookupFunc *lookupFunc)
{
    CsrMsgConvPrimEntry *ptr = CsrMsgConvFind(primType);
    if (ptr)
    {
        ptr->lookupFunc = lookupFunc;
    }
}
EXPORT_SYMBOL_GPL(CsrMsgConvCustomLookupRegister);

CsrMsgConvEntry *CsrMsgConvInit(void)
{
    if (!converter)
    {
        converter = kmalloc(sizeof(CsrMsgConvEntry), GFP_KERNEL);

        converter->profile_converters = NULL;
        converter->free_message = free_message;
        converter->sizeof_message = sizeof_message;
        converter->serialize_message = serialize_message;
        converter->deserialize_data = deserialize_data;
    }

    return converter;
}
EXPORT_SYMBOL_GPL(CsrMsgConvInit);