/*
   Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.

   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; see the file COPYING. If not, write to the
   Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
   MA  02110-1301  USA.
*/


/* block.hpp provides word and byte blocks with configurable allocators
*/


#ifndef TAO_CRYPT_BLOCK_HPP
#define TAO_CRYPT_BLOCK_HPP

#include "misc.hpp"
#include <string.h>         // memcpy
#include <stddef.h>         // ptrdiff_t

#ifdef USE_SYS_STL
    #include <algorithm>
#else
    #include "algorithm.hpp"
#endif


namespace STL = STL_NAMESPACE;


namespace TaoCrypt {


// a Base class for Allocators
template<class T>
class AllocatorBase
{
public:
    typedef T         value_type;
    typedef size_t    size_type;
    typedef ptrdiff_t difference_type;
    typedef T*        pointer;
    typedef const T*  const_pointer;
    typedef T&        reference;
    typedef const T&  const_reference;

    pointer       address(reference r) const {return (&r);}
    const_pointer address(const_reference r) const {return (&r); }
    void          construct(pointer p, const T& val) {new (p) T(val);}
    void          destroy(pointer p) {p->~T();}
    size_type     max_size() const {return ~size_type(0)/sizeof(T);}
protected:
};


// General purpose realloc
template<typename T, class A>
typename A::pointer StdReallocate(A& a, T* p, typename A::size_type oldSize,
                                  typename A::size_type newSize, bool preserve)
{
    if (oldSize == newSize)
        return p;

    if (preserve) {
        A b = A();
        typename A::pointer newPointer = b.allocate(newSize, 0);
        memcpy(newPointer, p, sizeof(T) * min(oldSize, newSize));
        a.deallocate(p, oldSize);
        STL::swap(a, b);
        return newPointer;
    }
    else {
        a.deallocate(p, oldSize);
        return a.allocate(newSize, 0);
    }
}


// Allocator that zeros out memory on deletion
template <class T>
class AllocatorWithCleanup : public AllocatorBase<T>
{
public:
    typedef typename AllocatorBase<T>::pointer   pointer;
    typedef typename AllocatorBase<T>::size_type size_type;

    pointer allocate(size_type n, const void* = 0)
    {
        if (n > this->max_size())
            return 0;
        if (n == 0)
            return 0;
        return NEW_TC T[n];
    }

    void deallocate(void* p, size_type n)
    {
        memset(p, 0, n * sizeof(T));
        tcArrayDelete((T*)p);
    }

    pointer reallocate(T* p, size_type oldSize, size_type newSize,
                       bool preserve)
    {
        return StdReallocate(*this, p, oldSize, newSize, preserve);
    }

    // VS.NET STL enforces the policy of "All STL-compliant allocators have to
    // provide a template class member called rebind".
    template <class U> struct rebind { typedef AllocatorWithCleanup<U> other;};
};


// Block class template
template<typename T, class A = AllocatorWithCleanup<T> >
class Block {
public:
    explicit Block(word32 s = 0) : sz_(s), buffer_(allocator_.allocate(sz_)) 
                    { CleanNew(sz_); }

    Block(const T* buff, word32 s) : sz_(s), buffer_(allocator_.allocate(sz_))
        { memcpy(buffer_, buff, sz_ * sizeof(T)); }

    Block(const Block& that) : sz_(that.sz_), buffer_(allocator_.allocate(sz_))
        { memcpy(buffer_, that.buffer_, sz_ * sizeof(T)); }

    Block& operator=(const Block& that) {
        Block tmp(that);
        Swap(tmp);
        return *this;
    }

    T& operator[] (word32 i) { return buffer_[i]; }
    const T& operator[] (word32 i) const { return buffer_[i]; }

    T* operator+ (word32 i) { return buffer_ + i; }
    const T* operator+ (word32 i) const { return buffer_ + i; }

    word32 size() const { return sz_; }

    T* get_buffer() const { return buffer_; }
    T* begin()      const { return get_buffer(); }

    void CleanGrow(word32 newSize)
    {
        if (newSize > sz_) {
            buffer_ = allocator_.reallocate(buffer_, sz_, newSize, true);
            memset(buffer_ + sz_, 0, (newSize - sz_) * sizeof(T));
            sz_ = newSize;
        }
    }

    void CleanNew(word32 newSize)
    {
        New(newSize);
        if (sz_ > 0)
          memset(buffer_, 0, sz_ * sizeof(T));
    }

    void New(word32 newSize)
    {
        buffer_ = allocator_.reallocate(buffer_, sz_, newSize, false);
        sz_ = newSize;
    }

    void resize(word32 newSize)
    {
        buffer_ = allocator_.reallocate(buffer_, sz_, newSize, true);
        sz_ = newSize;
    }

    void Swap(Block& other) {
        STL::swap(sz_, other.sz_);
        STL::swap(buffer_, other.buffer_);
        STL::swap(allocator_, other.allocator_);
    }

    ~Block() { allocator_.deallocate(buffer_, sz_); }
private:
    A      allocator_;
    word32 sz_;     // size in Ts
    T*     buffer_;
};


typedef Block<byte>   ByteBlock;
typedef Block<word>   WordBlock;
typedef Block<word32> Word32Block;


} // namespace

#endif // TAO_CRYPT_BLOCK_HPP
