//===- GCFactory.h --------------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef MCLD_GC_FACTORY_H
#define MCLD_GC_FACTORY_H
#ifdef ENABLE_UNITTEST
#include <gtest.h>
#endif
#include "mcld/ADT/TypeTraits.h"
#include "mcld/Support/Allocators.h"

#include <assert.h>
#include <cstddef>
#include <iterator>

namespace mcld
{

/** \class DataIteratorBase
 *  \brief DataIteratorBase provides the basic functions of DataIterator
 *  @see DataIterator
 */
template<typename ChunkType>
struct DataIteratorBase
{
public:
  ChunkType* m_pChunk;
  unsigned int m_Pos;

public:
  DataIteratorBase(ChunkType* X, unsigned int pPos)
  : m_pChunk(X), m_Pos(pPos)
  { }

  inline void advance() {
    ++m_Pos;
    if ((m_Pos == m_pChunk->bound) && (0 == m_pChunk->next))
      return;
    if (m_Pos == m_pChunk->bound) {
      m_pChunk = m_pChunk->next;
      m_Pos = 0;
    }
  }

  bool operator==(const DataIteratorBase& y) const
  { return ((this->m_pChunk == y.m_pChunk) && (this->m_Pos == y.m_Pos)); }

  bool operator!=(const DataIteratorBase& y) const
  { return ((this->m_pChunk != y.m_pChunk) || (this->m_Pos != y.m_Pos)); }
};

/** \class DataIterator
 *  \brief DataIterator provides STL compatible iterator for allocators
 */
template<typename ChunkType, class Traits>
class DataIterator : public DataIteratorBase<ChunkType>
{
public:
  typedef typename ChunkType::value_type  value_type;
  typedef Traits                          traits;
  typedef typename traits::pointer        pointer;
  typedef typename traits::reference      reference;
  typedef DataIterator<ChunkType, Traits> Self;
  typedef DataIteratorBase<ChunkType>     Base;

  typedef typename traits::nonconst_traits         nonconst_traits;
  typedef DataIterator<ChunkType, nonconst_traits> iterator;
  typedef typename traits::const_traits            const_traits;
  typedef DataIterator<ChunkType, const_traits>    const_iterator;
  typedef std::forward_iterator_tag                iterator_category;
  typedef size_t                                   size_type;
  typedef ptrdiff_t                                difference_type;

public:
  DataIterator()
  : Base(0, 0)
  { }

  DataIterator(ChunkType* pChunk, unsigned int pPos)
  : Base(pChunk, pPos)
  { }

  DataIterator(const DataIterator& pCopy)
  : Base(pCopy.m_pChunk, pCopy.m_Pos)
  { }

  ~DataIterator()
  { }

  // -----  operators  ----- //
  reference operator*() {
    if (0 == this->m_pChunk)
      assert(0 && "data iterator goes to a invalid position");
    return this->m_pChunk->data[Base::m_Pos];
  }

  Self& operator++() {
    this->Base::advance();
    return *this;
  }

  Self operator++(int) {
    Self tmp = *this;
    this->Base::advance();
    return tmp;
  }
};

template<typename Alloc>
class GCFactoryBase : public Alloc
{
public:
  typedef DataIterator<typename Alloc::chunk_type,
                       NonConstTraits<
                         typename Alloc::value_type> > iterator;
  typedef DataIterator<typename Alloc::chunk_type,
                       ConstTraits<
                         typename Alloc::value_type> > const_iterator;

  typedef typename Alloc::value_type value_type;
  typedef typename Alloc::pointer    pointer;
  typedef typename Alloc::reference  reference;
  typedef typename Alloc::size_type  size_type;

protected:
  GCFactoryBase()
  : Alloc(), m_NumAllocData(0)
  { }

  GCFactoryBase(size_t pNum)
  : Alloc(pNum), m_NumAllocData(0)
  { }

public:
  virtual ~GCFactoryBase()
  { Alloc::clear(); }

  // -----  modifiers  ----- //
  value_type* allocate(size_t N) {
    value_type* result = Alloc::allocate(N);
    if (0 != result)
      m_NumAllocData += N;
    return result;
  }

  value_type* allocate() {
    ++m_NumAllocData;
    return Alloc::allocate();
  }

  void deallocate(pointer &pPtr, size_type N) {
    Alloc::deallocate(pPtr, N);
    if (0 == pPtr)
      m_NumAllocData -= N;
  }

  void deallocate(pointer &pPtr) {
    Alloc::deallocate(pPtr);
    if (0 == pPtr)
      --m_NumAllocData;
  }

  void reset() {
    Alloc::reset();
    m_NumAllocData = 0;
  }

  // -----  iterators  ----- //
  iterator begin()
  { return iterator(Alloc::m_pRoot, 0); }

  const_iterator begin() const
  { return const_iterator(Alloc::m_pRoot, 0); }

  iterator end() {
    return (0 == Alloc::m_pCurrent)?
             begin():
             iterator(Alloc::m_pCurrent, Alloc::m_pCurrent->bound);
  }

  const_iterator end() const {
    return (0 == Alloc::m_pCurrent)?
             begin():
             const_iterator(Alloc::m_pCurrent, Alloc::m_pCurrent->bound);
  }

  // -----  observers  ----- //
  bool empty() const
  { return Alloc::empty(); }

  unsigned int capacity() const
  { return Alloc::max_size(); }

  unsigned int size() const
  { return m_NumAllocData; }

protected:
  unsigned int m_NumAllocData;
};

/** \class GCFactory
 *  \brief GCFactory provides a factory that guaratees to remove all allocated
 *  data.
 */
template<typename DataType, size_t ChunkSize>
class GCFactory : public GCFactoryBase<LinearAllocator<DataType, ChunkSize> >
{
public:
  GCFactory()
  : GCFactoryBase<LinearAllocator<DataType, ChunkSize> >()
  { }
};

template<typename DataType>
class GCFactory<DataType, 0> : public GCFactoryBase<LinearAllocator<DataType, 0> >
{
public:
  GCFactory(size_t pNum)
  : GCFactoryBase<LinearAllocator<DataType, 0> >(pNum)
  { }
};

} // namespace of mcld

#endif