/*
  Ypsilon Scheme System
  Copyright (c) 2004-2008 Y.FUJITA / LittleWing Company Limited.
  See license.txt for terms and conditions of use
*/

#include "core.h"
#include "arith.h"
#include "object_heap.h"
#include "object_factory.h"
#include "serialize.h"

#define BVO_TAG_NOUSE               0
#define BVO_TAG_LOOKUP              1
#define BVO_TAG_IMMEDIATE           2
#define BVO_TAG_PLIST               3
#define BVO_TAG_DLIST               4
#define BVO_TAG_VECTOR              5
#define BVO_TAG_RATIONAL            6
#define BVO_TAG_COMPLEX             7
#define BVO_TAG_FLONUM              8
#define BVO_TAG_BIGNUM              9
#define BVO_TAG_BVECTOR             10
#define BVO_TAG_SYMBOL              11
#define BVO_TAG_STRING              12
#define BVO_TAG_UNINTERNED_SYMBOL   13
#define MAX_BUNDLE_SIZE             sizeof(uint64_t)

serializer_t::serializer_t(object_heap_t* heap)
{
    m_heap = heap;
    m_lites = make_hashtable(m_heap, SCM_HASHTABLE_TYPE_EQ, lookup_mutable_hashtable_size(0));
    int depth = 32;
    m_stack = (scm_obj_t*)m_heap->allocate_private(sizeof(scm_obj_t) * depth);
    m_stack_limit = m_stack + depth;
    m_sp = m_stack;
    m_buf_size = 128;
    m_buf_mark = 0;
    m_buf = (uint8_t*)m_heap->allocate_private(m_buf_size);
    m_bad = NULL;
}

serializer_t::~serializer_t()
{
    m_heap->deallocate_private(m_stack);
    m_heap->deallocate_private(m_buf);
}

void
serializer_t::emit_u8(uint8_t octet)
{
    m_buf[m_buf_mark] = octet;
    m_buf_mark++;
    if (m_buf_mark + MAX_BUNDLE_SIZE >= m_buf_size) expand();
}

void
serializer_t::emit_u32(uint32_t n)
{
#if ARCH_IA32 || ARCH_X64
    assert(sizeof(uint32_t) <= MAX_BUNDLE_SIZE);
    *(uint32_t*)(m_buf + m_buf_mark) = n;
    m_buf_mark += sizeof(uint32_t);
    if (m_buf_mark + MAX_BUNDLE_SIZE >= m_buf_size) expand();
#else
    emit_u8((n >> 0) & 0xff);
    emit_u8((n >> 8) & 0xff);
    emit_u8((n >> 16) & 0xff);
    emit_u8((n >> 24) & 0xff);
#endif
}

void
serializer_t::emit_u64(uint64_t n)
{
#if ARCH_IA32 || ARCH_X64
    assert(sizeof(uint64_t) <= MAX_BUNDLE_SIZE);
    *(uint64_t*)(m_buf + m_buf_mark) = n;
    m_buf_mark += sizeof(uint64_t);
    if (m_buf_mark + MAX_BUNDLE_SIZE >= m_buf_size) expand();
#else
    emit_u8((n >> 0) & 0xff);
    emit_u8((n >> 8) & 0xff);
    emit_u8((n >> 16) & 0xff);
    emit_u8((n >> 24) & 0xff);
    emit_u8((n >> 32) & 0xff);
    emit_u8((n >> 40) & 0xff);
    emit_u8((n >> 48) & 0xff);
    emit_u8((n >> 56) & 0xff);
#endif
}

void
serializer_t::emit_bytes(const uint8_t* s, int n)
{
    if (m_buf_mark + MAX_BUNDLE_SIZE + n < m_buf_size) {
        memcpy(m_buf + m_buf_mark, s, n);
        m_buf_mark += n;
    } else {
        for (int i = 0; i < n; i++) emit_u8(s[i]);
    }
}

void
serializer_t::expand()
{
    uint8_t* prev = m_buf;
    int n2 = m_buf_size + m_buf_size;
    m_buf = (uint8_t*)m_heap->allocate_private(n2);
    memcpy(m_buf, prev, m_buf_size);
    m_buf_size = n2;
    m_heap->deallocate_private(prev);
}

void
serializer_t::push(scm_obj_t obj)
{
    if (m_sp == m_stack_limit) {
        scm_obj_t* prev = m_stack;
        int n = m_stack_limit - m_stack;
        int n2 = n + n;
        m_stack = (scm_obj_t*)m_heap->allocate_private(sizeof(scm_obj_t) * n2);
        memcpy(m_stack, prev, sizeof(scm_obj_t) * n);
        m_stack_limit = m_stack + n2;
        m_sp = m_stack + n;
        m_heap->deallocate_private(prev);
    }
    m_sp[0] = obj;
    m_sp++;
}

scm_obj_t
serializer_t::pop()
{
    if (m_sp == m_stack) return NULL;
    m_sp--;
    return m_sp[0];
}

void
serializer_t::scan(scm_obj_t obj)
{

loop:
    if (CELLP(obj)) {
        if (PAIRP(obj)) {
            scan(CAR(obj));
            obj = CDR(obj);
            goto loop;
        }
        if (SYMBOLP(obj) || STRINGP(obj)) {
            if (get_hashtable(m_lites, obj) != scm_undef) return;
            int nsize = put_hashtable(m_lites, obj, MAKEFIXNUM(m_lites->datum->live));
            if (nsize) rehash_hashtable(m_heap, m_lites, nsize);
            return;
        }
        if (VECTORP(obj)) {
            scm_vector_t vector = (scm_vector_t)obj;
            int count = vector->count;
            if (count == 0) return;
            scm_obj_t* elts = vector->elts;
            for (int i = 0; i < count; i++) scan(elts[i]);
            return;
        }
        if (BVECTORP(obj) || FLONUMP(obj) || BIGNUMP(obj) || RATIONALP(obj) || COMPLEXP(obj)) return;
        if (m_bad == NULL) m_bad = obj;
    }
}

void
serializer_t::put_lites()
{
    scm_obj_t* lites = (scm_obj_t*)m_heap->allocate_private(sizeof(scm_obj_t) * m_lites->datum->live);
    try {
        hashtable_rec_t* ht_datum = m_lites->datum;
        int nsize = m_lites->datum->capacity;
        for (int i = 0; i < nsize; i++) {
            scm_obj_t key = ht_datum->elts[i];
            scm_obj_t value = ht_datum->elts[i + nsize];
            if (CELLP(key)) {
                assert(FIXNUM(value) < m_lites->datum->live);
                lites[FIXNUM(value)] = key;
            }
        }
        emit_u32(m_lites->datum->live);
        for (int i = 0; i < m_lites->datum->live; i++) {
            if (SYMBOLP(lites[i])) {
                scm_symbol_t symbol = (scm_symbol_t)lites[i];
                if (UNINTERNEDSYMBOLP(symbol)) {
                    emit_u8(BVO_TAG_UNINTERNED_SYMBOL);
                    emit_u32(i);
                    int n = HDR_SYMBOL_SIZE(symbol->hdr) + 2;
                    emit_u32(n);
                    emit_bytes((uint8_t*)symbol->name, n);
                } else {
                    emit_u8(BVO_TAG_SYMBOL);
                    emit_u32(i);
                    int n = HDR_SYMBOL_SIZE(symbol->hdr);
                    emit_u32(n);
                    emit_bytes((uint8_t*)symbol->name, n);
                }
                continue;
            }
            if (STRINGP(lites[i])) {
                scm_string_t string = (scm_string_t)lites[i];
                emit_u8(BVO_TAG_STRING);
                emit_u32(i);
                int n = string->size;
                emit_u32(n);
                emit_bytes((uint8_t*)string->name, n);
                continue;
            }
        }
    } catch (...) {
        m_heap->deallocate_private(lites);
        throw;
    }
    m_heap->deallocate_private(lites);
}

void
serializer_t::put_list(scm_obj_t obj)
{
    int count = 0;
    while (PAIRP(obj)) {
        push(CAR(obj));
        obj = CDR(obj);
        count++;
    }
    if (obj == scm_nil) {
        emit_u8(BVO_TAG_PLIST);
        emit_u32(count);
    } else {
        emit_u8(BVO_TAG_DLIST);
        emit_u32(count);
        put_datum(obj);
    }
    while (count--) {
        obj = pop();
        assert(obj);
        put_datum(obj);
    }
}

void
serializer_t::put_datum(scm_obj_t obj)
{
    if (!CELLP(obj)) {
        emit_u8(BVO_TAG_IMMEDIATE);
#if ARCH_LP64
        emit_u64((uint64_t)obj);
#else
        emit_u32((uint32_t)obj);
#endif
        return;
    }
    if (PAIRP(obj)) {
        put_list(obj);
        return;
    }
    if (SYMBOLP(obj) || STRINGP(obj)) {
        scm_obj_t id = get_hashtable(m_lites, obj);
        emit_u8(BVO_TAG_LOOKUP);
        emit_u32((uint32_t)FIXNUM(id));
        return;
    }
    if (VECTORP(obj)) {
        scm_vector_t vector = (scm_vector_t)obj;
        int count = vector->count;
        emit_u8(BVO_TAG_VECTOR);
        emit_u32(count);
        scm_obj_t* elts = vector->elts;
        for (int i = 0; i < count; i++) put_datum(elts[i]);
        return;
    }
    if (BVECTORP(obj)) {
        scm_bvector_t bv = (scm_bvector_t)obj;
        int count = bv->count;
        emit_u8(BVO_TAG_BVECTOR);
        emit_u32(count);
        emit_bytes(bv->elts, count);
        return;
    }
    if (FLONUMP(obj)) {
        union {
            double      f64;
            uint64_t    u64;
        } n;
        scm_flonum_t flonum = (scm_flonum_t)obj;
        n.f64 = flonum->value;
        emit_u8(BVO_TAG_FLONUM);
        emit_u64(n.u64);
        return;
    }
    if (BIGNUMP(obj)) {
        scm_bignum_t bn = (scm_bignum_t)obj;
        assert(sizeof(bn->elts[0]) == sizeof(uint32_t));
        int sign = bn_get_sign(bn); // 0 or 1 or -1
        int count = bn_get_count(bn);
        emit_u8(BVO_TAG_BIGNUM);
        emit_u32(sign);
        emit_u32(count);
#if USE_DIGIT32
        for (int i = 0; i < count; i++) emit_u32(bn->elts[i]);
#else
        for (int i = 0; i < count; i++) emit_u64(bn->elts[i]);
#endif
        return;
    }
    if (RATIONALP(obj)) {
        scm_rational_t rat = (scm_rational_t)obj;
        emit_u8(BVO_TAG_RATIONAL);
        put_datum(rat->nume);
        put_datum(rat->deno);
        return;
    }
    if (COMPLEXP(obj)) {
        scm_complex_t comp = (scm_complex_t)obj;
        emit_u8(BVO_TAG_COMPLEX);
        put_datum(comp->real);
        put_datum(comp->imag);
        return;
    }
    fatal("%s:%u internal error: datum not supported in serialized object", __FILE__, __LINE__);
}

scm_obj_t
serializer_t::translate(scm_obj_t obj)
{
    scoped_lock lock(m_lites->lock);
    scan(obj);
    if (m_bad != NULL) return m_bad;
#if ARCH_LP64
    emit_u8(64);
#else
    emit_u8(32);
#endif
    put_lites();
    put_datum(obj);
    scm_bvector_t bytes = make_bvector(m_heap, m_buf_mark);
    memcpy(bytes->elts, m_buf, m_buf_mark);
    return bytes;
}

deserializer_t::deserializer_t(object_heap_t* heap)
{
    m_heap = heap;
    m_lites = NULL;
}

deserializer_t::~deserializer_t()
{
    if (m_lites) m_heap->deallocate_private(m_lites);
}

uint8_t
deserializer_t::fetch_u8()
{
    return *m_buf++;
}

void
deserializer_t::fetch_bytes(uint8_t* p, int n)
{
    memcpy(p, m_buf, n);
    m_buf += n;
    if (m_buf > m_buf_tail) throw true;
}

uint32_t
deserializer_t::fetch_u32()
{
#if ARCH_IA32 || ARCH_X64
    uint32_t value = *(uint32_t*)m_buf;
    m_buf += sizeof(uint32_t);
    if (m_buf > m_buf_tail) throw true;
    return value;
#else
    uint32_t value = 0;
    value += ((uint32_t)fetch_u8() << 0);
    value += ((uint32_t)fetch_u8() << 8);
    value += ((uint32_t)fetch_u8() << 16);
    value += ((uint32_t)fetch_u8() << 24);
    if (m_buf > m_buf_tail) throw true;
    return value;
#endif
}

uint64_t
deserializer_t::fetch_u64()
{
#if ARCH_IA32 || ARCH_X64
    uint64_t value = *(uint64_t*)m_buf;
    m_buf += sizeof(uint64_t);
    if (m_buf > m_buf_tail) throw true;
    return value;
#else
    uint64_t value = 0;
    value += ((uint64_t)fetch_u8() << 0);
    value += ((uint64_t)fetch_u8() << 8);
    value += ((uint64_t)fetch_u8() << 16);
    value += ((uint64_t)fetch_u8() << 24);
    value += ((uint64_t)fetch_u8() << 32);
    value += ((uint64_t)fetch_u8() << 40);
    value += ((uint64_t)fetch_u8() << 48);
    value += ((uint64_t)fetch_u8() << 56);
    if (m_buf > m_buf_tail) throw true;
    return value;
#endif
}

scm_obj_t
deserializer_t::get_datum()
{
    uint8_t octet = fetch_u8();
    switch (octet) {
        case BVO_TAG_LOOKUP: {
            uint32_t uid = fetch_u32();
            return m_lites[uid];
        }
        case BVO_TAG_IMMEDIATE: {
#if ARCH_LP64
            return (scm_obj_t)fetch_u64();
#else
            return (scm_obj_t)fetch_u32();
#endif
        }
        case BVO_TAG_PLIST: {
            int count = fetch_u32();
            scm_obj_t lst = scm_nil;
            for (int i = 0; i < count; i++) {
                lst = make_pair(m_heap, get_datum(), lst);
            }
            return lst;
        }
        case BVO_TAG_DLIST: {
            int count = fetch_u32();
            scm_obj_t lst = get_datum();
            for (int i = 0; i < count; i++) {
                lst = make_pair(m_heap, get_datum(), lst);
            }
            return lst;
        }
        case BVO_TAG_VECTOR: {
            int count = fetch_u32();
            scm_vector_t vector = make_vector(m_heap, count, scm_unspecified);
            scm_obj_t* elts = vector->elts;
            for (int i = 0; i < count; i++) elts[i] = get_datum();
            return vector;
        }
        case BVO_TAG_RATIONAL: {
            scm_obj_t nume = get_datum();
            scm_obj_t deno = get_datum();
            return make_rational(m_heap, nume, deno);
        }
        case BVO_TAG_COMPLEX: {
            scm_obj_t real = get_datum();
            scm_obj_t imag = get_datum();
            return make_complex(m_heap, real, imag);
        }
        case BVO_TAG_FLONUM: {
            union { double f64; uint64_t u64; } n;
            n.u64 = fetch_u64();
            return make_flonum(m_heap, n.f64);
        }
        case BVO_TAG_BIGNUM: {
            int sign = (int32_t)fetch_u32();
            int count = fetch_u32();
            scm_bignum_t bn = make_bignum(m_heap, count);
            bn_set_sign(bn, sign);
#if USE_DIGIT32
            for (int i = 0; i < count; i++) bn->elts[i] = fetch_u32();
#else
            for (int i = 0; i < count; i++) bn->elts[i] = fetch_u64();
#endif
            return bn;
        }
        case BVO_TAG_BVECTOR: {
            int count = fetch_u32();
            scm_bvector_t bv = make_bvector(m_heap, count);
            fetch_bytes(bv->elts, count);
            return bv;
        }
    }
    throw true;
}

void
deserializer_t::get_lites()
{
    int buflen = 128;
    char* buf = (char*)m_heap->allocate_private(buflen + 1);
    int count = fetch_u32();
    if (count < 0) throw true;
    m_lites = (scm_obj_t*)m_heap->allocate_private(sizeof(scm_obj_t) * count);
    for (int i = 0; i < count; i++) {
        uint8_t tag = fetch_u8();
        uint32_t uid = fetch_u32();
        uint32_t len = fetch_u32();
        if (uid > count) throw true;
        if (m_buf + len > m_buf_tail) throw true;
        if (len > buflen) {
            m_heap->deallocate_private(buf);
            buf = (char*)m_heap->allocate_private(len + 1);
            buflen = len;
        }
        fetch_bytes((uint8_t*)buf, len);
        buf[len] = 0;
        switch (tag) {
            case BVO_TAG_SYMBOL:
                m_lites[uid] = make_symbol(m_heap, buf, len);
                break;
            case BVO_TAG_UNINTERNED_SYMBOL:
                m_lites[uid] = make_symbol_uninterned(m_heap, buf, len - 2, buf[len - 1]);
                break;
            case BVO_TAG_STRING:
                m_lites[uid] = make_string_literal(m_heap, buf, len);
                break;
            default:
                m_heap->deallocate_private(buf);
                throw true;
        }
    }
    m_heap->deallocate_private(buf);
}

scm_obj_t
deserializer_t::translate(scm_bvector_t obj)
{
    try {
        m_buf = obj->elts;
        m_buf_tail = m_buf + obj->count;
        if (m_buf == m_buf_tail) return NULL;
#if ARCH_LP64
        if (fetch_u8() != 64) return NULL;
#else
        if (fetch_u8() != 32) return NULL;
#endif
        get_lites();
        scm_obj_t obj = get_datum();
        if (m_buf != m_buf_tail) return NULL;
        return obj;
    } catch (...) {
        return NULL;
    }
}
