/* Copyright (C) 2014 InfiniDB, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; version 2 of
   the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA. */

/******************************************************************************************
 * $Id$
 *
 ******************************************************************************************/

/* This allocator is for frequent small allocations that all get deallocated at once.
   It allocates large blocks of memory from the system and distributes 'allocsize'
   units to the caller.  When the large allocation is used up, it will allocate
   more unless the 'tmpspace' flag is set.  If it is, it will reuse the memory it
   already allocated.  This is useful for short-lived vars that are guaranteed to be
   out of scope by the time the allocator wraps around.

   TODO: make it STL and boost compliant...
*/

#pragma once

#include <stdint.h>

#include <optional>
#include <vector>
#include <unistd.h>
#include <atomic>

#include <boost/smart_ptr/allocate_shared_array.hpp>

#include "countingallocator.h"
#include "spinlock.h"

#define EXPORT

namespace utils
{
using FixedAllocatorBufIntegralType = uint8_t;
using FixedAllocatorBufType = FixedAllocatorBufIntegralType[];

class FixedAllocator
{
 public:
  EXPORT static const unsigned long DEFAULT_NUM_ELEMENTS = (4096 * 4);  // should be a multiple of pagesize

  EXPORT FixedAllocator()
   : capacityRemaining(0)
   , elementCount(DEFAULT_NUM_ELEMENTS)
   , elementSize(0)
   , currentlyStored(0)
   , tmpSpace(false)
   , nextAlloc(0)
   , useLock(false)
   , lock(false)
  {
  }

  EXPORT explicit FixedAllocator(allocators::CountingAllocator<FixedAllocatorBufType> alloc, unsigned long allocSize, bool isTmpSpace = false,
                                 unsigned long numElements = DEFAULT_NUM_ELEMENTS)
   : capacityRemaining(0)
   , elementCount(numElements)
   , elementSize(allocSize)
   , currentlyStored(0)
   , tmpSpace(isTmpSpace)
   , nextAlloc(0)
   , useLock(false)
   , lock(false)
   , alloc(alloc)
  {
  }

  EXPORT FixedAllocator(const FixedAllocator&);
  EXPORT FixedAllocator& operator=(const FixedAllocator&);
  virtual ~FixedAllocator()
  {
  }

  EXPORT void* allocate();
  EXPORT void* allocate(
      uint32_t len);  // a hack to make it work more like a pool allocator (use PoolAllocator instead)
  EXPORT void truncateBy(uint32_t amt);  // returns a portion of mem just allocated; use with caution
  void deallocate()
  {
  }                             // does nothing
  EXPORT void deallocateAll();  // drops all memory in use
  EXPORT uint64_t getMemUsage() const;
  void setUseLock(bool);
  void setAllocSize(uint32_t);

 private:
  void newBlock();

  std::vector<boost::shared_ptr<FixedAllocatorBufType>> mem;
  unsigned long capacityRemaining;
  uint64_t elementCount;
  unsigned long elementSize;
  uint64_t currentlyStored;
  bool tmpSpace;
  FixedAllocatorBufIntegralType* nextAlloc;
  bool useLock;
  std::atomic<bool> lock;
  std::optional<allocators::CountingAllocator<FixedAllocatorBufType>> alloc {};
};

inline void* FixedAllocator::allocate()
{
  void* ret;

  if (useLock)
    getSpinlock(lock);
  if (capacityRemaining < elementSize)
    newBlock();

  ret = nextAlloc;
  nextAlloc += elementSize;
  capacityRemaining -= elementSize;
  currentlyStored += elementSize;
  if (useLock)
    releaseSpinlock(lock);
  return ret;
}

inline void* FixedAllocator::allocate(uint32_t len)
{
  void* ret;

  if (useLock)
    getSpinlock(lock);
  if (capacityRemaining < len)
    newBlock();

  ret = nextAlloc;
  nextAlloc += len;
  capacityRemaining -= len;
  currentlyStored += len;
  if (useLock)
    releaseSpinlock(lock);
  return ret;
}

#undef EXPORT
}  // namespace utils
