#include "platform.h"

#include "bits.h"

#include <cstring>
#include <cstdlib>

NvMemoryType Platform::NonVolatileMemoryType()
{
    return _memoryType;
}

void Platform::NonVolatileMemoryType(NvMemoryType type)
{
    _memoryType = type;
}

void Platform::setupSpi()
{}

void Platform::closeSpi()
{}

int Platform::readWriteSpi(uint8_t* data, size_t len)
{
    return 0;
}

size_t Platform::readBytesUart(uint8_t* buffer, size_t length)
{
    return 0;
}

int Platform::readUart()
{
    return -1;
}

size_t Platform::writeUart(const uint8_t* buffer, size_t size)
{
    return 0;
}

size_t Platform::writeUart(const uint8_t data)
{
    return 0;
}

int Platform::uartAvailable()
{
    return 0;
}

void Platform::closeUart()
{}

void Platform::setupUart()
{}

bool Platform::overflowUart()
{
    return false;
}

void Platform::flushUart()
{}

uint32_t Platform::currentIpAddress()
{
    return 0x01020304;
}

uint32_t Platform::currentSubnetMask()
{
    return 0;
}

uint32_t Platform::currentDefaultGateway()
{
    return 0;
}

void Platform::macAddress(uint8_t* data)
{}

uint32_t Platform::uniqueSerialNumber()
{
    return 0x01020304;
}

void Platform::setupMultiCast(uint32_t addr, uint16_t port)
{}

void Platform::closeMultiCast()
{}

bool Platform::sendBytesMultiCast(uint8_t* buffer, uint16_t len)
{
    return false;
}

bool Platform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len)
{
    return false;
}

int Platform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen)
{
    return 0;
}

int Platform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port)
{
    return readBytesMultiCast(buffer, maxLen);
}

size_t Platform::flashEraseBlockSize()
{
    return 0;
}

size_t Platform::flashPageSize()
{
    // align to 32bit as default for Eeprom Emulation plattforms
    return 4;
}

uint8_t* Platform::userFlashStart()
{
    return nullptr;
}

size_t Platform::userFlashSizeEraseBlocks()
{
    return 0;
}

void Platform::flashErase(uint16_t eraseBlockNum)
{}

void Platform::flashWritePage(uint16_t pageNumber, uint8_t* data)
{}

uint8_t* Platform::getEepromBuffer(uint32_t size)
{
    return nullptr;
}

void Platform::commitToEeprom()
{}

uint8_t* Platform::getNonVolatileMemoryStart()
{
    if (_memoryType == Flash)
        return userFlashStart();

#ifdef KNX_FLASH_CALLBACK
    else if (_memoryType == Callback)
        return _callbackFlashRead();

#endif
    else
        return getEepromBuffer(KNX_FLASH_SIZE);
}

size_t Platform::getNonVolatileMemorySize()
{
    if (_memoryType == Flash)
        return userFlashSizeEraseBlocks() * flashEraseBlockSize() * flashPageSize();

#ifdef KNX_FLASH_CALLBACK
    else if (_memoryType == Callback)
        return _callbackFlashSize();

#endif
    else
        return KNX_FLASH_SIZE;
}

void Platform::commitNonVolatileMemory()
{
    if (_memoryType == Flash)
    {
        if (_bufferedEraseblockNumber > -1 && _bufferedEraseblockDirty)
        {
            writeBufferedEraseBlock();

            free(_eraseblockBuffer);
            _eraseblockBuffer = nullptr;
            _bufferedEraseblockNumber = -1;  // does that make sense?
        }
    }

#ifdef KNX_FLASH_CALLBACK
    else if (_memoryType == Callback)
        return _callbackFlashCommit();

#endif
    else
    {
        commitToEeprom();
    }
}

uint32_t Platform::writeNonVolatileMemory(uint32_t relativeAddress, uint8_t* buffer, size_t size)
{
#ifdef KNX_LOG_MEM
    print("Platform::writeNonVolatileMemory relativeAddress ");
    print(relativeAddress);
    print(" size ");
    println(size);
#endif

    if (_memoryType == Flash)
    {
        while (size > 0)
        {
            loadEraseblockContaining(relativeAddress);
            uint32_t start = _bufferedEraseblockNumber * (flashEraseBlockSize() * flashPageSize());
            uint32_t end = start +  (flashEraseBlockSize() * flashPageSize());

            uint32_t offset = relativeAddress - start;
            uint32_t length = end - relativeAddress;

            if (length > size)
                length = size;

            memcpy(_eraseblockBuffer + offset, buffer, length);
            _bufferedEraseblockDirty = true;

            relativeAddress += length;
            buffer += length;
            size -= length;
        }

        return relativeAddress;
    }

#ifdef KNX_FLASH_CALLBACK
    else if (_memoryType == Callback)
        return _callbackFlashWrite(relativeAddress, buffer, size);

#endif
    else
    {
        memcpy(getEepromBuffer(KNX_FLASH_SIZE) + relativeAddress, buffer, size);
        return relativeAddress + size;
    }
}

uint32_t Platform::readNonVolatileMemory(uint32_t relativeAddress, uint8_t* buffer, size_t size)
{
#ifdef KNX_LOG_MEM
    print("Platform::readNonVolatileMemory relativeAddress ");
    print(relativeAddress);
    print(" size ");
    println(size);
#endif

    if (_memoryType == Flash)
    {
        uint32_t offset = 0;

        while (size > 0)
        {
            // bufferd block is "left" of requested memory, read until the end and return
            if (_bufferedEraseblockNumber < getEraseBlockNumberOf(relativeAddress))
            {
                memcpy(buffer + offset, userFlashStart() + relativeAddress, size);
                return relativeAddress + size;
            }
            // bufferd block is "right" of requested memory, and may interfere
            else if (_bufferedEraseblockNumber > getEraseBlockNumberOf(relativeAddress))
            {
                // if the end of the requested memory is before the buffered block, read until the end and return
                int32_t eraseblockNumberEnd = getEraseBlockNumberOf(relativeAddress + size - 1);

                if (_bufferedEraseblockNumber > eraseblockNumberEnd)
                {
                    memcpy(buffer + offset, userFlashStart() + relativeAddress, size);
                    return relativeAddress + size;
                }
                // if not, read until the buffered block starts and loop through while again
                else
                {
                    uint32_t sizeToRead = (eraseblockNumberEnd * flashEraseBlockSize()) - relativeAddress;
                    memcpy(buffer + offset, userFlashStart() + relativeAddress, sizeToRead);
                    relativeAddress += sizeToRead;
                    size -= sizeToRead;
                    offset += sizeToRead;
                }
            }
            // start of requested memory is within the buffered erase block
            else
            {
                // if the end of the requested memory is also in the buffered block, read until the end and return
                int32_t eraseblockNumberEnd = getEraseBlockNumberOf(relativeAddress + size - 1);

                if (_bufferedEraseblockNumber == eraseblockNumberEnd)
                {
                    uint8_t* start = _eraseblockBuffer + (relativeAddress - _bufferedEraseblockNumber * flashEraseBlockSize());
                    memcpy(buffer + offset, start, size);
                    return relativeAddress + size;
                }
                // if not, read until the end of the buffered block and loop through while again
                else
                {
                    uint32_t offsetInBufferedBlock = relativeAddress - _bufferedEraseblockNumber * flashEraseBlockSize();
                    uint8_t* start = _eraseblockBuffer + offsetInBufferedBlock;
                    uint32_t sizeToRead = flashEraseBlockSize() - offsetInBufferedBlock;
                    memcpy(buffer + offset, start, sizeToRead);
                    relativeAddress += sizeToRead;
                    size -= sizeToRead;
                    offset += sizeToRead;
                }
            }
        }

        return relativeAddress;
    }
    else
    {
        memcpy(buffer, getEepromBuffer(KNX_FLASH_SIZE) + relativeAddress, size);
        return relativeAddress + size;
    }
}

// writes value repeat times into flash starting at relativeAddress
// returns next free relativeAddress
uint32_t Platform::writeNonVolatileMemory(uint32_t relativeAddress, uint8_t value, size_t repeat)
{
    if (_memoryType == Flash)
    {
        while (repeat > 0)
        {
            loadEraseblockContaining(relativeAddress);
            uint32_t start = _bufferedEraseblockNumber * (flashEraseBlockSize() * flashPageSize());
            uint32_t end = start +  (flashEraseBlockSize() * flashPageSize());

            uint32_t offset = relativeAddress - start;
            uint32_t length = end - relativeAddress;

            if (length > repeat)
                length = repeat;

            memset(_eraseblockBuffer + offset, value, length);
            _bufferedEraseblockDirty = true;

            relativeAddress += length;
            repeat -= length;
        }

        return relativeAddress;
    }
    else
    {
        memset(getEepromBuffer(KNX_FLASH_SIZE) + relativeAddress, value, repeat);
        return relativeAddress + repeat;
    }
}

void Platform::loadEraseblockContaining(uint32_t relativeAddress)
{
    int32_t blockNum = getEraseBlockNumberOf(relativeAddress);

    if (blockNum < 0)
    {
        println("loadEraseblockContaining could not get valid eraseblock number");
        fatalError();
    }

    if (blockNum != _bufferedEraseblockNumber && _bufferedEraseblockNumber >= 0)
        writeBufferedEraseBlock();

    bufferEraseBlock(blockNum);
}

int32_t Platform::getEraseBlockNumberOf(uint32_t relativeAddress)
{
    return relativeAddress / (flashEraseBlockSize() * flashPageSize());
}


void Platform::writeBufferedEraseBlock()
{
    if (_bufferedEraseblockNumber > -1 && _bufferedEraseblockDirty)
    {
        flashErase(_bufferedEraseblockNumber);

        for (uint32_t i = 0; i < flashEraseBlockSize(); i++)
        {
            int32_t pageNumber = _bufferedEraseblockNumber * flashEraseBlockSize() + i;
            uint8_t* data = _eraseblockBuffer + flashPageSize() * i;
            flashWritePage(pageNumber, data);
        }

        _bufferedEraseblockDirty = false;
    }
}


void Platform::bufferEraseBlock(int32_t eraseBlockNumber)
{
    if (_bufferedEraseblockNumber == eraseBlockNumber)
        return;

    if (_eraseblockBuffer == nullptr)
    {
        _eraseblockBuffer = (uint8_t*)malloc(flashEraseBlockSize() * flashPageSize());
    }

    memcpy(_eraseblockBuffer, userFlashStart() + eraseBlockNumber * flashEraseBlockSize() * flashPageSize(), flashEraseBlockSize() * flashPageSize());

    _bufferedEraseblockNumber = eraseBlockNumber;
    _bufferedEraseblockDirty = false;
}


#ifdef KNX_FLASH_CALLBACK
void Platform::registerFlashCallbacks(
    FlashCallbackSize callbackFlashSize,
    FlashCallbackRead callbackFlashRead,
    FlashCallbackWrite callbackFlashWrite,
    FlashCallbackCommit callbackFlashCommit)
{
    println("Set Callback");
    _memoryType = Callback;
    _callbackFlashSize = callbackFlashSize;
    _callbackFlashRead = callbackFlashRead;
    _callbackFlashWrite = callbackFlashWrite;
    _callbackFlashCommit = callbackFlashCommit;
    _callbackFlashSize();
}

FlashCallbackSize Platform::callbackFlashSize()
{
    return _callbackFlashSize;
}
FlashCallbackRead Platform::callbackFlashRead()
{
    return _callbackFlashRead;
}
FlashCallbackWrite Platform::callbackFlashWrite()
{
    return _callbackFlashWrite;
}
FlashCallbackCommit Platform::callbackFlashCommit()
{
    return _callbackFlashCommit;
}
#endif
