/*========================== begin_copyright_notice ============================

Copyright (C) 2017-2021 Intel Corporation

SPDX-License-Identifier: MIT

============================= end_copyright_notice ===========================*/

#pragma once

#include "cif/builtins/memory/buffer/buffer.h"

#include "cif/export/pimpl_base.h"

#include "cif/macros/enable.h"

#include "cif/helpers/memory.h"

namespace CIF {

namespace Builtins {

CIF_DECLARE_INTERFACE_PIMPL(Buffer) : CIF::PimplBase {
    CIF_PIMPL_DECLARE_CONSTRUCTOR(Version_t version, CIF::ICIF *parentInterface) {
    }

    CIF_PIMPL_DECLARE_CONSTRUCTOR(Version_t version) {
    }

    CIF_PIMPL_DECLARE_CONSTRUCTOR() {
    }

    CIF_PIMPL_DECLARE_DESTRUCTOR(){
        Deallocate();
    }

  /// Copies given element to the end of the buffer
  /// Note : If (packed == false), then this function will automatically align current underlying buffer
  ///        pointer to alignof(ElementT)
  template<typename ElementT>
  bool PushBackRawCopy(const ElementT &newEl, bool packed = true){
      static_assert(std::is_pod<ElementT>::value, "Supporting only POD types");
      if(packed == false){
          size_t alignment = alignof(ElementT);
          bool success = AlignUp(alignment);
          if(success == false){
              return false;
          }
      }

      return PushBackRawBytes(&newEl, sizeof(ElementT));
  }

    /// Sets custom allocator and deallocator functions to be used by this buffer interface
    /// Note : will reallocate using new allocators only when additional space will be needed
    /// Note : reallocator is optional and can be nullptr
    void SetAllocator(AllocatorT allocator, DeallocatorT deallocator, ReallocatorT reallocator){
        if(allocator == nullptr){
            allocator = EmptyAllocator;
        }

        if((this->memory) && (this->tempDeallocator == nullptr)){
            this->tempDeallocator = this->deallocator;
        }

        this->allocator = allocator;
        this->deallocator = deallocator;
        this->reallocator = reallocator;
    }

    /// Sets underlying buffer storage and its deallocator
    /// Note : will destroy current underlying buffer (if any)
    void SetUnderlyingStorage(void *memory, size_t size, DeallocatorT deallocator){
        Deallocate();

        this->memory = memory;
        this->size = size;
        this->capacity = size;
        this->tempDeallocator = deallocator;
    }

    /// Sets underlying constant buffer storage and its deallocator
    /// Note : will destroy current underlying buffer (if any)
    /// Note : will allocate new memory if this const memory will be accessed in non-const manners
    void SetUnderlyingStorage(const void *memory, size_t size){
        Deallocate();

        // casting-away constness, but marking as const
        this->memory = const_cast<void*>(memory);
        this->isConst = true;

        this->size = size;
        this->capacity = size;
        this->tempDeallocator = nullptr;
    }

    /// Detaches and returns current allocation leaving this buffer's underlying buffer empty
    void *DetachAllocation(){
        void *mem = memory;
        this->memory = nullptr;
        this->size = 0;
        this->capacity = 0;
        this->isConst = false;
        this->tempDeallocator = nullptr;
        return mem;
    }

    /// Returns const raw access to underlying buffer
    const void *GetMemoryRaw() const{
        return memory;
    }

    /// Returns writeable access to underlyng buffer
    /// Note : for constant buffer this will cause reallocation (copy!) to writeable memory
    void *GetMemoryRawWriteable(){
        if(this->isConst && this->capacity != 0){
            if(false == Reallocate(this->size, this->capacity)){
                return nullptr;
            }
        }
        return memory;
    }

    /// Returns size in bytes of underlying buffer
    size_t GetSizeRaw() const{
        return size;
    }

    /// Returns capacity in bytes of underlying buffer
    size_t GetCapacityRaw() const{
        return capacity;
    }

    /// Resizes (may reallocate) underlying buffer's size
    bool Resize(size_t newSize) {
        return this->Reallocate(newSize, std::max(this->capacity, newSize));
    }

    /// Resizes (may reallocate) the underlying buffer's capacity
    bool Reserve(size_t newCapacity){
        return this->Reallocate(this->size, newCapacity);
    }

    /// Sets the size to 0
    void Clear(){
        this->size = 0;
    }

    /// Sets the size to 0 and deallocates underlying buffer
    void Deallocate(){
        if(isConst){
            this->memory = nullptr;
            this->size = 0;
            this->capacity = 0;
            this->isConst = false;
            this->tempDeallocator = nullptr;
        }else if(tempDeallocator){
            tempDeallocator(this->memory);
            this->memory = nullptr;
            this->size = 0;
            this->capacity = 0;
            this->tempDeallocator = nullptr;
        }else{
            if(deallocator != nullptr){
                deallocator(this->memory);
            }
            this->memory = nullptr;
            this->size = 0;
            this->capacity = 0;
        }
    }

    /// Aligns up current size to meet required alignment
    bool AlignUp(uint32_t alignment){
        size_t misalligned = alignment - (this->size % alignment);
        size_t newSize = this->size + misalligned;
        return Reallocate(newSize, newSize);
    }

    /// Pushes new raw element to buffer
    bool PushBackRawBytes(const void *newData, size_t size){
        if(size == 0){
            // nothing to do
            return true;
        }

        size_t oldSize = this->size;
        if(false == Reallocate(this->size + size, this->size + size)){
            return false;
        }

        CIF::SafeCopy(CIF::OffsetedPtr(GetMemoryRawWriteable(), oldSize), this->capacity - oldSize, newData, size);
        return true;
    }

    /// Can be true if SetUnderlyingStorage(const void *, size_t) was used, please refer to SetUnderlyingStorage
    bool IsConst() const{
        return isConst;
    }

protected:
    static void * CIF_CALLING_CONV EmptyAllocator(size_t size){
        return nullptr;
    }

    static void * CIF_CALLING_CONV DefaultAllocator(size_t size){
        return new(std::nothrow) char[size];
    }

    static void CIF_CALLING_CONV DefaultDeallocator(void * memory){
        if(memory != nullptr){
            delete []reinterpret_cast<char*>(memory);
        }
    }

    bool Reallocate(size_t newSize, size_t newCapacity){
        if(isConst){
            void * newMem = this->allocator(newCapacity);
            if(newMem == nullptr){
                return false;
            }

            size_t bytesToCopy = std::min(newSize, this->size);
            CIF::SafeCopy(newMem, newCapacity, this->memory, bytesToCopy);
            this->memory = newMem;
            this->isConst = false;
            this->tempDeallocator = nullptr;
            this->capacity = newCapacity;
            this->size = newSize;
            return true;
        }

        if(tempDeallocator != nullptr){
            if(newCapacity <= this->capacity){
                this->size = newSize;
                return true;
            }

            void * newMem = this->allocator(newCapacity);
            if(newMem == nullptr){
                return false;
            }
            size_t bytesToCopy = std::min(newSize, this->size);
            CIF::SafeCopy(newMem, newCapacity, this->memory, bytesToCopy);
            void *oldMemory = this->memory;
            this->memory = newMem;
            this->capacity = newCapacity;
            this->size = newSize;
            this->tempDeallocator(oldMemory);
            this->tempDeallocator = nullptr;
            return true;
        }

        if(newCapacity <= this->capacity){
            this->size = newSize;
            return true;
        }

        if(reallocator != nullptr){
            void *newMem = reallocator(this->memory, this->capacity, newCapacity);
            if(newMem == nullptr){
                return false;
            }

            this->memory = newMem;
            this->capacity = newCapacity;
            this->size = newSize;
            return true;
        }

        void *newMem = allocator(newCapacity);
        if(newMem == nullptr){
            return false;
        }

        size_t bytesToCopy = std::min(newSize, this->size);
        CIF::SafeCopy(newMem, newCapacity, this->memory, bytesToCopy);
        if(deallocator != nullptr){
            deallocator(this->memory);
        }
        this->memory = newMem;
        this->capacity = newCapacity;
        this->size = newSize;

        return true;
    }

    void *memory = nullptr;
    size_t size = 0;
    size_t capacity = 0;
    bool isConst = false;
    DeallocatorT tempDeallocator = nullptr;
    AllocatorT allocator = DefaultAllocator;
    DeallocatorT deallocator = DefaultDeallocator;
    ReallocatorT reallocator = nullptr;
};

CIF_DEFINE_INTERFACE_TO_PIMPL_FORWARDING_CTOR_DTOR(Buffer);

}

}

#include "cif/macros/disable.h"
