#ifndef FRAMEWORK_CONTAINER_INDEXEDRINGMEMORY_H_
#define FRAMEWORK_CONTAINER_INDEXEDRINGMEMORY_H_

#include "ArrayList.h"
#include "../globalfunctions/CRC.h"
#include "../serviceinterface/ServiceInterfaceStream.h"
#include "../returnvalues/HasReturnvaluesIF.h"
#include "../serialize/SerialArrayListAdapter.h"
#include <cmath>

template<typename T>
class Index: public SerializeIF{
    /**
     * Index is the Type used for the list of indices. The template parameter is the type which describes the index, it needs to be a child of SerializeIF to be able to make it persistent
     */
    static_assert(std::is_base_of<SerializeIF,T>::value,"Wrong Type for Index, Type must implement SerializeIF");
public:
    Index():blockStartAddress(0),size(0),storedPackets(0){}

    Index(uint32_t startAddress):blockStartAddress(startAddress),size(0),storedPackets(0){

    }

    void setBlockStartAddress(uint32_t newAddress){
        this->blockStartAddress = newAddress;
    }

    uint32_t getBlockStartAddress() const {
        return blockStartAddress;
    }

    const T* getIndexType() const {
        return &indexType;
    }

    T* modifyIndexType(){
        return &indexType;
    }
    /**
     * Updates the index Type. Uses = operator
     * @param indexType Type to copy from
     */
    void setIndexType(T* indexType) {
        this->indexType = *indexType;
    }

    uint32_t getSize() const {
        return size;
    }

    void setSize(uint32_t size) {
        this->size = size;
    }

    void addSize(uint32_t size){
        this->size += size;
    }

    void setStoredPackets(uint32_t newStoredPackets){
        this->storedPackets = newStoredPackets;
    }

    void addStoredPackets(uint32_t packets){
        this->storedPackets += packets;
    }

    uint32_t getStoredPackets() const{
        return this->storedPackets;
    }

    ReturnValue_t serialize(uint8_t** buffer, size_t* size,
                size_t maxSize, Endianness streamEndianness) const {
        ReturnValue_t result = SerializeAdapter::serialize(&blockStartAddress,buffer,size,maxSize,streamEndianness);
        if(result != HasReturnvaluesIF::RETURN_OK){
            return result;
        }
        result = indexType.serialize(buffer,size,maxSize,streamEndianness);
        if(result != HasReturnvaluesIF::RETURN_OK){
            return result;
        }
        result = SerializeAdapter::serialize(&this->size,buffer,size,maxSize,streamEndianness);
        if(result != HasReturnvaluesIF::RETURN_OK){
            return result;
        }
        result = SerializeAdapter::serialize(&this->storedPackets,buffer,size,maxSize,streamEndianness);
        return result;
    }

    ReturnValue_t deSerialize(const uint8_t** buffer, size_t* size,
                Endianness streamEndianness){
        ReturnValue_t result = SerializeAdapter::deSerialize(&blockStartAddress,buffer,size,streamEndianness);
        if(result != HasReturnvaluesIF::RETURN_OK){
            return result;
        }
        result = indexType.deSerialize(buffer,size,streamEndianness);
        if(result != HasReturnvaluesIF::RETURN_OK){
            return result;
        }
        result = SerializeAdapter::deSerialize(&this->size,buffer,size,streamEndianness);
        if(result != HasReturnvaluesIF::RETURN_OK){
            return result;
        }
        result = SerializeAdapter::deSerialize(&this->storedPackets,buffer,size,streamEndianness);
        if(result != HasReturnvaluesIF::RETURN_OK){
            return result;
        }
        return result;
    }

    size_t getSerializedSize() const {
        uint32_t size = SerializeAdapter::getSerializedSize(&blockStartAddress);
        size += indexType.getSerializedSize();
        size += SerializeAdapter::getSerializedSize(&this->size);
        size += SerializeAdapter::getSerializedSize(&this->storedPackets);
        return size;
    }


    bool operator==(const Index<T>& other){
        return ((blockStartAddress == other.getBlockStartAddress()) && (size==other.getSize())) && (indexType == *(other.getIndexType()));
    }

private:
    uint32_t blockStartAddress;
    uint32_t size;
    uint32_t storedPackets;
    T indexType;
};



template<typename T>
class IndexedRingMemoryArray: public SerializeIF, public ArrayList<Index<T>, uint32_t>{
    /**
     * Indexed Ring Memory Array is a class for a ring memory with indices. It assumes that the newest data comes in last
     * It uses the currentWriteBlock as pointer to the current writing position
     * The currentReadBlock must be set manually
     */
public:
    IndexedRingMemoryArray(uint32_t startAddress, uint32_t size, uint32_t bytesPerBlock, SerializeIF* additionalInfo,
            bool overwriteOld) :ArrayList<Index<T>,uint32_t>(NULL,(uint32_t)10,(uint32_t)0),totalSize(size),indexAddress(startAddress),currentReadSize(0),currentReadBlockSizeCached(0),lastBlockToReadSize(0), additionalInfo(additionalInfo),overwriteOld(overwriteOld){

        //Calculate the maximum number of indices needed for this blocksize
        uint32_t maxNrOfIndices = floor(static_cast<double>(size)/static_cast<double>(bytesPerBlock));

        //Calculate the Size needeed for the index itself
        uint32_t serializedSize = 0;
        if(additionalInfo!=NULL){
            serializedSize += additionalInfo->getSerializedSize();
        }
        //Size of current iterator type
        Index<T> tempIndex;
        serializedSize += tempIndex.getSerializedSize();

        //Add Size of Array
        serializedSize += sizeof(uint32_t); //size of array
        serializedSize += (tempIndex.getSerializedSize() * maxNrOfIndices); //size of elements
        serializedSize += sizeof(uint16_t); //size of crc

        //Calculate new size after index
        if(serializedSize > totalSize){
            error << "IndexedRingMemory: Store is too small for index" << std::endl;
        }
        uint32_t useableSize = totalSize - serializedSize;
        //Update the totalSize for calculations
        totalSize = useableSize;

        //True StartAddress
        uint32_t trueStartAddress = indexAddress + serializedSize;

        //Calculate True number of Blocks and reset size of true Number of Blocks
        uint32_t trueNumberOfBlocks = floor(static_cast<double>(totalSize) / static_cast<double>(bytesPerBlock));

        //allocate memory now
        this->entries = new Index<T>[trueNumberOfBlocks];
        this->size = trueNumberOfBlocks;
        this->maxSize_ = trueNumberOfBlocks;
        this->allocated = true;

        //Check trueNumberOfBlocks
        if(trueNumberOfBlocks<1){
            error << "IndexedRingMemory: Invalid Number of Blocks: " << trueNumberOfBlocks;
        }



        //Fill address into index
        uint32_t address = trueStartAddress;
        for (typename IndexedRingMemoryArray<T>::Iterator it = this->begin();it!=this->end();++it) {
            it->setBlockStartAddress(address);
            it->setSize(0);
            it->setStoredPackets(0);
            address += bytesPerBlock;
        }


        //Initialize iterators
        currentWriteBlock = this->begin();
        currentReadBlock = this->begin();
        lastBlockToRead = this->begin();

        //Check last blockSize
        uint32_t lastBlockSize = (trueStartAddress + useableSize) -  (this->back()->getBlockStartAddress());
        if((lastBlockSize<bytesPerBlock) && (this->size > 1)){
            //remove the last Block so the second last block has more size
            this->size -= 1;
            debug << "IndexedRingMemory: Last Block is smaller than bytesPerBlock, removed last block" << std::endl;
        }
    }

    /**
     * Resets the whole index, the iterators and executes the given reset function on every index type
     * @param typeResetFnc static reset function which accepts a pointer to the index Type
     */
    void reset(void (*typeResetFnc)(T*)){
        currentReadBlock = this->begin();
        currentWriteBlock = this->begin();
        lastBlockToRead = this->begin();
        currentReadSize = 0;
        currentReadBlockSizeCached = 0;
        lastBlockToReadSize = 0;
        for(typename IndexedRingMemoryArray<T>::Iterator it = this->begin();it!=this->end();++it){
            it->setSize(0);
            it->setStoredPackets(0);
            (*typeResetFnc)(it->modifyIndexType());
        }
    }

    void resetBlock(typename IndexedRingMemoryArray<T>::Iterator it,void (*typeResetFnc)(T*)){
        it->setSize(0);
        it->setStoredPackets(0);
        (*typeResetFnc)(it->modifyIndexType());
    }

    /*
     * Reading
     */

    void setCurrentReadBlock(typename IndexedRingMemoryArray<T>::Iterator it){
        currentReadBlock = it;
        currentReadBlockSizeCached = it->getSize();
    }

    void resetRead(){
        currentReadBlock = this->begin();
        currentReadSize = 0;
        currentReadBlockSizeCached = this->begin()->getSize();
        lastBlockToRead = currentWriteBlock;
        lastBlockToReadSize = currentWriteBlock->getSize();
    }
    /**
     * Sets the last block to read to this iterator.
     * Can be used to dump until block x
     * @param it The iterator for the last read block
     */
    void setLastBlockToRead(typename IndexedRingMemoryArray<T>::Iterator it){
        lastBlockToRead = it;
        lastBlockToReadSize = it->getSize();
    }

    /**
     * Set the read pointer to the first written Block, which is the first non empty block in front of the write block
     * Can be the currentWriteBlock as well
     */
    void readOldest(){
        resetRead();
        currentReadBlock = getNextNonEmptyBlock();
        currentReadBlockSizeCached = currentReadBlock->getSize();

    }

    /**
     * Sets the current read iterator to the next Block and resets the current read size
     * The current size of the block will be cached to avoid race condition between write and read
     * If the end of the ring is reached the read pointer will be set to the begin
     */
    void readNext(){
        currentReadSize = 0;
        if((this->size != 0) && (currentReadBlock.value ==this->back())){
            currentReadBlock = this->begin();
        }else{
            currentReadBlock++;
        }

        currentReadBlockSizeCached = currentReadBlock->getSize();
    }

    /**
     * Returns the address which is currently read from
     * @return Address to read from
     */
    uint32_t getCurrentReadAddress() const {
        return getAddressOfCurrentReadBlock() + currentReadSize;
    }
    /**
     * Adds readSize to the current size and checks if the read has no more data left and advances the read block
     * @param readSize The size that was read
     * @return Returns true if the read can go on
     */
    bool addReadSize(uint32_t readSize) {
        if(currentReadBlock == lastBlockToRead){
            //The current read block is the last to read
            if((currentReadSize+readSize)<lastBlockToReadSize){
                //the block has more data -> return true
                currentReadSize += readSize;
                return true;
            }else{
                //Reached end of read -> return false
                currentReadSize = lastBlockToReadSize;
                return false;
            }
        }else{
            //We are not in the last Block
            if((currentReadSize + readSize)<currentReadBlockSizeCached){
                //The current Block has more data
                currentReadSize += readSize;
                return true;
            }else{
                //The current block is written completely
                readNext();
                if(currentReadBlockSizeCached==0){
                    //Next block is empty
                    typename IndexedRingMemoryArray<T>::Iterator it(currentReadBlock);
                    //Search if any block between this and the last block is not empty
                    for(;it!=lastBlockToRead;++it){
                        if(it == this->end()){
                            //This is the end, next block is the begin
                            it = this->begin();
                            if(it == lastBlockToRead){
                                //Break if the begin is the lastBlockToRead
                                break;
                            }
                        }
                        if(it->getSize()!=0){
                            //This is a non empty block. Go on reading with this block
                            currentReadBlock = it;
                            currentReadBlockSizeCached = it->getSize();
                            return true;
                        }
                    }
                    //reached lastBlockToRead and every block was empty, check if the last block is also empty
                    if(lastBlockToReadSize!=0){
                        //go on with last Block
                        currentReadBlock = lastBlockToRead;
                        currentReadBlockSizeCached = lastBlockToReadSize;
                        return true;
                    }
                    //There is no non empty block left
                    return false;
                }
                //Size is larger than 0
                return true;
            }
        }
    }
    uint32_t getRemainigSizeOfCurrentReadBlock() const{
        if(currentReadBlock == lastBlockToRead){
            return (lastBlockToReadSize - currentReadSize);
        }else{
            return (currentReadBlockSizeCached - currentReadSize);
        }
    }

    uint32_t getAddressOfCurrentReadBlock() const {
        return currentReadBlock->getBlockStartAddress();
    }

    /**
     * Gets the next non empty Block after the current write block,
     * @return Returns the iterator to the block. If there is non, the current write block is returned
     */
    typename IndexedRingMemoryArray<T>::Iterator getNextNonEmptyBlock() const {
        for(typename IndexedRingMemoryArray<T>::Iterator it = getNextWrite();it!=currentWriteBlock;++it){
                if(it == this->end()){
                    it = this->begin();
                    if(it == currentWriteBlock){
                        break;
                    }
                }
                if(it->getSize()!=0){
                    return it;
                }
        }
        return currentWriteBlock;
    }

    /**
     * Returns a copy of the oldest Index type
     * @return Type of Index
     */
    T* getOldest(){
        return (getNextNonEmptyBlock()->modifyIndexType());
    }


    /*
     * Writing
     */
    uint32_t getAddressOfCurrentWriteBlock() const{
        return currentWriteBlock->getBlockStartAddress();
    }

    uint32_t getSizeOfCurrentWriteBlock() const{
        return currentWriteBlock->getSize();
    }

    uint32_t getCurrentWriteAddress() const{
        return getAddressOfCurrentWriteBlock() + getSizeOfCurrentWriteBlock();
    }

    void clearCurrentWriteBlock(){
        currentWriteBlock->setSize(0);
        currentWriteBlock->setStoredPackets(0);
    }

    void addCurrentWriteBlock(uint32_t size, uint32_t storedPackets){
        currentWriteBlock->addSize(size);
        currentWriteBlock->addStoredPackets(storedPackets);
    }

    T* modifyCurrentWriteBlockIndexType(){
        return currentWriteBlock->modifyIndexType();
    }
    void updatePreviousWriteSize(uint32_t size, uint32_t storedPackets){
        typename IndexedRingMemoryArray<T>::Iterator it = getPreviousBlock(currentWriteBlock);
        it->addSize(size);
        it->addStoredPackets(storedPackets);
    }


    /**
     * Checks if the block has enough space for sizeToWrite
     * @param sizeToWrite The data to be written in the Block
     * @return Returns true if size to write is smaller the remaining size of the block
     */
    bool hasCurrentWriteBlockEnoughSpace(uint32_t sizeToWrite){
        typename IndexedRingMemoryArray<T>::Iterator next = getNextWrite();
        uint32_t addressOfNextBlock = next->getBlockStartAddress();
        uint32_t availableSize = ((addressOfNextBlock+totalSize) - (getAddressOfCurrentWriteBlock()+getSizeOfCurrentWriteBlock()))%totalSize;
        return (sizeToWrite < availableSize);
    }

    /**
     * Checks if the store is full if overwrite old is false
     * @return Returns true if it is writeable and false if not
     */
    bool isNextBlockWritable(){
        //First check if this is the end of the list
        typename IndexedRingMemoryArray<T>::Iterator next;
        next = getNextWrite();
        if((next->getSize()!=0) && (!overwriteOld)){
            return false;
        }
        return true;
    }

    /**
     * Updates current write Block Index Type
     * @param infoOfNewBlock
     */
    void updateCurrentBlock(T* infoOfNewBlock){
        currentWriteBlock->setIndexType(infoOfNewBlock);
    }


    /**
     * Succeed to next block, returns FAILED if overwrite is false and the store is full
     * @return
     */
    ReturnValue_t writeNext(){
        //Check Next Block
        if(!isNextBlockWritable()){
            //The Index is full and does not overwrite old
            return HasReturnvaluesIF::RETURN_FAILED;
        }
        //Next block can be written, update Metadata
        currentWriteBlock = getNextWrite();
        currentWriteBlock->setSize(0);
        currentWriteBlock->setStoredPackets(0);
        return HasReturnvaluesIF::RETURN_OK;
    }

    /**
     * Serializes the Index and calculates the CRC.
     * Parameters according to HasSerializeIF
     * @param buffer
     * @param size
     * @param maxSize
     * @param streamEndianness
     * @return
     */
    ReturnValue_t serialize(uint8_t** buffer, size_t* size,
                size_t maxSize, Endianness streamEndianness) const{
        uint8_t* crcBuffer = *buffer;
        uint32_t oldSize = *size;
        if(additionalInfo!=NULL){
            additionalInfo->serialize(buffer,size,maxSize,streamEndianness);
        }
        ReturnValue_t result = currentWriteBlock->serialize(buffer,size,maxSize,streamEndianness);
        if(result != HasReturnvaluesIF::RETURN_OK){
                    return result;
        }
        result = SerializeAdapter::serialize(&this->size,buffer,size,maxSize,streamEndianness);
        if(result != HasReturnvaluesIF::RETURN_OK){
                    return result;
        }

        uint32_t i = 0;
        while ((result == HasReturnvaluesIF::RETURN_OK) && (i < this->size)) {
            result = SerializeAdapter::serialize(&this->entries[i], buffer, size,
                    maxSize, streamEndianness);
            ++i;
        }
        if(result != HasReturnvaluesIF::RETURN_OK){
            return result;
        }
        uint16_t crc = Calculate_CRC(crcBuffer,(*size-oldSize));
        result = SerializeAdapter::serialize(&crc,buffer,size,maxSize,streamEndianness);
        return result;
    }


    /**
     * Get the serialized Size of the index
     * @return The serialized size of the index
     */
    size_t getSerializedSize() const {

        uint32_t size = 0;
        if(additionalInfo!=NULL){
            size += additionalInfo->getSerializedSize();
        }
        size += currentWriteBlock->getSerializedSize();
        size += SerializeAdapter::getSerializedSize(&this->size);
        size += (this->entries[0].getSerializedSize()) * this->size;
        uint16_t crc = 0;
        size += SerializeAdapter::getSerializedSize(&crc);
        return size;
    }
    /**
     * DeSerialize the Indexed Ring from a buffer, deSerializes the current write iterator
     * CRC Has to be checked before!
     * @param buffer
     * @param size
     * @param streamEndianness
     * @return
     */

    ReturnValue_t deSerialize(const uint8_t** buffer, size_t* size,
                Endianness streamEndianness){

        ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
        if(additionalInfo!=NULL){
            result = additionalInfo->deSerialize(buffer,size,streamEndianness);
        }
        if(result != HasReturnvaluesIF::RETURN_OK){
                    return result;
        }

        Index<T> tempIndex;
        result = tempIndex.deSerialize(buffer,size,streamEndianness);
        if(result != HasReturnvaluesIF::RETURN_OK){
                    return result;
        }
        uint32_t tempSize = 0;
        result = SerializeAdapter::deSerialize(&tempSize,buffer,size,streamEndianness);
        if(result != HasReturnvaluesIF::RETURN_OK){
                    return result;
        }
        if(this->size != tempSize){
            return HasReturnvaluesIF::RETURN_FAILED;
        }
        uint32_t i = 0;
        while ((result == HasReturnvaluesIF::RETURN_OK) && (i < this->size)) {
            result = SerializeAdapter::deSerialize(
                    &this->entries[i], buffer, size,
                    streamEndianness);
            ++i;
        }
        if(result != HasReturnvaluesIF::RETURN_OK){
                    return result;
        }
        typename IndexedRingMemoryArray<T>::Iterator cmp(&tempIndex);
        for(typename IndexedRingMemoryArray<T>::Iterator it= this->begin();it!=this->end();++it){
            if(*(cmp.value) == *(it.value)){
                currentWriteBlock = it;
                return HasReturnvaluesIF::RETURN_OK;
            }
        }
        //Reached if current write block iterator is not found
        return HasReturnvaluesIF::RETURN_FAILED;
    }

    uint32_t getIndexAddress() const {
        return indexAddress;
    }


    /*
     * Statistics
     */
    uint32_t getStoredPackets() const {
        uint32_t size = 0;
        for(typename IndexedRingMemoryArray<T>::Iterator it= this->begin();it!=this->end();++it){
            size += it->getStoredPackets();
        }
        return size;
    }

    uint32_t getTotalSize() const {
        return totalSize;
    }

    uint32_t getCurrentSize() const{
        uint32_t size = 0;
        for(typename IndexedRingMemoryArray<T>::Iterator it= this->begin();it!=this->end();++it){
            size += it->getSize();
        }
        return size;
    }

    bool isEmpty() const{
        return getCurrentSize()==0;
    }

    double getPercentageFilled() const{
        uint32_t filledSize = 0;
        for(typename IndexedRingMemoryArray<T>::Iterator it= this->begin();it!=this->end();++it){
            filledSize += it->getSize();
        }

        return (double)filledSize/(double)this->totalSize;
    }

    typename IndexedRingMemoryArray<T>::Iterator getCurrentWriteBlock() const{
        return currentWriteBlock;
    }
    /**
     * Get the next block of the currentWriteBlock.
     * Returns the first one if currentWriteBlock is the last one
     * @return Iterator pointing to the next block after currentWriteBlock
     */
    typename IndexedRingMemoryArray<T>::Iterator getNextWrite() const{
        typename IndexedRingMemoryArray<T>::Iterator next(currentWriteBlock);
        if((this->size != 0) && (currentWriteBlock.value == this->back())){
            next = this->begin();
        }else{
            ++next;
        }
        return next;
    }
    /**
     * Get the block in front of the Iterator
     * Returns the last block if it is the first block
     * @param it iterator which you want the previous block from
     * @return pointing to the block before it
     */
    typename IndexedRingMemoryArray<T>::Iterator getPreviousBlock(typename IndexedRingMemoryArray<T>::Iterator it) {
        if(this->begin() == it){
            typename IndexedRingMemoryArray<T>::Iterator next((this->back()));
            return next;
        }
        typename IndexedRingMemoryArray<T>::Iterator next(it);
        --next;
        return next;
    }
private:
    //The total size used by the blocks (without index)
    uint32_t totalSize;

    //The address of the index
    const uint32_t indexAddress;

    //The iterators for writing and reading
    typename IndexedRingMemoryArray<T>::Iterator currentWriteBlock;
    typename IndexedRingMemoryArray<T>::Iterator currentReadBlock;

    //How much of the current read block is read already
    uint32_t currentReadSize;

    //Cached Size of current read block
    uint32_t currentReadBlockSizeCached;

    //Last block of current write (should be write block)
    typename IndexedRingMemoryArray<T>::Iterator lastBlockToRead;
    //current size of last Block to read
    uint32_t lastBlockToReadSize;

    //Additional Info to be serialized with the index
    SerializeIF* additionalInfo;

    //Does it overwrite old blocks?
    const bool overwriteOld;

};




#endif /* FRAMEWORK_CONTAINER_INDEXEDRINGMEMORY_H_ */