// ------------------------------------------------------------------------------------------------
#include "Common.hpp"

// ------------------------------------------------------------------------------------------------
#include <cstdio>
#include <cstdlib>
#include <cstring>


// ------------------------------------------------------------------------------------------------
namespace SqMod {

// ------------------------------------------------------------------------------------------------
// N[0] - contains least significant bits, N[3] - most significant
static SQChar * Bin128ToDec(const Uint32 N[4])
{
    // log10(x) = log2(x) / log2(10) ~= log2(x) / 3.322
    static SQChar s[128 / 3 + 1 + 1];
    Uint32 n[4];
    SQChar * p = s;
    int i;

    std::memset(s, '0', sizeof(s) - 1);
    s[sizeof(s) - 1] = '\0';

    std::memcpy(n, N, sizeof(n));

    for (i = 0; i < 128; i++)
    {
        int j, carry;

        carry = (n[3] >= 0x80000000);
        // Shift n[] left, doubling it
        n[3] = ((n[3] << 1) & 0xFFFFFFFF) + (n[2] >= 0x80000000);
        n[2] = ((n[2] << 1) & 0xFFFFFFFF) + (n[1] >= 0x80000000);
        n[1] = ((n[1] << 1) & 0xFFFFFFFF) + (n[0] >= 0x80000000);
        n[0] = ((n[0] << 1) & 0xFFFFFFFF);

        // Add s[] to itself in decimal, doubling it
        for (j = sizeof(s) - 2; j >= 0; j--)
        {
            s[j] += s[j] - '0' + carry;

            carry = (s[j] > '9');

            if (carry)
            {
                s[j] -= 10;
            }
        }
    }

    while ((p[0] == '0') && (p < &s[sizeof(s) - 2]))
    {
        p++;
    }

    return p;
}

// ------------------------------------------------------------------------------------------------
CSStr AsTypeStr(Uint32 id)
{
    switch (id)
    {
        case MMDB_DATA_TYPE_EXTENDED:       return _SC("extended");
        case MMDB_DATA_TYPE_POINTER:        return _SC("pointer");
        case MMDB_DATA_TYPE_UTF8_STRING:    return _SC("string");
        case MMDB_DATA_TYPE_DOUBLE:         return _SC("double");
        case MMDB_DATA_TYPE_BYTES:          return _SC("bytes");
        case MMDB_DATA_TYPE_UINT16:         return _SC("uint16");
        case MMDB_DATA_TYPE_UINT32:         return _SC("uint32");
        case MMDB_DATA_TYPE_MAP:            return _SC("map");
        case MMDB_DATA_TYPE_INT32:          return _SC("int32");
        case MMDB_DATA_TYPE_UINT64:         return _SC("uint64");
        case MMDB_DATA_TYPE_UINT128:        return _SC("uint128");
        case MMDB_DATA_TYPE_ARRAY:          return _SC("array");
        case MMDB_DATA_TYPE_CONTAINER:      return _SC("container");
        case MMDB_DATA_TYPE_END_MARKER:     return _SC("endmarker");
        case MMDB_DATA_TYPE_BOOLEAN:        return _SC("boolean");
        case MMDB_DATA_TYPE_FLOAT:          return _SC("float");
        default:                            return _SC("unknonw");
    }
}

// ------------------------------------------------------------------------------------------------
bool GetEntryAsBool(const MMDB_entry_data_s & ed)
{
    bool value = false;
    // Identify the type of entry data
    switch (ed.type)
    {
        case MMDB_DATA_TYPE_POINTER: {
            value = ed.pointer > 0;
        } break;
        case MMDB_DATA_TYPE_UTF8_STRING: {
            if (ed.data_size > 0)
            {
                value = ConvTo< bool >::From(reinterpret_cast< CSStr >(ed.utf8_string));
            }
        } break;
        case MMDB_DATA_TYPE_DOUBLE: {
            value = ConvTo< bool >::From(ed.double_value);
        } break;
        case MMDB_DATA_TYPE_BYTES: {
            for (Uint32 i = 0; i < ed.data_size; ++i)
            {
                if (ed.bytes[i] != 0)
                {
                    value = true;
                    // Found somethinf that isn't 0
                    break;
                }
            }
        } break;
        case MMDB_DATA_TYPE_UINT16: {
            value = ConvTo< bool >::From(ed.uint16);
        } break;
        case MMDB_DATA_TYPE_UINT32: {
            value = ConvTo< bool >::From(ed.uint16);
        } break;
        case MMDB_DATA_TYPE_INT32: {
            value = ConvTo< bool >::From(ed.uint16);
        } break;
        case MMDB_DATA_TYPE_UINT64: {
            value = ConvTo< bool >::From(ed.uint16);
        } break;
        case MMDB_DATA_TYPE_UINT128: {
#if defined(MMDB_UINT128_IS_BYTE_ARRAY) && (MMDB_UINT128_IS_BYTE_ARRAY == 1)
            for (Uint32 i = 0; i < sizeof(ed.uint128); ++i)
            {
                if (ed.uint128[i] != 0)
                {
                    value = true;
                    // Found somethinf that isn't 0
                    break;
                }
            }
#else
            value = ed.uint128 > 0;
#endif // MMDB_UINT128_IS_BYTE_ARRAY

        } break;
        case MMDB_DATA_TYPE_BOOLEAN: {
            value = ed.boolean ? true : false;
        } break;
        case MMDB_DATA_TYPE_FLOAT: {
            value = ConvTo< bool >::From(ed.float_value);
        } break;
        default:
            STHROWF("Unsupported conversion from (%s) to (boolean)", AsTypeStr(ed.type));
    }
    // Return the extracted value
    return value;
}

// ------------------------------------------------------------------------------------------------
SQInteger GetEntryAsInteger(const MMDB_entry_data_s & ed)
{
    SQInteger value = 0;
    // Identify the type of entry data
    switch (ed.type)
    {
        case MMDB_DATA_TYPE_POINTER: {
            value = static_cast< SQInteger >(ed.pointer);
        } break;
        case MMDB_DATA_TYPE_UTF8_STRING: {
            if (ed.data_size > 0)
            {
                value = ConvTo< SQInteger >::From(reinterpret_cast< CSStr >(ed.utf8_string));
            }
        } break;
        case MMDB_DATA_TYPE_DOUBLE: {
            value = ConvTo< SQInteger >::From(ed.double_value);
        } break;
        case MMDB_DATA_TYPE_BYTES: {
            std::memcpy(&value, ed.bytes, Clamp(ed.data_size, 0U, sizeof(value)));
        } break;
        case MMDB_DATA_TYPE_UINT16: {
            value = ConvTo< SQInteger >::From(ed.uint16);
        } break;
        case MMDB_DATA_TYPE_UINT32: {
            value = ConvTo< SQInteger >::From(ed.uint32);
        } break;
        case MMDB_DATA_TYPE_INT32: {
            value = ConvTo< SQInteger >::From(ed.int32);
        } break;
        case MMDB_DATA_TYPE_UINT64: {
            value = ConvTo< SQInteger >::From(ed.uint64);
        } break;
        case MMDB_DATA_TYPE_UINT128: {
#if defined(MMDB_UINT128_IS_BYTE_ARRAY) && (MMDB_UINT128_IS_BYTE_ARRAY == 1)
            std::memcpy(&value, ed.uint128, sizeof(value));
#else
            std::memcpy(&value, &ed.uint128, sizeof(value));
#endif // MMDB_UINT128_IS_BYTE_ARRAY
        } break;
        case MMDB_DATA_TYPE_BOOLEAN: {
            value = ed.boolean ? 1 : 0;
        } break;
        case MMDB_DATA_TYPE_FLOAT: {
            value = ConvTo< SQInteger >::From(ed.float_value);
        } break;
        default:
            STHROWF("Unsupported conversion from (%s) to (integer)", AsTypeStr(ed.type));
    }
    // Return the extracted value
    return value;
}

// ------------------------------------------------------------------------------------------------
SQFloat GetEntryAsFloat(const MMDB_entry_data_s & ed)
{
    SQFloat value = 0.0;
    // Identify the type of entry data
    switch (ed.type)
    {
        case MMDB_DATA_TYPE_POINTER: {
            value = ConvTo< SQFloat >::From(static_cast< SQInteger >(ed.pointer));
        } break;
        case MMDB_DATA_TYPE_UTF8_STRING: {
            if (ed.data_size > 0)
            {
                value = ConvTo< SQFloat >::From(reinterpret_cast< CSStr >(ed.utf8_string));
            }
        } break;
        case MMDB_DATA_TYPE_DOUBLE: {
            value = ConvTo< SQFloat >::From(ed.double_value);
        } break;
        case MMDB_DATA_TYPE_BYTES: {
            // Not our problem if the result will be junk!
            std::memcpy(&value, ed.bytes, Clamp(ed.data_size, 0U, sizeof(value)));
        } break;
        case MMDB_DATA_TYPE_UINT16: {
            value = ConvTo< SQFloat >::From(ed.uint16);
        } break;
        case MMDB_DATA_TYPE_UINT32: {
            value = ConvTo< SQFloat >::From(ed.uint32);
        } break;
        case MMDB_DATA_TYPE_INT32: {
            value = ConvTo< SQFloat >::From(ed.int32);
        } break;
        case MMDB_DATA_TYPE_UINT64: {
            value = ConvTo< SQFloat >::From(ed.uint64);
        } break;
        case MMDB_DATA_TYPE_UINT128: {
            SQInteger num;
            // Convert to integer first
#if defined(MMDB_UINT128_IS_BYTE_ARRAY) && (MMDB_UINT128_IS_BYTE_ARRAY == 1)
            std::memcpy(&num, ed.uint128, sizeof(num));
#else
            std::memcpy(&num, &ed.uint128, sizeof(num));
#endif // MMDB_UINT128_IS_BYTE_ARRAY
            // Now convert to float
            value = ConvTo< SQFloat >::From(num);
        } break;
        case MMDB_DATA_TYPE_BOOLEAN: {
            value = ed.boolean ? 1.0 : 0.0;
        } break;
        case MMDB_DATA_TYPE_FLOAT: {
            value = ConvTo< SQFloat >::From(ed.float_value);
        } break;
        default:
            STHROWF("Unsupported conversion from (%s) to (float)", AsTypeStr(ed.type));
    }
    // Return the extracted value
    return value;
}

// ------------------------------------------------------------------------------------------------
Object GetEntryAsLong(const MMDB_entry_data_s & ed)
{
    Uint64 value = 0;
    // Identify the type of entry data
    switch (ed.type)
    {
        case MMDB_DATA_TYPE_POINTER: {
            value = static_cast< Uint64 >(ed.pointer);
        } break;
        case MMDB_DATA_TYPE_UTF8_STRING: {
            if (ed.data_size > 0)
            {
                value = ConvTo< Uint64 >::From(reinterpret_cast< CSStr >(ed.utf8_string));
            }
        } break;
        case MMDB_DATA_TYPE_DOUBLE: {
            value = ConvTo< Uint64 >::From(ed.double_value);
        } break;
        case MMDB_DATA_TYPE_BYTES: {
            std::memcpy(&value, ed.bytes, Clamp(ed.data_size, 0U, sizeof(value)));
        } break;
        case MMDB_DATA_TYPE_UINT16: {
            value = ConvTo< Uint64 >::From(ed.uint16);
        } break;
        case MMDB_DATA_TYPE_UINT32: {
            value = ConvTo< Uint64 >::From(ed.uint32);
        } break;
        case MMDB_DATA_TYPE_INT32: {
            value = ConvTo< Uint64 >::From(ed.int32);
        } break;
        case MMDB_DATA_TYPE_UINT64: {
            value = ConvTo< Uint64 >::From(ed.uint64);
        } break;
        case MMDB_DATA_TYPE_UINT128: {
#if defined(MMDB_UINT128_IS_BYTE_ARRAY) && (MMDB_UINT128_IS_BYTE_ARRAY == 1)
            std::memcpy(&value, ed.uint128, sizeof(value));
#else
            std::memcpy(&value, &ed.uint128, sizeof(value));
#endif // MMDB_UINT128_IS_BYTE_ARRAY
        } break;
        case MMDB_DATA_TYPE_BOOLEAN: {
            value = ed.boolean ? 1 : 0;
        } break;
        case MMDB_DATA_TYPE_FLOAT: {
            value = ConvTo< Uint64 >::From(ed.float_value);
        } break;
        default:
            STHROWF("Unsupported conversion from (%s) to (long)", AsTypeStr(ed.type));
    }
    // Obtain the initial stack size
    const StackGuard sg;
    // Push a long integer instance with the requested value on the stack
    SqMod_PushULongObject(DefaultVM::Get(), value);
    // Obtain the object from the stack and return it
    return Var< Object >(DefaultVM::Get(), -1).value;
}

// ------------------------------------------------------------------------------------------------
Object GetEntryAsString(const MMDB_entry_data_s & ed)
{
    // Obtain the initial stack size
    const StackGuard sg;
    // The default vm
    HSQUIRRELVM vm = DefaultVM::Get();
    // Identify the type of entry data
    switch (ed.type)
    {
        case MMDB_DATA_TYPE_POINTER: {
            sq_pushstring(vm, ToStrF("%p", ed.pointer), -1);
        } break;
        case MMDB_DATA_TYPE_UTF8_STRING: {
            sq_pushstring(vm, ed.utf8_string, ed.data_size);
        } break;
        case MMDB_DATA_TYPE_DOUBLE: {
            sq_pushstring(vm, ToStrF("%f", ed.double_value), -1);
        } break;
        case MMDB_DATA_TYPE_BYTES: {
            sq_pushstring(vm, reinterpret_cast< CSStr >(ed.bytes), ed.data_size / sizeof(SQChar));
        } break;
        case MMDB_DATA_TYPE_UINT16: {
            sq_pushstring(vm, ToStrF("%u", ed.uint16), -1);
        } break;
        case MMDB_DATA_TYPE_UINT32: {
            sq_pushstring(vm, ToStrF("%u", ed.uint32), -1);
        } break;
        case MMDB_DATA_TYPE_INT32: {
            sq_pushstring(vm, ToStrF("%d", ed.int32), -1);
        } break;
        case MMDB_DATA_TYPE_UINT64: {
            sq_pushstring(vm, ToStrF("%llu", ed.uint64), -1);
        } break;
        case MMDB_DATA_TYPE_UINT128: {
#if defined(MMDB_UINT128_IS_BYTE_ARRAY) && (MMDB_UINT128_IS_BYTE_ARRAY == 1)
            sq_pushstring(vm, Bin128ToDec(reinterpret_cast< const Uint32 * >(ed.uint128)), -1);
#else
            sq_pushstring(vm, Bin128ToDec(reinterpret_cast< const Uint32 * >(&ed.uint128)), -1);
#endif // MMDB_UINT128_IS_BYTE_ARRAY
        } break;
        case MMDB_DATA_TYPE_BOOLEAN: {
            sq_pushstring(vm, ed.boolean ? _SC("true") : _SC("false"), -1);
        } break;
        case MMDB_DATA_TYPE_FLOAT: {
            sq_pushstring(vm, ToStrF("%f", ed.float_value), -1);
        } break;
        default:
            STHROWF("Unsupported conversion from (%s) to (string)", AsTypeStr(ed.type));
    }
    // Obtain the object from the stack and return it
    return Var< Object >(vm, -1).value;
}

// ------------------------------------------------------------------------------------------------
Object GetEntryAsBytes(const MMDB_entry_data_s & ed)
{
    // Obtain the initial stack size
    const StackGuard sg;
    // The result of operations
    SQRESULT res = SQ_OK;
    // The default vm
    HSQUIRRELVM vm = DefaultVM::Get();
    // Identify the type of entry data
    switch (ed.type)
    {
        case MMDB_DATA_TYPE_POINTER: {
            res = SqMod_PushBufferData(vm,
                    reinterpret_cast< const char * >(&ed.pointer), sizeof(ed.pointer), 0);
        } break;
        case MMDB_DATA_TYPE_UTF8_STRING: {
            res = SqMod_PushBufferData(vm,
                    reinterpret_cast< const char * >(ed.utf8_string), ed.data_size, 0);
        } break;
        case MMDB_DATA_TYPE_DOUBLE: {
            res = SqMod_PushBufferData(vm,
                    reinterpret_cast< const char * >(&ed.double_value), sizeof(ed.double_value), 0);
        } break;
        case MMDB_DATA_TYPE_BYTES: {
            res = SqMod_PushBufferData(vm,
                    reinterpret_cast< const char * >(ed.bytes), ed.data_size, 0);
        } break;
        case MMDB_DATA_TYPE_UINT16: {
            res = SqMod_PushBufferData(vm,
                    reinterpret_cast< const char * >(&ed.uint16), sizeof(ed.uint16), 0);
        } break;
        case MMDB_DATA_TYPE_UINT32: {
            res = SqMod_PushBufferData(vm,
                    reinterpret_cast< const char * >(&ed.uint32), sizeof(ed.uint32), 0);
        } break;
        case MMDB_DATA_TYPE_INT32: {
            res = SqMod_PushBufferData(vm,
                    reinterpret_cast< const char * >(&ed.int32), sizeof(ed.int32), 0);
        } break;
        case MMDB_DATA_TYPE_UINT64: {
            res = SqMod_PushBufferData(vm,
                    reinterpret_cast< const char * >(&ed.uint64), sizeof(ed.uint64), 0);
        } break;
        case MMDB_DATA_TYPE_UINT128: {
            res = SqMod_PushBufferData(vm,
                    reinterpret_cast< const char * >(&ed.uint128), sizeof(ed.uint128), 0);
        } break;
        case MMDB_DATA_TYPE_BOOLEAN: {
            res = SqMod_PushBufferData(vm,
                    reinterpret_cast< const char * >(&ed.boolean), sizeof(ed.boolean), 0);
        } break;
        case MMDB_DATA_TYPE_FLOAT: {
            res = SqMod_PushBufferData(vm,
                                 reinterpret_cast< const char * >(&ed.float_value),
                                 sizeof(ed.float_value), 0);
        } break;
        default:
            STHROWF("Unsupported conversion from (%s) to (buffer)", AsTypeStr(ed.type));
    }
    // Did we fail to push the buffer o the stack?
    if (SQ_FAILED(res))
    {
        STHROWF("Failed to convert the (%s) value to a buffer.", AsTypeStr(ed.type));
    }
    // Obtain the object from the stack and return it
    return Var< Object >(vm, -1).value;
}


} // Namespace:: SqMod