#include "Dtypes.h"
#include "MockSerial.h"
#include "MeasurementSession.h"
#include "MockStorage.h"
#include <unity.h>
#include <vector>
#include <iostream>

template<typename Measurement_T>
std::vector<Measurement_T> parseMessagePack(const uint8_t * data, uint32_t &startTime, uint32_t &startIndex)
{
    int offset = 0;

    // map header
    const int expectedMapSize = 3;
    auto mapHeader = reinterpret_cast<const uint8_t*>(&data[offset]);
    offset += 1;
    TEST_ASSERT_MESSAGE( *mapHeader == (0b10000000 | expectedMapSize), "Map Header wrong");

    // string255: sessionStartTime
    auto stringHeader = reinterpret_cast<const char*>(&data[offset++]);
    auto stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);

    TEST_ASSERT_EQUAL(*stringHeader, '\xd9');
    std::string sessionStartTimeStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
    TEST_ASSERT( sessionStartTimeStr == "sessionStartTime");
    offset += *stringSize;

    //uint32
    auto intCode = reinterpret_cast<const uint8_t*>(&data[offset++]);
    startTime = ntohl(*reinterpret_cast<const uint32_t*>(&data[offset]));
    offset += 4;

    // string255: startIndex
    stringHeader = reinterpret_cast<const char*>(&data[offset++]);
    stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);
    TEST_ASSERT_MESSAGE(*stringHeader == '\xd9', "String header wrong");
    std::string startIndexStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
    TEST_ASSERT_MESSAGE( startIndexStr == "startIndex", "startIndex string is wrong");
    offset += *stringSize;

    //uint32
    intCode = reinterpret_cast<const uint8_t*>(&data[offset++]);
    startIndex = ntohl(*reinterpret_cast<const uint32_t*>(&data[offset]));
    offset += 4;

    // string255: values
    stringHeader = reinterpret_cast<const char*>(&data[offset++]);
    stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);
    TEST_ASSERT_MESSAGE(*stringHeader == '\xd9', "String header wrong");
    std::string valueStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
    TEST_ASSERT_MESSAGE( valueStr == "values", "values string is wrong");
    offset += *stringSize;

    // vector
    auto vectorHeader = reinterpret_cast<const char*>(&data[offset++]);
    TEST_ASSERT_MESSAGE( *vectorHeader == '\xc9', "Vector header wrong");
    size_t vectorLength = ntohl(*reinterpret_cast<const uint32_t*>(&data[offset])) / sizeof(Measurement_T);
    offset += 4;
    offset += 1; // jump over type

    auto vectorData = reinterpret_cast<const Measurement_T *>(&data[offset]);
    return std::vector<Measurement_T>(vectorData, vectorData + vectorLength);
}


void testSessionChunkAdd()
{
    const uint_t size = 16;
    SessionChunk<uint16_t, size> chunk;
    for( uint16_t i=0; i < size; ++i) {
        bool res = chunk.addPoint(i);
        TEST_ASSERT_MESSAGE(res, "Adding point failed");
        TEST_ASSERT_MESSAGE( chunk.numMeasurements() == i+1, "Number of measurements reported wrong");
    }
    bool res = chunk.addPoint(0);
    TEST_ASSERT_MESSAGE(!res, "Full chunk was not detected");
    TEST_ASSERT_MESSAGE(chunk.numMeasurements() == size, "Point appears to be added");
}

void testSessionChunkGetterSetter()
{
    const uint_t size = 16;
    SessionChunk<uint16_t, size> chunk;
    const uint32_t time = 244213;
    const uint32_t startIdx = 131;
    chunk.init(time, startIdx);
    TEST_ASSERT_MESSAGE( chunk.getStartIndex() == startIdx, "Start Index wrong");
    TEST_ASSERT_MESSAGE( chunk.getStartTime() == time, "Start time wrong");
}

void testSessionChunkSerialization()
{
    const uint_t size = 16;
    const uint32_t startTime = 194232;
    const uint32_t startIndex = 1314;
    const uint_t fillSize = 12;

    SessionChunk<uint16_t, size> chunk;
    chunk.init(startTime, startIndex);
    for( uint16_t i=0; i < fillSize; ++i) {
        bool res = chunk.addPoint(i);
        TEST_ASSERT_MESSAGE(res, "Adding point failed");
        TEST_ASSERT_MESSAGE( chunk.numMeasurements() == i+1, "Number of measurements reported wrong");
    }

    std::vector<uint8_t> data;
    VectorAdaptor adaptor( &data );
    StreamingMsgPackEncoder<VectorAdaptor> encoder(&adaptor);
    chunk.serialize(encoder);
    uint32_t readStartTime=0;
    uint32_t readStartIndex=0;
    auto result = parseMessagePack<uint16_t>(&data[0], readStartTime, readStartIndex);
    TEST_ASSERT_MESSAGE(startIndex == readStartIndex && startTime == readStartTime, "");
    TEST_ASSERT_MESSAGE(result.size() == fillSize, "Wrong result array size");
    for( uint16_t i=0; i < fillSize; ++i) {
        TEST_ASSERT_MESSAGE(result[i] == i, "Wrong array contents");
    }
}


void testSession() {
    const uint32_t SESSION_SIZE = 128;
    typedef MeasurementSession<uint16_t, MockStorageReader, MockStorageWriter, SESSION_SIZE> MockSession;

    const uint32_t startTime = 194842;
    const uint_t fillSize = SESSION_SIZE * 4 + 7;

    MockSession session;
    session.init(startTime);
    for (uint16_t i = 0; i < fillSize; ++i) {
        session.addPoint(i);
    }

    std::vector<uint8_t> data;
    VectorAdaptor adaptor( &data );
    StreamingMsgPackEncoder<VectorAdaptor> encoder(&adaptor);
    session.serialize(encoder, 0);
    uint32_t readStartTime=0;
    uint32_t readStartIndex=0;
    auto result = parseMessagePack<uint16_t>(&data[0], readStartTime, readStartIndex);
    TEST_ASSERT_MESSAGE(readStartIndex == 0 && startTime == readStartTime, "");
    TEST_ASSERT_MESSAGE(result.size() == fillSize, "Wrong result array size");
    for( uint16_t i=0; i < fillSize; ++i) {
        TEST_ASSERT_MESSAGE(result[i] == i, "Wrong array contents");
    }
}


void testPartialSessionSerialization() {
    const uint32_t SESSION_SIZE = 1024*8 - 16 * sizeof(uint32_t);
    typedef MeasurementSession<uint16_t, MockStorageReader, MockStorageWriter, SESSION_SIZE> MockSession;

    const uint32_t startTime = 194842;
    const uint_t fillSize = 4937 + 81;

    MockSession session;
    session.init(startTime);
    for (uint16_t i = 0; i < fillSize; ++i) {
        session.addPoint(i);
    }

    std::vector<uint8_t> data;
    VectorAdaptor adaptor( &data );

    StreamingMsgPackEncoder<VectorAdaptor> encoder(&adaptor);
    encoder.setSizeCountMode(true);
    session.serialize(encoder, 0);
    auto totalSize = encoder.getContentLength();

    std::vector<uint32_t> splits = {953, totalSize};
    //std::vector<uint32_t> splits = {totalSize};
    uint32_t written = 0;
    data.clear();
    for(auto & split : splits) {
        ChunkedStreamingMsgPackEncoder<VectorAdaptor> encoder(&adaptor, written, split);
        session.serialize(encoder, 0);
        written = encoder.sentBytes();
    }
    TEST_ASSERT(written == totalSize);

    uint32_t readStartTime=0;
    uint32_t readStartIndex=0;
    auto result = parseMessagePack<uint16_t>(&data[0], readStartTime, readStartIndex);
    TEST_ASSERT_MESSAGE(readStartIndex == 0 && startTime == readStartTime, "");
    TEST_ASSERT_MESSAGE(result.size() == fillSize, "Wrong result array size");
    for( uint16_t i=0; i < fillSize; ++i) {
        TEST_ASSERT_MESSAGE(result[i] == i, "Wrong array contents");
    }
}

void testPartialSessionSerializationEmptyArray() {
    const uint32_t SESSION_SIZE = 1024*8 - 16 * sizeof(uint32_t);
    typedef MeasurementSession<uint16_t, MockStorageReader, MockStorageWriter, SESSION_SIZE> MockSession;

    const uint32_t startTime = 194842;
    const uint_t fillSize = 4937 + 81;

    std::vector<uint8_t> data;
    VectorAdaptor adaptor( &data );

    MockSession session;
    session.init(startTime);
    ChunkedStreamingMsgPackEncoder<VectorAdaptor> encoder(&adaptor, 0, 6);
    session.serialize(encoder, 0);
    auto written = encoder.sentBytes();
    TEST_ASSERT(written == 6);
}

void allTests()
{
    UNITY_BEGIN();
    RUN_TEST(testPartialSessionSerializationEmptyArray);
    RUN_TEST(testPartialSessionSerialization);
    RUN_TEST(testSessionChunkAdd);
    RUN_TEST(testSessionChunkGetterSetter);
    RUN_TEST(testSessionChunkSerialization);
    RUN_TEST(testSession);
    UNITY_END();
}

#ifdef ARDUINO
void setup() {
    // NOTE!!! Wait for >2 secs
    // if board doesn't support software reset via Serial.DTR/RTS
    delay(2000);
    allTests();
}

void loop() {
}

#else

int main(int argc, char**argv)
{
    allTests();
    return 0;
}

#endif