/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 <recordio.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#ifndef WIN32
#include <netinet/in.h>
#else
#include <winsock2.h> /* for _htonl and _ntohl */
#endif

void deallocate_String(char **s)
{
    if (*s)
        free(*s);
    *s = 0;
}

void deallocate_Buffer(struct buffer *b)
{
    if (b->buff)
        free(b->buff);
    b->buff = 0;
}

struct buff_struct {
    int32_t len;
    int32_t off;
    char *buffer;
};

static int resize_buffer(struct buff_struct *s, int newlen)
{
    char *buffer= NULL;
    while (s->len < newlen) {
        s->len *= 2;
    }
    buffer = (char*)realloc(s->buffer, s->len);
    if (!buffer) {
        s->buffer = 0;
        return -ENOMEM;
    }
    s->buffer = buffer;
    return 0;
}

int oa_start_record(struct oarchive *oa, const char *tag)
{
    return 0;
}
int oa_end_record(struct oarchive *oa, const char *tag)
{
    return 0;
}
int oa_serialize_int(struct oarchive *oa, const char *tag, const int32_t *d)
{
    struct buff_struct *priv = oa->priv;
    int32_t i = htonl(*d);
    if ((priv->len - priv->off) < sizeof(i)) {
        int rc = resize_buffer(priv, priv->len + sizeof(i));
        if (rc < 0) return rc;
    }
    memcpy(priv->buffer+priv->off, &i, sizeof(i));
    priv->off+=sizeof(i);
    return 0;
}
int64_t zoo_htonll(int64_t v)
{
    int i = 0;
    char *s = (char *)&v;
    if (htonl(1) == 1) {
        return v;
    }
    for (i = 0; i < 4; i++) {
        int tmp = s[i];
        s[i] = s[8-i-1];
        s[8-i-1] = tmp;
    }

    return v;
}

int oa_serialize_long(struct oarchive *oa, const char *tag, const int64_t *d)
{
    const int64_t i = zoo_htonll(*d);
    struct buff_struct *priv = oa->priv;
    if ((priv->len - priv->off) < sizeof(i)) {
        int rc = resize_buffer(priv, priv->len + sizeof(i));
        if (rc < 0) return rc;
    }
    memcpy(priv->buffer+priv->off, &i, sizeof(i));
    priv->off+=sizeof(i);
    return 0;
}
int oa_start_vector(struct oarchive *oa, const char *tag, const int32_t *count)
{
    return oa_serialize_int(oa, tag, count);
}
int oa_end_vector(struct oarchive *oa, const char *tag)
{
    return 0;
}
int oa_serialize_bool(struct oarchive *oa, const char *name, const int32_t *i)
{
    //return oa_serialize_int(oa, name, i);
    struct buff_struct *priv = oa->priv;
    if ((priv->len - priv->off) < 1) {
        int rc = resize_buffer(priv, priv->len + 1);
        if (rc < 0)
            return rc;
    }
    priv->buffer[priv->off] = (*i == 0 ? '\0' : '\1');
    priv->off++;
    return 0;
}
static const int32_t negone = -1;
int oa_serialize_buffer(struct oarchive *oa, const char *name,
        const struct buffer *b)
{
    struct buff_struct *priv = oa->priv;
    int rc;
    if (!b) {
        return oa_serialize_int(oa, "len", &negone);
    }
    rc = oa_serialize_int(oa, "len", &b->len);
    if (rc < 0)
        return rc;
    // this means a buffer of NUll 
    // with size of -1. This is 
    // waht we use in java serialization for NULL
    if (b->len == -1) {
      return rc;
    }
    if ((priv->len - priv->off) < b->len) {
        rc = resize_buffer(priv, priv->len + b->len);
        if (rc < 0)
            return rc;
    }
    memcpy(priv->buffer+priv->off, b->buff, b->len);
    priv->off += b->len;
    return 0;
}
int oa_serialize_string(struct oarchive *oa, const char *name, char **s)
{
    struct buff_struct *priv = oa->priv;
    int32_t len;
    int rc;
    if (!*s) {
        oa_serialize_int(oa, "len", &negone);
        return 0;
    }
    len = strlen(*s);
    rc = oa_serialize_int(oa, "len", &len);
    if (rc < 0)
        return rc;
    if ((priv->len - priv->off) < len) {
        rc = resize_buffer(priv, priv->len + len);
        if (rc < 0)
            return rc;
    }
    memcpy(priv->buffer+priv->off, *s, len);
    priv->off += len;
    return 0;
}
int ia_start_record(struct iarchive *ia, const char *tag)
{
    return 0;
}
int ia_end_record(struct iarchive *ia, const char *tag)
{
    return 0;
}
int ia_deserialize_int(struct iarchive *ia, const char *tag, int32_t *count)
{
    struct buff_struct *priv = ia->priv;
    if ((priv->len - priv->off) < sizeof(*count)) {
        return -E2BIG;
    }
    memcpy(count, priv->buffer+priv->off, sizeof(*count));
    priv->off+=sizeof(*count);
    *count = ntohl(*count);
    return 0;
}

int ia_deserialize_long(struct iarchive *ia, const char *tag, int64_t *count)
{
    struct buff_struct *priv = ia->priv;
    int64_t v = 0;
    if ((priv->len - priv->off) < sizeof(*count)) {
        return -E2BIG;
    }
    memcpy(count, priv->buffer+priv->off, sizeof(*count));
    priv->off+=sizeof(*count);
    v = zoo_htonll(*count); // htonll and  ntohll do the same
    *count = v;
    return 0;
}
int ia_start_vector(struct iarchive *ia, const char *tag, int32_t *count)
{
    return ia_deserialize_int(ia, tag, count);
}
int ia_end_vector(struct iarchive *ia, const char *tag)
{
    return 0;
}
int ia_deserialize_bool(struct iarchive *ia, const char *name, int32_t *v)
{
    struct buff_struct *priv = ia->priv;
    //fprintf(stderr, "Deserializing bool %d\n", priv->off);
    //return ia_deserialize_int(ia, name, v);
    if ((priv->len - priv->off) < 1) {
        return -E2BIG;
    }
    *v = priv->buffer[priv->off];
    priv->off+=1;
    //fprintf(stderr, "Deserializing bool end %d\n", priv->off);
    return 0;
}
int ia_deserialize_buffer(struct iarchive *ia, const char *name,
        struct buffer *b)
{
    struct buff_struct *priv = ia->priv;
    int rc = ia_deserialize_int(ia, "len", &b->len);
    if (rc < 0)
        return rc;
    if ((priv->len - priv->off) < b->len) {
        return -E2BIG;
    }
    // set the buffer to null
    if (b->len == -1) {
       b->buff = NULL;
       return rc;
    }
    b->buff = malloc(b->len);
    if (!b->buff) {
        return -ENOMEM;
    }
    memcpy(b->buff, priv->buffer+priv->off, b->len);
    priv->off += b->len;
    return 0;
}
int ia_deserialize_string(struct iarchive *ia, const char *name, char **s)
{
    struct buff_struct *priv = ia->priv;
    int32_t len;
    int rc = ia_deserialize_int(ia, "len", &len);
    if (rc < 0)
        return rc;
    if ((priv->len - priv->off) < len) {
        return -E2BIG;
    }
    if (len < 0) {
        return -EINVAL;
    }
    *s = malloc(len+1);
    if (!*s) {
        return -ENOMEM;
    }
    memcpy(*s, priv->buffer+priv->off, len);
    (*s)[len] = '\0';
    priv->off += len;
    return 0;
}

static struct iarchive ia_default = {
        ia_start_record,
        ia_end_record,
        ia_start_vector,
        ia_end_vector,
        ia_deserialize_bool,
        ia_deserialize_int,
        ia_deserialize_long ,
        ia_deserialize_buffer,
        ia_deserialize_string};

static struct oarchive oa_default = {
        oa_start_record,
        oa_end_record,
        oa_start_vector,
        oa_end_vector,
        oa_serialize_bool,
        oa_serialize_int,
        oa_serialize_long ,
        oa_serialize_buffer,
        oa_serialize_string};

struct iarchive *create_buffer_iarchive(char *buffer, int len)
{
    struct iarchive *ia;
    struct buff_struct *buff;
    ia = malloc(sizeof(*ia));
    if (!ia) return 0;
    buff = malloc(sizeof(struct buff_struct));
    if (!buff) {
        free(ia);
        return 0;
    }
    *ia = ia_default;
    buff->off = 0;
    buff->buffer = buffer;
    buff->len = len;
    ia->priv = buff;
    return ia;
}

struct oarchive *create_buffer_oarchive()
{
    struct oarchive *oa;
    struct buff_struct *buff;
    oa = malloc(sizeof(*oa));
    if (!oa) return 0;
    buff = malloc(sizeof(struct buff_struct));
    if (!buff) {
        free(oa);
        return 0;
    }
    *oa = oa_default;
    buff->off = 0;
    buff->buffer = malloc(128);
    buff->len = 128;
    oa->priv = buff;
    return oa;
}

void close_buffer_iarchive(struct iarchive **ia)
{
    free((*ia)->priv);
    free(*ia);
    *ia = 0;
}

void close_buffer_oarchive(struct oarchive **oa, int free_buffer)
{
    if (free_buffer) {
        struct buff_struct *buff = (struct buff_struct *)(*oa)->priv;
        if (buff->buffer) {
            free(buff->buffer);
        }
    }
    free((*oa)->priv);
    free(*oa);
    *oa = 0;
}

char *get_buffer(struct oarchive *oa)
{
    struct buff_struct *buff = oa->priv;
    return buff->buffer;
}
int get_buffer_len(struct oarchive *oa)
{
    struct buff_struct *buff = oa->priv;
    return buff->off;
}
