#include "..\Public\UE4ComCommon.h"

namespace Ventuz
{
namespace UE4
{

// return true if the hash value has changed
bool SharedDatabase::HandleMessage(const DataKeyValuePair &kvp)
{
    switch (kvp.key.type)
    {
    case AncID::CameraMatrix:
    {
        memcpy(cameraMatrix, kvp.data, kvp.length);
        matricesUpdated = true;
        break;
    }
    case AncID::ProjectionMatrix:
    {
        memcpy(projectionMatrix, kvp.data, kvp.length);
        matricesUpdated = true;
        break;
    }
    case AncID::Event:
    {
        FromVentuzEvent evnt{};
        evnt.paramHash = kvp.key.hash;
        if (kvp.length == 8)
            evnt.argument = *(int *)(kvp.data + 4);
        events.emplace_back(evnt);
        return true;
    }
    case AncID::IntValue:
    {
        intValues[kvp.key.hash] = *(int *)kvp.data;
        //hash = intData->paramHash;
        return true;
    }
    case AncID::FloatValue:
    {
        floatValues[kvp.key.hash] = *(float *)kvp.data;
        //hash = floatData->paramHash;
        return true;
    }
    case AncID::StringValue:
    {
        //hash = ((uint *)kvp.data)[0];
        stringValues[kvp.key.hash] = std::string((char *)kvp.data, kvp.length);
        return true;
    }
    case AncID::ErrorMessage:
    {
        errorMessage = std::string((char *)kvp.data, kvp.length);
        return true;
    }
    case AncID::BoolValue:
    {
        //FromVentuzBool *boolData = (FromVentuzBool *)kvp.data;
        boolValues[kvp.key.hash] = (bool)*kvp.data;
        //hash = boolData->paramHash;
        return true;
    }
    case AncID::TransformValue:
    {
        float mx[16];
        for (int x = 0; x < 16; x++)
            mx[x] = (float)((double *)kvp.data)[x];

        if (kvp.key.nodeid != GUID{})
        {
            auto transformByGUID = transformValuesByGUID.find(kvp.key.nodeid);
            if (transformByGUID != transformValuesByGUID.end())
            {
                if (memcmp(&(*transformByGUID).second, mx, 16 * sizeof(float)))
                    updatedTransformValues.emplace_back(kvp.key.nodeid);
            }

            memcpy(&transformValuesByGUID[kvp.key.nodeid], mx, 16 * sizeof(float));
        }

        if (kvp.key.hash)
        {
            memcpy(transformValues[kvp.key.hash], mx, 16 * sizeof(float));
            //hash = transformData->paramHash;
            return true;
        }
        break;
    }
    case AncID::IntArray:
    {
        //FromVentuzInt *intData = (FromVentuzInt *)kvp.data;
        auto &intArray = intArrays[kvp.key.hash];

        intArray.clear();
        for (int y = 0; y < kvp.length / sizeof(int); y++)
            intArray.emplace_back(((int *)kvp.data)[y]);
        //hash = intData->paramHash;
        return true;
    }
    case AncID::FloatArray:
    {
        //FromVentuzFloat *floatData = (FromVentuzFloat *)kvp.data;

        auto &floatArray = floatArrays[kvp.key.hash];

        floatArray.clear();
        for (int y = 0; y < kvp.length / sizeof(float); y++)
            floatArray.emplace_back(((float *)kvp.data)[y]);
        //hash = floatData->paramHash;
        return true;
    }
    case AncID::BoolArray:
    {
        //FromVentuzBool *boolData = (FromVentuzBool *)kvp.data;
        auto &boolArray = boolArrays[kvp.key.hash];

        boolArray.clear();
        for (int y = 0; y < kvp.length / sizeof(bool); y++)
            boolArray.emplace_back(((bool *)kvp.data)[y]);
        //hash = boolData->paramHash;
        return true;
    }
    case AncID::StringArray:
    {
        //hash = ((uint *)stringData)[0];

        int len = MultiByteToWideChar(CP_ACP, 0, (char *)kvp.data, kvp.length, 0, 0);
        if (!len)
            break;

        std::vector<wchar_t> wbuff(len + 1);
        if (!MultiByteToWideChar(CP_ACP, 0, (char *)kvp.data, kvp.length, &wbuff[0], len))
            break;

        stringArrays[kvp.key.hash] = std::wstring(&wbuff[0], len);
        return true;
    }
    case AncID::InputData:
    {
        inputBundles.emplace_back(std::string((char*)kvp.data, kvp.length));
        return false;
    }
    case AncID::Viewports:
    {
        memcpy(&viewportData, kvp.data, kvp.length);
        return false;
    }
    case AncID::AlphaType:
    {
        memcpy(&alphaMode, kvp.data, kvp.length);
        return false;
    }
    case AncID::UnrealVersion:
    {
        memcpy(&unrealVersion, kvp.data, kvp.length);
        return false;
    }

    default:
        break;
    }

    return false;
}

// return true if the hash value has changed
bool SharedDatabase::HandleMessage(unsigned char *message, int _length, uint &hash, std::function<void( const DataKeyValuePair& )>& hashChangeCallback )
{
    AncID fourcc = *((AncID *)message);

    DataKeyValuePair kvp;
    kvp.key.type = fourcc;
    kvp.data = message + 4;
    kvp.length = _length - 4;

    // special case for old style matrix transform - convert to new style
    double matrix[16];

    switch (fourcc)
    {
    case AncID::Event:
    case AncID::IntValue:
    case AncID::FloatValue:
    case AncID::StringValue:
    case AncID::BoolValue:
    case AncID::IntArray:
    case AncID::FloatArray:
    case AncID::BoolArray:
    case AncID::StringArray:
    {
        kvp.key.hash = ((uint *)kvp.data)[0];
        kvp.data += 4;
        kvp.length -= 4;
        break;
    }
    case AncID::TransformValue:
    {
        FromVentuzMatrix *transformData = (FromVentuzMatrix *)kvp.data;
        kvp.key.hash = transformData->paramHash;
        kvp.key.nodeid = transformData->nodeID;
        for (int x = 0; x < 16; x++)
            matrix[x] = transformData->value[x];
        kvp.data = (unsigned char *)matrix;
        kvp.length = sizeof(matrix);
        break;
    }
    case AncID::InputData:
    {
        kvp.length = ((int *)kvp.data)[0];
        kvp.data += 4;
        break;
    }
    case AncID::CameraMatrix:
    case AncID::ProjectionMatrix:
    case AncID::Viewports:
    case AncID::AlphaType:
    case AncID::UnrealVersion:
        break;

    default:
        break;
    }

    hash = kvp.key.hash;

    bool hashChanged = HandleMessage(kvp);
    if ( hashChanged && hashChangeCallback )
      hashChangeCallback( kvp );

    return hashChanged;
}

void SharedDatabase::Reset()
{
    inputBundles.clear();

    events.clear();
    intValues.clear();
    floatValues.clear();
    stringValues.clear();
    boolValues.clear();
    colorValues.clear();
    transformValues.clear();
    transformValuesByGUID.clear();

    intArrays.clear();
    floatArrays.clear();
    stringArrays.clear();
    boolArrays.clear();

    errorMessage = "";

    memset(&viewportData, 0, sizeof(ViewportData));
}

}
}
