/*****************************************************************************
 * $LastChangedDate: 2010-01-22 18:02:35 -0500 (Fri, 22 Jan 2010) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   RGBA class.
 *//*
 * LEGAL:   COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#ifndef GFX_RGBA_HH
#define GFX_RGBA_HH 1

#include "base/array_tiny.hh"
#include "base/clamp.hh"
using namespace base;
#include "math/module.hh"
using namespace math;
#include "gfx/osg.hh"
#include "gfx/defs.hh"
using namespace gfx;

namespace gfx {

#if COMPILE_OSG
// Convert RGBA object to osg::Vec4f[4] v.v.
#define RGBA2VEC4F( RGBA ) osg::Vec4f(RGBA)
#define VEC4F2RGBA( V )    RGBA(V)
typedef osg::Vec4Array Colors;
#endif // COMPILE_OSG

// Index to bytes of RGBA object.
// In OpenGL, RGBA byte-order is independent of CPU endianness.
// glColor4ubv() always expects red at GLubyte[0].
enum { RR, GG, BB, AA };

#define RGBA_LEN    4
#define RGBA8_LEN   4
#define BGRA_LEN    4
#define BGRA8_LEN   4

////////////////////////////////////////////////////////////////////////////////
/// @brief RGBA class.  Based on uint8[4].  Can output/convert to fp[4].
///
class RGBA
{
public:

// Cast uint8[4] as uint32.
#define RGBA_CAST(RGBA)       *reinterpret_cast<uint32*>( RGBA )
#define CONST_RGBA_CAST(RGBA) *reinterpret_cast<const uint32*>( RGBA )

    RGBA( void )
    {
        RGBA_CAST( mRgba ) = 0;  // idiom: RGBA_CAST() is a cast, not a call
    }

    explicit RGBA( const uint8 rgba[4] )
    {
        // rgba[4] input is implicity clamped to a byte.
        RGBA_CAST( mRgba ) = CONST_RGBA_CAST( rgba );
    }

    explicit RGBA( const fp rgba[4] )
    {
        Set( rgba[RR], rgba[GG], rgba[BB], rgba[AA] );
    }

    RGBA( int r, int g, int b, int a )
    {
        Set( r, g, b, a );  // Set(int,int,int,int)
    }

    RGBA( uint r, uint g, uint b, uint a )
    {
        Set( r, g, b, a );  // Set(uint,uint,uint,uint)
    }

    RGBA( fp r, fp g, fp b, fp a )
    {
        Set( r, g, b, a );
    }

    RGBA( const RGBA& src )
    {
        RGBA_CAST( mRgba ) = CONST_RGBA_CAST( src.mRgba );
    }

#if COMPILE_OSG
// For searchability, use RGBA2VEC4F()/VEC4F2RGBA().
    explicit RGBA( const osg::Vec4f& v )
    {
        Set( v[RR], v[GG], v[BB], v[AA] );
    }

    operator osg::Vec4f() const
    {
        return osg::Vec4f( mRgba[RR] * gfx::defs::RECIP_256,
                           mRgba[GG] * gfx::defs::RECIP_256,
                           mRgba[BB] * gfx::defs::RECIP_256,
                           mRgba[AA] * gfx::defs::RECIP_256 );
    }
#endif // COMPILE_OSG

    void Set( const uint8 rgba[4] )
    {
        RGBA_CAST( mRgba ) = CONST_RGBA_CAST( rgba );
    }

    // Set(int)/Set(uint) differ.
    // ClampFF(int) clamps negatives at 0 but ClampFF(uint) doesn't.
    void Set( int r, int g, int b, int a )
    {
        mRgba[RR] = ClampFF( r );
        mRgba[GG] = ClampFF( g );
        mRgba[BB] = ClampFF( b );
        mRgba[AA] = ClampFF( a );
    }

    void Set( uint r, uint g, uint b, uint a )
    {
        mRgba[RR] = ClampFF( r );
        mRgba[GG] = ClampFF( g );
        mRgba[BB] = ClampFF( b );
        mRgba[AA] = ClampFF( a );
    }

    void Set( fp r, fp g, fp b, fp a )
    {
        mRgba[RR] = ClampFF( uint(r * 256.0f) );
        mRgba[GG] = ClampFF( uint(g * 256.0f) );
        mRgba[BB] = ClampFF( uint(b * 256.0f) );
        mRgba[AA] = ClampFF( uint(a * 256.0f) );
    }

    void Set( int same )
    {
        mRgba[RR] = mRgba[GG] = mRgba[BB] = mRgba[AA] = same;
    }

    // OSG-compatibility.
    void set( uint r, uint g, uint b, uint a ) { Set( r, g, b, a ); }
    void set( fp r, fp g, fp b, fp a )         { Set( r, g, b, a ); }

    const uint8* Get( void ) const
    {
        return mRgba;
    }

    // RGBA object --> fp[4] (rare).
    template<typename FP>  // should be a float type
    void Get4f( FP out[4] /*OUT*/ ) const
    {
        out[RR] = mRgba[RR] * gfx::defs::RECIP_256;
        out[GG] = mRgba[GG] * gfx::defs::RECIP_256;
        out[BB] = mRgba[BB] * gfx::defs::RECIP_256;
        out[AA] = mRgba[AA] * gfx::defs::RECIP_256;
    }

    void Get4f( TinyArray<4,fp>& out /*OUT*/ ) const
    {
        out[RR] = mRgba[RR] * gfx::defs::RECIP_256;
        out[GG] = mRgba[GG] * gfx::defs::RECIP_256;
        out[BB] = mRgba[BB] * gfx::defs::RECIP_256;
        out[AA] = mRgba[AA] * gfx::defs::RECIP_256;
    }

    // RGBA object --> uint[4]
    void Get4ub( uint8 out[4]/*OUT*/ ) const
    {
        RGBA_CAST( out ) = CONST_RGBA_CAST( mRgba );
    }

    RGBA& operator=( const RGBA& src )
    {
        RGBA_CAST( mRgba ) = CONST_RGBA_CAST( src.mRgba );
        return *this;
    }

    uint8& operator[]( uint i )
    {
        ASSERT( i < 4 );
        return mRgba[i];
    }

    const uint8& operator[]( uint i ) const // compiled when object is const
    {
        ASSERT( i < 4 );
        return mRgba[i];
    }

    bool IfZero( void ) const
    {
        return CONST_RGBA_CAST( mRgba ) == 0;
    }

    friend inline bool operator<( const RGBA src1, const RGBA src2 )
    {
        return CONST_RGBA_CAST( src1.mRgba ) < CONST_RGBA_CAST( src2.mRgba );
    }

    friend inline bool operator==( const RGBA src1, const RGBA src2 )
    {
        return CONST_RGBA_CAST( src1.mRgba ) == CONST_RGBA_CAST( src2.mRgba );
    }

    friend inline bool operator!=( const RGBA src1, const RGBA src2 )
    {
        return CONST_RGBA_CAST( src1.mRgba ) != CONST_RGBA_CAST( src2.mRgba );
    }

private:
    uint8   mRgba[4];
};

} // namespace gfx

#endif // GFX_RGBA_HH
