/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Array2D class.
 *//*
 * LEGAL:   COPYRIGHT (C) 2007 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#ifndef BASE_ARRAY_2D_HH
#define BASE_ARRAY_2D_HH 1

#include <vector>
#include "base/types.hh"
#include "base/clamp.hh"
#include "base/defs.hh"

namespace base {

////////////////////////////////////////////////////////////////////////////////
/// @brief 2D array that clamps indexs.
///
/// Indexs are signed, rather than unsigned, to clamp negative indexs at 0.
///
/// Array2D is allowed to be initially empty which is defined as sx=sy=0.
/// Methods that access elements become invalid when an array is empty.
///
template<typename T,typename BASE=Void>  // optionally, BASE can be Shared
class Array2D : public BASE
{
COPYABLE(Array2D)
public:
    Array2D( void )
    :   mSx(0),
        mSy(0),
        mSxM1(0),
        mSyM1(0),
        mVec()
    {
        resize( 0, 0 );  // does assertions
    }

    /// (sx,sy) defines 2D size.
    Array2D( const int sx, const int sy )
    :   mSx(sx),
        mSy(sy),
        mSxM1(sx-1),
        mSyM1(sy-1),
        mVec()
    {
        resize( sx, sy );  // does assertions
    }

    ~Array2D()
    {
    }

    void resize( const int sx, const int sy )
    {
    ASSERT( (sx >= 0) and (sy >= 0) );
    ASSERT( not ((sx == 0) and (sy  > 0)) );  // allow empty array defined as sx=sy=0
    ASSERT( not ((sx  > 0) and (sy == 0)) );  // but don't allow only one dim to be 0
    ASSERT( uint(sx * sy) < base::defs::MAX_ELEMS );

        mSx   = sx;
        mSy   = sy;
        mSxM1 = sx - 1;  // to speed Index1D()
        mSyM1 = sy - 1;
        mVec.resize( sx * sy );

    DEBUG_CODE( if ( not empty() ) { ASSERT( (mSxM1 >= 0) and (mSyM1 >= 0) ); } )  // invalid anyway if empty
    }

    void clear( void )
    {
        resize( 0, 0 );
    }

    uint size( void ) const
    {
    ASSERT( uint(mSx * mSy) == mVec.size() );

        return mVec.size();
    }

    uint width( void ) const
    {
        return mSx;
    }

    uint height( void ) const
    {
        return mSy;
    }

    bool empty( void ) const
    {
    ASSERT( bool(mSx|mSy) == (not mVec.empty()) );

        return (mSx | mSy) == 0;
    }

    /// @return Convert 2D index to 1D index.
    uint Index1D( int x, int y ) const
    {
    ASSERT( not empty() );

        // Clamp the 2D indexs in range {0,..,size-1}.
        x = Clamp( x, mSxM1 );
        y = Clamp( y, mSyM1 );

        // Compute 1D index into underlying array.
        // Formula follows from this order of "for" loops.
        // Each x has to fully iterate y, hence, (x * mSy).
        // for ( x = 0; x < mSx; ++x )
        // for ( y = 0; y < mSy; ++y )

        const uint idx = (x * mSy) + y;
    ASSERT( idx < mVec.size() );
        return idx;
    }

    /// Write to element by its 2D coordinates.
    /// The order of args of Set() is similar to:
    /// mArray[x][y] = val;
    /// mArray.Set( x, y, val );
    void Set( const int x, const int y, const T& val )
    {
    ASSERT( not empty() );

        mVec[Index1D(x,y)] = val;
    }

    /// R/W access to element by its 2D coordinates.
    /// Can be used to write to element (Set() is more sensible).
    T& Get( const int x, const int y )
    {
    ASSERT( not empty() );

        return mVec[Index1D(x,y)];
    }

    /// R/O access to element by its 2D coordinates.
    const T& Get( const int x, const int y ) const
    {
    ASSERT( not empty() );

        return mVec[Index1D(x,y)];
    }

    /// R/W access to element by its 1D index.
    T& operator[]( const uint index1D )
    {
    ASSERT( not empty() );
    ASSERT( index1D < mVec.size() );

        return mVec[index1D];
    }

    /// R/O access to element by its 1D index.
    const T& operator[]( const uint index1D ) const
    {
    ASSERT( not empty() );
    ASSERT( index1D < mVec.size() );

        return mVec[index1D];
    }

private:
    int             mSx;      ///< size of X dimension
    int             mSy;      ///< size of Y dimension
    int             mSxM1;    ///< mSx - 1
    int             mSyM1;    ///< mSx - 1
    std::vector<T>  mVec;     ///< actual array
};

} // namespace base

#endif // BASE_ARRAY_2D_HH
