#include "node-lmdbx.h"
#include <string.h>
#include <stdio.h>

/*
control character types:
1 - metadata
2 - symbols
6 - false
7 - true
8- 16 - negative doubles
16-24 positive doubles
27 - String starts with a character 27 or less or is an empty string
0 - multipart separator
> 27 normal string characters
*/
/*
* Convert arbitrary scalar values to buffer bytes with type preservation and type-appropriate ordering
*/

size_t valueToKey(const Local<Value> &jsKey, uint8_t* targetBytes, size_t remainingBytes, bool inArray, bool throwErrors) {
    size_t bytesWritten;
    if (jsKey->IsString()) {
        int utfWritten = 0;
        Local<String> string = Local<String>::Cast(jsKey);
        #if NODE_VERSION_AT_LEAST(11,0,0)
        bytesWritten = string->WriteUtf8(Isolate::GetCurrent(), (char*) targetBytes, remainingBytes, &utfWritten, v8::String::WriteOptions::NO_NULL_TERMINATION);
        #else
        bytesWritten = string->WriteUtf8((char*) targetBytes, remainingBytes, &utfWritten, v8::String::WriteOptions::NO_NULL_TERMINATION);
        #endif
        if (utfWritten < string->Length()) {
            if (throwErrors)
                Nan::ThrowError("String is too long to fit in a key with a maximum of 511 bytes");
            return 0;
        }
        if (bytesWritten == 0 || targetBytes[0] < 28) {
            // use string/escape indicator starting byte
            if (remainingBytes == 0) {
                if (throwErrors)
                    Nan::ThrowError("String is too long to fit in a key with a maximum of 511 bytes");
                return 0;
            }
            memmove(targetBytes + 1, targetBytes, bytesWritten++);
            targetBytes[0] = 27;
        }
        return bytesWritten;
    }

    if (jsKey->IsNumber()
#if NODE_VERSION_AT_LEAST(12,0,0)
        || jsKey->IsBigInt()
#endif
        ) {
        double number;
        if (jsKey->IsNumber())
            number = Local<Number>::Cast(jsKey)->Value();
#if NODE_VERSION_AT_LEAST(12,0,0)
        else {
            bool lossless = true;
            number = (double) Local<BigInt>::Cast(jsKey)->Int64Value(&lossless);
            if (!lossless) {
                if (throwErrors)
                    Nan::ThrowError("BigInt was too large to use as a key.");
                return 0;
            }
        }
#endif
        uint64_t asInt = *((uint64_t*) &number);
        if (number < 0) {
            asInt = asInt ^ 0x7fffffffffffffff;
            targetBytes[0] = (uint8_t) (asInt >> 60);
        } else {
            targetBytes[0] = (uint8_t) (asInt >> 60) | 0x10;
        }
        // TODO: Use byte_swap to do this faster
        targetBytes[1] = (uint8_t) (asInt >> 52) & 0xff;
        targetBytes[2] = (uint8_t) (asInt >> 44) & 0xff;
        targetBytes[3] = (uint8_t) (asInt >> 36) & 0xff;
        targetBytes[4] = (uint8_t) (asInt >> 28) & 0xff;
        targetBytes[5] = (uint8_t) (asInt >> 20) & 0xff;
        targetBytes[6] = (uint8_t) (asInt >> 12) & 0xff;
        targetBytes[7] = (uint8_t) (asInt >> 4) & 0xff;
        targetBytes[8] = (uint8_t) (asInt << 4) & 0xff;
        if (targetBytes[8] == 0 && !inArray) {
            if (targetBytes[6] == 0 && targetBytes[7] == 0) {
                if (targetBytes[5] == 0 && targetBytes[4] == 0)
                        return 4;
                else
                    return 6;
            } else
                return 8;
        } else
            return 9;
       //fprintf(stdout, "asInt %x %x %x %x %x %x %x %x %x\n", targetBytes[0], targetBytes[1], targetBytes[2], targetBytes[3], targetBytes[4], targetBytes[5], targetBytes[6], targetBytes[7], targetBytes[8]);
    } else if (jsKey->IsArray()) {
        Local<Array> array = Local<Array>::Cast(jsKey);
        int length = array->Length();
        Local<Context> context = Nan::GetCurrentContext();
        bytesWritten = 0;
        for (int i = 0; i < length; i++) {
            if (i > 0) {
                if (remainingBytes <= 10) {
                    if (throwErrors)
                        Nan::ThrowError("Array is too large to fit in a key with a maximum of 511 bytes");
                    return 0;
                }
                *targetBytes = 0;
                targetBytes++;
                bytesWritten++;
                remainingBytes--;
            }
            size_t size = valueToKey(array->Get(context, i).ToLocalChecked(), targetBytes, remainingBytes, true, throwErrors);
            if (!size)
                return 0;
            targetBytes += size;
            bytesWritten += size;
            remainingBytes -= size;
        }
        return bytesWritten;
    } else if (jsKey->IsNull()) {
        targetBytes[0] = 0;
        return 1;
    } else if (jsKey->IsBoolean()) {
        targetBytes[0] = jsKey->IsTrue() ? 7 : 6;
        return 1;
    } else if (jsKey->IsArrayBufferView()) {
        bytesWritten = Local<ArrayBufferView>::Cast(jsKey)->CopyContents(targetBytes, remainingBytes);
        if (bytesWritten > remainingBytes - 10 && // guard the second check with this first check to see if we are close to the end
                Local<ArrayBufferView>::Cast(jsKey)->ByteLength() > bytesWritten) {
            if (throwErrors)
                Nan::ThrowError("Buffer is too long to fit in a key with a maximum of 511 bytes");
            return 0; // not enough space
        }
        return bytesWritten;
    } else if (jsKey->IsSymbol()) {
        int utfWritten;
#if NODE_VERSION_AT_LEAST(14,0,0)
        Local<String> string = Local<String>::Cast(Local<Symbol>::Cast(jsKey)->Description());
#else
        Local<String> string = Local<String>::Cast(Local<Symbol>::Cast(jsKey)->Name());
#endif

        targetBytes[0] = 2;
#if NODE_VERSION_AT_LEAST(11,0,0)
        bytesWritten = string->WriteUtf8(Isolate::GetCurrent(), (char*) targetBytes + 1, remainingBytes - 1, &utfWritten, v8::String::WriteOptions::NO_NULL_TERMINATION) + 1;
#else
        bytesWritten = string->WriteUtf8((char*) targetBytes + 1, remainingBytes - 1, &utfWritten, v8::String::WriteOptions::NO_NULL_TERMINATION) + 1;
#endif
        if (utfWritten < string->Length()) {
            Nan::ThrowError("Symbol name is too long to fit in a key with a maximum of 511 bytes");
            return 0;
        }
        return bytesWritten;
    } else {
        if (throwErrors)
            Nan::ThrowError("Invalid type for key.");
        return 0;
    }
}

bool valueToMDBXKey(const Local<Value>& jsKey, MDBX_val& mdbxKey, KeySpace& keySpace) {
    if (jsKey->IsArrayBufferView()) {
        // special case where we can directly use this
        mdbxKey.iov_base = node::Buffer::Data(jsKey);
        mdbxKey.iov_len = Local<ArrayBufferView>::Cast(jsKey)->ByteLength();
        return true;
    }
    uint8_t* targetBytes = keySpace.getTarget();
    size_t size = mdbxKey.iov_len = valueToKey(jsKey, targetBytes, 511, false, keySpace.fixedSize);
    mdbxKey.iov_base = targetBytes;
    if (!keySpace.fixedSize)
        keySpace.position += size;
    return size;
}

Local<Value> MDBXKeyToValue(MDBX_val &val) {
    Local<Value> value;
    int consumed = 0;
    uint8_t* keyBytes = (uint8_t*) val.iov_base;
    int size = val.iov_len;
    if (size < 1) {
        return Nan::Null();
    }
    uint8_t controlByte = keyBytes[0];
    if (controlByte < 24) {
        if (controlByte < 8) {
            consumed = 1;
            if (controlByte == 6) {
                value = Nan::New<Boolean>(false);
            } else if (controlByte == 7) {
                value = Nan::New<Boolean>(true);
            } else if (controlByte == 0) {
                value = Nan::Null();
            } else if (controlByte == 2) {
                consumed = size;
                uint8_t* separatorPosition = (uint8_t*) memchr((char*) keyBytes + 1, 0, size - 1);
                if (separatorPosition) {
                    value = Symbol::For(Isolate::GetCurrent(), Nan::New<v8::String>((char*) keyBytes + 1, separatorPosition - keyBytes - 1).ToLocalChecked());
                    consumed = separatorPosition - keyBytes;
                } else {
                    value = Symbol::For(Isolate::GetCurrent(), Nan::New<v8::String>((char*) keyBytes + 1, size - 1).ToLocalChecked());
                    consumed = size;
                }
            } else {
                return Nan::CopyBuffer(
                    (char*)val.iov_base,
                    val.iov_len
                ).ToLocalChecked();
            }
        } else {
            uint64_t asInt = ((uint64_t) keyBytes[0] << 60) | ((uint64_t) keyBytes[1] << 52) | ((uint64_t) keyBytes[2] << 44) | ((uint64_t) keyBytes[3] << 36);
            if (size > 4) {
                asInt |= ((uint64_t) keyBytes[4] << 28) | ((uint64_t) keyBytes[5] << 20);
                if (size > 6) {
                    asInt |= ((uint64_t) keyBytes[6] << 12) | ((uint64_t) keyBytes[7] << 4);
                    if (size > 8) {
                        asInt |= (uint64_t) keyBytes[8] >> 4;
                    }
                }
            }
            if (controlByte < 16)
                asInt = asInt ^ 0x7fffffffffffffff;
//           fprintf(stdout, "asInt %x %x \n",asInt, asInt >> 32);

            value = Nan::New<Number>(*((double*) &asInt));
            consumed = 9;
        }
    } else {
        consumed = val.iov_len;
        bool isOneByte = true;
        int8_t* position = ((int8_t*) val.iov_base);
        int8_t* end = position + consumed;
        if (*position == 27) {
            position++; // skip string escape byte
            consumed--;
            val.iov_base = (char*) val.iov_base + 1;
        }
        for (; position < end; position++) {
            if (*position < 1) { // by using signed chars, non-latin is negative and separators are less than 1
                int8_t c = *position;
                if (c < 0) {
                    isOneByte = false;
                } else { // 0, separator
                    consumed = position - ((int8_t*) val.iov_base);
                    break;
                }
            }
        }
        if (isOneByte)
            value = v8::String::NewFromOneByte(Isolate::GetCurrent(), (uint8_t*) val.iov_base, v8::NewStringType::kNormal, consumed).ToLocalChecked();
        else
            value = Nan::New<v8::String>((char*) val.iov_base, consumed).ToLocalChecked();
    }
    if (consumed < size) {
        Local<Value> nextValue;
        if (keyBytes[consumed] != 0 && keyBytes[consumed] != 30) {
            nextValue = Nan::New<v8::String>("Invalid separator byte").ToLocalChecked();
        } else {
            MDBX_val nextPart;
            nextPart.iov_len = size - consumed - 1;
            nextPart.iov_base = &keyBytes[consumed + 1];
            nextValue = MDBXKeyToValue(nextPart);
        }
        v8::Local<v8::Array> resultsArray;
        Local<Context> context = Nan::GetCurrentContext();
        if (nextValue->IsArray()) {
            v8::Local<v8::Array> nextArray = Local<Array>::Cast(nextValue);
            int length = nextArray->Length();
            resultsArray = Nan::New<v8::Array>(1 + length);
            for (int i = 0; i < length; i++) {
                (void)resultsArray->Set(context, i + 1, nextArray->Get(context, i).ToLocalChecked());
            }
        } else {
            resultsArray = Nan::New<v8::Array>(2);
            (void)resultsArray->Set(context, 1, nextValue);
        }
        (void)resultsArray->Set(context, 0, value);
        value = resultsArray;
    }
    return value;
}

NAN_METHOD(bufferToKeyValue) {
    if (!node::Buffer::HasInstance(info[0])) {
        Nan::ThrowError("Invalid key. Should be a Buffer.");
        return;
    }
    MDBX_val key;
    key.iov_len = node::Buffer::Length(info[0]);
    key.iov_base = node::Buffer::Data(info[0]);
    info.GetReturnValue().Set(MDBXKeyToValue(key));
}
NAN_METHOD(keyValueToBuffer) {
    uint8_t* targetBytes = fixedKeySpace->getTarget();
    size_t size = valueToKey(info[0], targetBytes, 511, false, true);
    if (!size) {
        return;
    }
    Nan::MaybeLocal<v8::Object> buffer = Nan::CopyBuffer(
            (char*)targetBytes,
            size);
    info.GetReturnValue().Set(buffer.ToLocalChecked());
}


KeySpaceHolder::KeySpaceHolder() {
    previousSpace = nullptr;
}
KeySpaceHolder::KeySpaceHolder(KeySpaceHolder* existingSpace, uint8_t* existingData) {
    previousSpace = existingSpace;
    data = existingData;
}
KeySpaceHolder::~KeySpaceHolder() {
    if (previousSpace)
        delete previousSpace;
    delete[] data;
}

uint8_t* KeySpace::getTarget() {
    if (position + 511 > size) {
        if (fixedSize) {
            Nan::ThrowError("Key is too large");
            return nullptr;
        } else {
            previousSpace = new KeySpaceHolder(previousSpace, data);
            size = size << 1; // grow on each expansion
            data = new uint8_t[size];
        }
    }
    return &data[position];
}
KeySpace::KeySpace(bool fixed) {
    fixedSize = fixed;
    position = 0;
    size = fixed ? 512 : 8192;
    data = new uint8_t[size];
}
