/*
 *
 *    soniK digital audio editor
 *    Copyright (C) 2003-2004, 2006  Robert Walker <rob@tenfoot.org.uk>
 *
 *    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; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    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
 *
 */

#ifndef SONIK_AUTO_BUFFER
#define SONIK_AUTO_BUFFER

#include <cstddef>
#include <cassert>

namespace Sonik
{
  /**
   * auto_buffer - smart array on heap
   */
  template <typename T>
  class auto_buffer
  {
  public:
    explicit auto_buffer(std::size_t s) throw()
      : mPtr(new T[s]), mSz(s), mCap(s) { }

    explicit auto_buffer(T* p=0,
                         std::size_t s=0,
                         std::size_t c=0) throw()
      : mPtr(p), mSz(s), mCap((c < s) ? s : c) { }
    /**
     * Copy constructors
     */
    auto_buffer(auto_buffer& a) throw()
      : mPtr(a.mPtr), mSz(a.mSz), mCap(a.mCap) { a.release(); }

    /**
     * Assignement operators
     */
    auto_buffer& operator=(auto_buffer& a) throw()
    {
      reset(a.mPtr, a.mSz, a.mCap);
      a.release();
      return *this;
    }

    virtual ~auto_buffer() { delete[] mPtr; }

    /**
     * Return ptr to the data
     */
    T*       data() throw()              { return mPtr; }
    const T* data() const throw()        { return mPtr; }

    /**
     * Return ptr to one past the last element
     * use as p != b.end()
     */
    T*       end() throw()               { return mPtr + mSz; }
    const T* end() const throw()         { return mPtr + mSz; }

    /**
     * array access operators - no bounds checking!
     */
    T&          operator[](std::size_t i) throw()       { return mPtr[i]; }
    const T&    operator[](std::size_t i) const throw() { return mPtr[i]; }

    /**
     * Return the current size of the buffer
     */
    std::size_t size()     const throw() { return mSz; }

    /**
     * Return the capacity of the buffer
     */
    std::size_t capacity() const throw() { return mCap; }

    /**
     * Change usable size of the data. Does NOT reallocate.
     */
    void resize(std::size_t sz) throw() { assert(sz <= mCap); mSz = sz; }

    /**
     * Reset ptr and size & return memory block
     */
    T* release() throw()
    {
      T* tmp = mPtr;
      mPtr = 0;
      mSz = 0;
      mCap = 0;
      return tmp;
    }

    /**
     * free memory (if allocated), reset ptr and size
     */
    void reset() throw()
    {
      reset(0, 0, 0);
    }

    /**
     * free memory (if allocated), reset ptr and size
     */
    void reset(std::size_t s) throw()
    {
      reset(new T[s], s, s);
    }

    /**
     * free memory (if allocated), reset ptr and size
     */
    void reset(T* p, std::size_t s, std::size_t c=0) throw()
    {
      if (p != mPtr)
      {
        delete[] mPtr;
        mPtr = p;
        mSz = s;
        mCap = (c < s) ? s : c;
      }
    }

    static void swap(auto_buffer& a, auto_buffer& b)
    {
      T*          tmpPtr = a.mPtr;
      std::size_t tmpSz = a.mSz;
      std::size_t tmpCap = a.mCap;
      a.mPtr  = b.mPtr;
      a.mSz   = b.mSz;
      a.mCap  = b.mCap;
      b.mPtr  = tmpPtr;
      b.mSz   = tmpSz;
      b.mCap  = tmpCap;
    }

  private:
    T*          mPtr;
    std::size_t mSz;
    std::size_t mCap;
  };

  template <typename T>
  class auto_buffer_2d
  {
  public:
    explicit auto_buffer_2d(std::size_t r, std::size_t s) throw()
      : mPtr(new T[r*s]), mRows(r), mSz(s), mCap(s) { }

    explicit auto_buffer_2d(T* p=0,
                            std::size_t r=0,
                            std::size_t s=0,
                            std::size_t c=0) throw()
      : mPtr(p), mRows(r), mSz(s), mCap((c < s) ? s : c) { }
    /**
     * Copy constructors
     */
    auto_buffer_2d(auto_buffer_2d& a) throw()
      : mPtr(a.mPtr), mRows(a.mRows), mSz(a.mSz), mCap(a.mCap) { a.release(); }

    /**
     * Assignement operators
     */
    auto_buffer_2d& operator=(auto_buffer_2d& a) throw()
    {
      reset(a.mPtr, a.mRows, a.mSz, a.mCap);
      a.release();
      return *this;
    }

    virtual ~auto_buffer_2d() { delete[] mPtr; }

    /**
     * Return ptr to the data
     */
    T*       data(std::size_t r) throw()       { return mPtr + (r * mCap); }
    const T* data(std::size_t r) const throw() { return mPtr + (r * mCap); }

    /**
     * Return ptr to one past the last element
     * use as p != b.end()
     */
    T*       end(std::size_t r) throw()        { return mPtr + (r * mCap) + mSz; }
    const T* end(std::size_t r) const throw()  { return mPtr + (r * mCap) + mSz; }

    /**
     * array access operators
     */
    T*          operator[](std::size_t r) throw()       { return data(r); }
    const T*    operator[](std::size_t r) const throw() { return data(r); }

    /**
     * Return the number of rows of the buffer
     */
    std::size_t rows()     const throw() { return mRows; }

    /**
     * Return the current size of the buffer
     */
    std::size_t size()     const throw() { return mSz; }

    /**
     * Return the capacity of the buffer
     */
    std::size_t capacity() const throw() { return mCap; }

    /**
     * Change usable size of the data. Does NOT reallocate.
     */
    void resize(std::size_t sz) throw() { assert(sz <= mCap); mSz = sz; }

    /**
     * Reset ptr and size & return memory block
     */
    T* release() throw()
    {
      T* tmp = mPtr;
      mPtr = 0;
      mRows = 0;
      mSz = 0;
      mCap = 0;
      return tmp;
    }

    /**
     * free memory (if allocated), reset ptr and size
     */
    void reset() throw()
    {
      reset(0, 0, 0, 0);
    }

    /**
     * free memory (if allocated), reset ptr and size
     */
    void reset(std::size_t r, std::size_t s) throw()
    {
      reset(new T[r*s], r, s, s);
    }

    /**
     * free memory (if allocated), reset ptr and size
     */
    void reset(T* p, std::size_t r, std::size_t s, std::size_t c=0) throw()
    {
      if (p != mPtr)
      {
        delete[] mPtr;
        mPtr = p;
        mRows = r;
        mSz = s;
        mCap = (c < s) ? s : c;
      }
    }

    static void swap(auto_buffer_2d& a, auto_buffer_2d& b)
    {
      T*          tmpPtr = a.mPtr;
      std::size_t tmpRows = a.mRows;
      std::size_t tmpSz = a.mSz;
      std::size_t tmpCap = a.mCap;
      a.mPtr  = b.mPtr;
      a.mRows = b.mRows;
      a.mSz   = b.mSz;
      a.mCap  = b.mCap;
      b.mPtr  = tmpPtr;
      b.mRows = tmpRows;
      b.mSz   = tmpSz;
      b.mCap  = tmpCap;
    }

  private:
    T*          mPtr;
    std::size_t mRows;
    std::size_t mSz;
    std::size_t mCap;
  };
}

#endif // SONIK_AUTO_BUFFER
