/**
 *  @file   mem1_allocator.h
 *  @brief  固定領域から固定サイズのメモリを1つづアロケートするアロケータ
 *  @author tenk*
 *  @date   2004,2010
 *  @note
 *  -   mem1_pool_alloc<SIZE,ALN> は、指定したアドレスからの固定のメモリを
 *      プールとして、特定サイズのメモリを1つづつ取得するアロケータ.
 *  -   static_mem1_allocator<T,BYTES,ALN> は、mem1_pool_allocを使って
 *      BYTESバイトのstatic配列を確保してT型のメモリを取得する
 *      std::allocator風のアロケータ
 *  -   Public Domain Software
 */
#ifndef MEM1_ALLOCATOR_H
#define MEM1_ALLOCATOR_H

#include <cstring>
#ifndef assert
#include <cassert>
#endif


// ============================================================================
//
// ============================================================================

#ifndef MEM1_POOL_ALLOC_DEFINED
#define MEM1_POOL_ALLOC_DEFINED

/** bufからbufSzバイトのメモリから、cellSzバイトのメモリをアロケートする.
 *  - セルサイズ/アライメントは、最小で sizeof(void*) バイトになる.
 *  - ALN は、sizeof(void*)より大きなアライメントにしたい場合用(doubleなりint64_tなり).
 */
template<typename SIZE=unsigned, typename ALN=void* >
class mem1_pool_alloc {
public:
    mem1_pool_alloc (std::size_t cellSz=0, void* buf=0, std::size_t bufSz=0) throw();
    void        init(std::size_t cellSz=0, void* buf=0, std::size_t bufSz=0) throw();

    void*       alloc();                            ///< cellSzバイトのメモリ取得.
    bool        dealloc(void* p);                   ///< alloc()で取得したメモリの開放.
    std::size_t alloc_bytes() const;                ///< 1アロケートのサイズ(バイト数).
    std::size_t capacity() const {return cellNum_;} ///< 最大アロケート数.
    bool        isAllocPtr(const void* p) const;    ///< pはこれでallocしたポインタか?
    void        CHECK_PTR(const void *p) const;     ///< ポインタの正当性チェック.

private:
    /// 空きメモリをリンクして管理するための型
    union Unit {
        Unit*   link_;                  ///< リンク.
        ALN     aln_;                   ///< アライメント用.
    };
    Unit*       unitBuf_;               ///< unit単位で管理するメモリ.
    Unit*       unitBufEnd_;            ///< unit単位で管理するメモリ.
    Unit*       base_;                  ///< 空きメモリ管理の基点.
    SIZE        cellUnits_;             ///< 1要素のUnit単位の値.
    SIZE        cellNum_;               ///< 確保可能な要素数.
};



/** デバッグ・コンパイル時の、ポインタの正当性チェック.
 */
template<typename SIZE, typename ALN>
inline void mem1_pool_alloc<SIZE, ALN>::CHECK_PTR(const void *p) const {
  #ifndef NDEBUG
    const Unit  *cur = reinterpret_cast<const Unit*>(p);
    assert(unitBuf_ <= cur && cur < unitBufEnd_);
    assert((cur-unitBuf_) % cellUnits_ == 0);
  #endif
}


/// 1アロケートのサイズ(バイト数).
template<typename SIZE, typename ALN>
inline std::size_t mem1_pool_alloc<SIZE,ALN>::alloc_bytes() const {
    return sizeof(Unit) * cellUnits_;
}


/// 1アロケートのサイズ(バイト数).
template<typename SIZE, typename ALN> inline
bool    mem1_pool_alloc<SIZE,ALN>::isAllocPtr(const void* p) const {
    return (char*)unitBuf_ <= (char*)p && (char*)p < (char*)unitBufEnd_;
}


/** コンストラクタ.
 */
template<typename SIZE, typename ALN>
mem1_pool_alloc<SIZE,ALN>::mem1_pool_alloc( std::size_t cellSz, void* buf, std::size_t bufSz ) throw()
    : unitBuf_(0), unitBufEnd_(0), base_(0), cellUnits_(0), cellNum_(0)
{
    if (buf != 0)
        init(cellSz, buf, bufSz);
}


/** 初期化. アドレスbufからのbufSzバイトを、cellSzバイト単位に管理.
 */
template<typename SIZE, typename ALN>
void mem1_pool_alloc<SIZE,ALN>::init( std::size_t cellSz, void* buf, std::size_t bufSz ) throw()
{
    if (unitBuf_ != 0)
        return;
    assert( (std::size_t(buf) % sizeof(Unit)) == 0 );       // アライメントチェック
    assert( cellSz >= sizeof(Unit) || (cellSz == 0 && buf == 0 && bufSz == 0) );
    assert( bufSz >= cellSz  || bufSz == 0  );

    unitBuf_        = reinterpret_cast<Unit*>(buf);
    cellNum_        = cellSz > 0 ? bufSz / cellSz : 0;
    cellUnits_      = (cellSz + sizeof(Unit)-1) / sizeof(Unit);
    std::size_t cellBytes = alloc_bytes();
    assert( cellSz == cellBytes );
    base_           = 0;
    unitBufEnd_     = &unitBuf_[ cellUnits_ * cellNum_ ];

    if (buf && cellNum_ > 0) {
        using std::memset;
        memset(buf, 0, bufSz);
        base_       = &unitBuf_[0];
        for (std::size_t i = 0; i < cellNum_-1; ++i) {
            unitBuf_[i * cellUnits_].link_ = &unitBuf_[(i+1) * cellUnits_];
        }
        unitBuf_[(cellNum_-1) * cellUnits_].link_ = NULL;
    }
}


/** 1つ分メモリ(cellSzバイト)をアロケート.
 */
template<typename SIZE, typename ALN>
inline void* mem1_pool_alloc<SIZE,ALN>::alloc() {
    Unit*   p   = base_;
    if (p) {
      #ifndef NDEBUG
        CHECK_PTR( p );
        if ( p->link_ ) CHECK_PTR(p->link_);
      #endif
        base_ = p->link_;
    }
    return p;
}


/** alloc()されてたメモリを開放.
 */
template<typename SIZE, typename ALN>
inline bool mem1_pool_alloc<SIZE,ALN>::dealloc(void* p) {
    if (p) {
        Unit*   u    = reinterpret_cast<Unit*>(p);
      #ifndef NDEBUG
        CHECK_PTR( p );
      #endif
        u->link_     = base_;
        base_        = u;
        return 1;
    }
    return 0;
}


#endif  // MEM1_POOL_ALLOC_DEFINED




// ============================================================================
//
// ============================================================================

/// メモリ不足時にエラー扱いするためのクラス.
template< typename T >
class mem1_allocator_no_mem {
public:
    static T*   allocate(std::size_t) {assert(0&&"not enough memory");return 0;}
    static void deallocate(T*)        {assert(0&&"bad pointer"); }
};



// ============================================================================
//
// ============================================================================

/// 固定領域から固定サイズのメモリを1つづアロケートするアロケータ.
/// BYTES分のメモリを予めstaticに確保し、そこからアロケートする.
/// ※アプリケーション内で使う量が一定範囲とわかっている場合用.
/// Aに別のallocatorを指定すればメモリ不足時、そちらから確保する.
template< typename T, unsigned BYTES, class A=mem1_allocator_no_mem<T> >
class static_mem1_allocator : public A {
public:
    typedef T               value_type;
    typedef T*              pointer;
    typedef const T*        const_pointer;
    typedef T&              reference;
    typedef const T&        const_reference;
    typedef std::size_t     size_type;
    //typedef unsigned      size_type;
    typedef A               base_type;
    typedef std::ptrdiff_t  difference_type;

    static_mem1_allocator() { s_mem1_pool_.init(sizeof(T), s_buf_, BYTES); }
    static_mem1_allocator(const static_mem1_allocator&) {}
    ~static_mem1_allocator() throw() {}

    static pointer          address(reference       x) {return &x;}
    static const_pointer    address(const_reference x) {return &x;}

    static pointer allocate(size_type n,const void *) {return allocate(n);}

    static pointer allocate(size_type n) {
        assert(n == 1);
        pointer  p = (pointer)s_mem1_pool_.alloc();
        if (p == 0)
            p = base_type::allocate(n);
        return p;
    }

    static void deallocate(pointer p, size_type)    {deallocate(p);}

    static void deallocate(pointer p) {
        if (s_mem1_pool_.isAllocPtr(p))
            s_mem1_pool_.dealloc(p);
        else
            base_type::deallocate(p);
    }

    static void construct(pointer p, const T& val)  {new((void*)p) T(val);}
    static void construct(pointer p)                {new((void*)p) T;}
    static void destroy(pointer p)                  {p->~T();}

    static size_type max_size() throw() {return ~0; }

    bool operator ==(const static_mem1_allocator& r) const {return true;}
    bool operator !=(const static_mem1_allocator& r) const {return !(*this == r);}

private:
    static mem1_pool_alloc<>    s_mem1_pool_;
    static double               s_buf_[ (BYTES+sizeof(double)-1) / sizeof(double) ];
};


template< typename T, unsigned BYTES, class A >
mem1_pool_alloc<> static_mem1_allocator<T,BYTES,A>::s_mem1_pool_;

template< typename T, unsigned BYTES, class A >
double            static_mem1_allocator<T,BYTES,A>::s_buf_[ (BYTES+sizeof(double)-1) / sizeof(double) ];


#endif  // MEM1_ALLOCATOR_H