/* 
 * Copyright 2008-2018 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
#include <aerospike/as_msgpack.h>
#include <aerospike/as_msgpack_serializer.h>
#include <aerospike/as_serializer.h>
#include <aerospike/as_types.h>
#include <citrusleaf/alloc.h>
#include <limits.h>
#include <string.h>

/******************************************************************************
 * STATIC FUNCTIONS
 *****************************************************************************/

static void as_msgpack_serializer_destroy(as_serializer *);
static int as_msgpack_serializer_serialize(as_serializer *, const as_val *,
                                           as_buffer *);
static int32_t as_msgpack_serializer_serialize_presized(as_serializer *,
                                                        const as_val *,
                                                        uint8_t *);
static int as_msgpack_serializer_deserialize(as_serializer *, as_buffer *,
                                             as_val **);
static uint32_t as_msgpack_serializer_serialize_getsize(as_serializer *,
                                                        const as_val *);

/******************************************************************************
 * VARIABLES
 *****************************************************************************/

static const as_serializer_hooks as_msgpack_serializer_hooks = {
    .destroy = as_msgpack_serializer_destroy,
    .serialize = as_msgpack_serializer_serialize,
    .serialize_presized = as_msgpack_serializer_serialize_presized,
    .deserialize = as_msgpack_serializer_deserialize,
    .serialize_getsize = as_msgpack_serializer_serialize_getsize,
};

/******************************************************************************
 * FUNCTIONS
 *****************************************************************************/

as_serializer *as_msgpack_new()
{
    return as_serializer_new(&as_msgpack_serializer_hooks);
}

as_serializer *as_msgpack_init(as_serializer *s)
{
    as_serializer_init(s, &as_msgpack_serializer_hooks);
    return s;
}

/******************************************************************************
 * STATIC FUNCTIONS
 *****************************************************************************/

static void as_msgpack_serializer_destroy(as_serializer *s) { return; }

static uint32_t as_msgpack_serializer_serialize_getsize(as_serializer *s,
                                                        const as_val *v)
{
    as_packer packer = {
        .buffer = NULL, // no buffer means the request is for size
        .capacity = 0,
        .offset = 0,
        .head = 0,
        .tail = 0,
    };

    if (as_pack_val(&packer, v) != 0) {
        return 0;
    }

    return packer.offset;
}

static int32_t as_msgpack_serializer_serialize_presized(as_serializer *s,
                                                        const as_val *v,
                                                        uint8_t *buf)
{
    as_packer packer = {
        .buffer = buf,
        // Prevent extra allocation.
        // buf should contain (pre-sized) space for the unpacking.
        .capacity = INT_MAX,
        .offset = 0,
        .head = 0,
        .tail = 0,
    };

    if (as_pack_val(&packer, v) != 0) {
        return -1;
    }

    return packer.offset;
}

static int as_msgpack_serializer_serialize(as_serializer *s, const as_val *v,
                                           as_buffer *buff)
{
    as_packer packer = {
        .buffer = (unsigned char *)cf_malloc(AS_PACKER_BUFFER_SIZE),
        .capacity = AS_PACKER_BUFFER_SIZE,
        .offset = 0,
        .head = 0,
        .tail = 0,
    };

    if (!packer.buffer) {
        return 1;
    }

    int rc = as_pack_val(&packer, v);

    if (rc != 0) {
        // Cleanup buffers on error.
        as_packer_buffer *p = packer.head;
        as_packer_buffer *tmp;

        while (p) {
            tmp = p;
            p = p->next;

            // Free buffer entry.
            cf_free(tmp->buffer);
            cf_free(tmp);
        }

        // Free main buffer.
        cf_free(packer.buffer);
        return rc;
    }

    if (packer.head) {
        // Combine buffers into a single contiguous buffer.
        as_packer_buffer *p = packer.head;
        int size = packer.offset;

        while (p) {
            size += p->length;
            p = p->next;
        }

        unsigned char *target = (unsigned char *)cf_malloc(size);
        p = packer.head;
        int offset = 0;

        while (p) {
            memcpy(target + offset, p->buffer, p->length);
            offset += p->length;

            as_packer_buffer *tmp = p;
            p = p->next;

            // Free original buffer entry.
            cf_free(tmp->buffer);
            cf_free(tmp);
        }

        memcpy(target + offset, packer.buffer, packer.offset);

        // Free original main buffer.
        cf_free(packer.buffer);

        // Transfer new buffer.
        buff->data = target;
        buff->size = size;
        buff->capacity = size;
    }
    else {
        // Use existing buffer directly.
        buff->data = packer.buffer;
        buff->size = packer.offset;
        buff->capacity = packer.capacity;
    }
    return 0;
}

static int as_msgpack_serializer_deserialize(as_serializer *s, as_buffer *buff,
                                             as_val **v)
{
    as_unpacker unpacker = {
        .buffer = buff->data,
        .length = buff->size,
        .offset = 0,
    };

    return as_unpack_val(&unpacker, v);
}
