// Aqsis
// Copyright (C) 2001, Paul C. Gregory and the other authors and contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name of the software's owners nor the names of its
//   contributors may be used to endorse or promote products derived from this
//   software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// (This is the New BSD license)

/** \file
 *
 * \brief Define classes for manipulating image channels.
 *
 * \author Chris Foster  chris42f _at_ gmail.com
 */

#ifndef IMAGECHANNEL_H_INCLUDED
#define IMAGECHANNEL_H_INCLUDED

#include <aqsis/aqsis.h>

#include <limits>
#include <string>
#include <vector>

#include <aqsis/math/math.h>
#include <aqsis/tex/buffers/channelinfo.h>
#include <aqsis/tex/buffers/iimagechannel.h>

namespace Aqsis {

//------------------------------------------------------------------------------
/** \brief Convert a dspy format type into the enum EqChannelType equivilant.
 * \return the equivilant EqChannelType type for the given PkDspy* constant
 */
AQSIS_TEX_SHARE EqChannelType chanFormatFromPkDspy(TqInt dspyFormat);

/** \brief Convert a EqChannelType to the PkDspy equivilant
 */
AQSIS_TEX_SHARE TqInt pkDspyFromChanFormat(EqChannelType format);

//------------------------------------------------------------------------------
/** \brief A constant-valued image channel source
 */
class AQSIS_TEX_SHARE CqImageChannelConstant : public IqImageChannelSource
{
	public:
		/** \brief Construct a constant image channel
		 *
		 * \param value - the constant value which will be generated by the channel.
		 */
		CqImageChannelConstant(TqFloat value = 0);
		virtual void requireSize(TqInt width, TqInt height) const;
		virtual const TqFloatConv* getRow(TqInt row) const;
	private:
		TqFloatConv m_value; ///< The value for the channel
		mutable std::vector<TqFloatConv> m_rowBuf; ///< buffer to hold generated row of data
};


//------------------------------------------------------------------------------
/** \brief A image channel source which produces a checker pattern
 *
 * The checker pattern is suitable for use as a base image to show alpha in
 * other images.
 */
class AQSIS_TEX_SHARE CqImageChannelCheckered : public IqImageChannelSource
{
	public:
		/** \brief Construct a checker channel
		 *
		 * \param tileSize - the size of the checkered tiles
		 */
		CqImageChannelCheckered(TqInt tileSize = 16);
		virtual void requireSize(TqInt width, TqInt height) const;
		virtual const TqFloatConv* getRow(TqInt row) const;
	private:
		TqInt m_tileSize; ///< The checker tile size
		mutable std::vector<TqFloatConv> m_checkerRow0; ///< pattern for even tiles
		mutable std::vector<TqFloatConv> m_checkerRow1; ///< pattern for odd tiles
};


//------------------------------------------------------------------------------
/** \brief A rescaling image channel
 */
class AQSIS_TEX_SHARE CqImageChannelZoom : public IqImageChannelSource
{
	public:
		/** \brief Construct a checker channel
		 *
		 * \param tileSize - the size of the checkered tiles
		 */
		CqImageChannelZoom(const IqImageChannelSource& src, TqInt zoomFactor);
		virtual void requireSize(TqInt width, TqInt height) const;
		virtual const TqFloatConv* getRow(TqInt row) const;
	private:
		const IqImageChannelSource& m_source; ///< Place to get source data from.
		TqInt m_zoomFactor; ///< The amount to zoom in by.
		mutable std::vector<TqFloatConv> m_rowBuf; ///< mem buffer for rows.
};

//------------------------------------------------------------------------------
/** \brief A thin wrapper around channel data held in a CqMixedImageBuffer
 *
 * CqImageChannel wraps around a single channel of a subregion of a heterogenous
 * array:
 *
 * A subregion looks like:
 *
 *  [ o o o o ]  
 *  [ o X X o ]  ^
 *  [ o X X o ]  | height
 *  [ o X X o ]  v
 *      <->
 *     width
 *
 *    <----->
 *    full_width
 *
 * where each pixel (o or X) possibly contains hetrogenous data types,
 * eg, X = [0xRR 0xGGGG] for a 8 bit red channel, and a 16 bit green channel.
 *
 * To do this we need to store
 * - A pointer to the start of the data
 * - width and height of the region
 * - the number of bytes to skip between entries ("stride")
 * - the number of pixels which are skipped at the end of each row (rowSkip =
 *   full_width - width)
 *
 * Operations which need access to the type of the channel data (eg,
 * conversions) are performed in the subclass CqImageChannelTyped
 */
class AQSIS_TEX_SHARE CqImageChannel : public IqImageChannel
{
	public:
		/** \brief Construct an image channel.
		 *
		 * \param chanInfo - channel information (name & type)
		 * \param data - raw pointer to the start of the channel data
		 * \param width - width of the channel in pixels
		 * \param height - height of the channel in pixels
		 * \param stride - stride between pixels in bytes
		 * \param rowSkip - number of pixels which are skipped at the end of
		 *                  each row (allows for support of rectangular subregions).
		 */
		CqImageChannel(const SqChannelInfo& chanInfo, TqUint8* data,
				TqInt width, TqInt height, TqInt stride, TqInt rowSkip = 0);
		virtual inline ~CqImageChannel();
		/** \brief Get descriptive information about the channel
		 */
		inline const SqChannelInfo& channelInfo() const;

		// Inherited
		virtual void requireSize(TqInt width, TqInt height) const;
		virtual void copyFrom(const IqImageChannelSource& source);
		virtual void compositeOver(const IqImageChannelSource& source,
				const IqImageChannelSource& sourceAlpha);

		/// \note Use default copy constructor and assignment ops.
	protected:
		/** \brief Replace a row in the current channel
		 *
		 * Performs the necessary type conversion from TqFloatConv to
		 * the type of this channel.
		 *
		 * \param row - image row to replace, counting from the top (row 0).
		 * \param buf - buffer holding the data to replace the row with.
		 */
		virtual void replaceRow(TqInt row, const TqFloatConv* buf) = 0;
		/** \brief Composite a row onto the current channel
		 *
		 * Performs the necessary type conversion from TqFloatConv to
		 * the type of this channel.  The source is assumed to be
		 * alpha-premultipled.
		 *
		 * \param row - image row to replace, counting from the top (row 0).
		 * \param buf - buffer holding the data to replace the row with.
		 */
		virtual void compositeRow(TqInt row, const TqFloatConv* src,
				const TqFloatConv* srcAlpha) = 0;

		SqChannelInfo m_chanInfo; ///< channel format information
		TqUint8* m_data;    ///< raw data
		TqInt m_width;      ///< width of raw data in pixels
		TqInt m_height;     ///< height of raw data in pixels
		TqInt m_stride;     ///< stride between one pixel and the next in bytes
		TqInt m_rowSkip;    ///< number of pixels to skip at the end of each row
		mutable std::vector<TqFloatConv> m_copyBuf; ///< temporary row buffer for holding results of a conversion
};


//------------------------------------------------------------------------------
/** \brief Override some methods of CqImageChannel with type-specific
 * conversion capabilities.
 */
template<typename T>
class CqImageChannelTyped : public CqImageChannel
{
	public:
		/** \brief Constructor
		 * \see CqImageChannel::CqImageChannel
		 */
		inline CqImageChannelTyped(const SqChannelInfo& chanInfo, TqUint8* data,
				TqInt width, TqInt height, TqInt stride, TqInt rowSkip = 0);
	private:
		// Inherited
		virtual const TqFloatConv* getRow(TqInt row) const;
		virtual void replaceRow(TqInt row, const TqFloatConv* buf);
		virtual void compositeRow(TqInt row, const TqFloatConv* src,
				const TqFloatConv* srcAlpha);

		/// Convert the type held by this channel into a float.
		static inline TqFloatConv convertToFloat(T t);
		/// Convert from a float to the type held by this channel.
		static inline T convertFromFloat(TqFloatConv f);
};


//==============================================================================
// Implementation details
//==============================================================================

// CqImageChannel implementation
inline CqImageChannel::~CqImageChannel()
{ }

inline const SqChannelInfo& CqImageChannel::channelInfo() const
{
	return m_chanInfo;
}


//------------------------------------------------------------------------------
// CqImageChannelTyped implementation
template<typename T>
inline CqImageChannelTyped<T>::CqImageChannelTyped(const SqChannelInfo& chanInfo,
		TqUint8* data, TqInt width, TqInt height, TqInt stride, TqInt rowSkip)
	: CqImageChannel(chanInfo, data, width, height, stride, rowSkip)
{ }

template<typename T>
const TqFloatConv* CqImageChannelTyped<T>::getRow(TqInt row) const
{
	TqUint8* srcBuf = m_data + row*m_stride*(m_width + m_rowSkip);
	std::vector<TqFloatConv>::iterator destBuf = m_copyBuf.begin();
	for(TqInt i = 0; i < m_width; ++i)
	{
		*destBuf = convertToFloat(*reinterpret_cast<T*>(srcBuf));
		srcBuf += m_stride;
		destBuf++;
	}
	return &m_copyBuf[0];
}

template<typename T>
void CqImageChannelTyped<T>::replaceRow(TqInt row, const TqFloatConv* buf)
{
	TqUint8* destBuf = m_data + row*m_stride*(m_width + m_rowSkip);
	for(TqInt i = 0; i < m_width; ++i)
	{
		*reinterpret_cast<T*>(destBuf) = convertFromFloat(*buf);
		destBuf += m_stride;
		buf++;
	}
}

template<typename T>
void CqImageChannelTyped<T>::compositeRow(TqInt row, const TqFloatConv* src,
		const TqFloatConv* srcAlpha)
{
	TqUint8* destBuf = m_data + row*m_stride*(m_width + m_rowSkip);
	for(TqInt i = 0; i < m_width; ++i)
	{
		TqFloatConv oldCol = convertToFloat(*reinterpret_cast<T*>(destBuf));
		*reinterpret_cast<T*>(destBuf) = convertFromFloat(*src + (1 - *srcAlpha)*oldCol);
		destBuf += m_stride;
		src++;
		srcAlpha++;
	}
}

template<typename T>
inline TqFloatConv CqImageChannelTyped<T>::convertToFloat(T src)
{
	if(std::numeric_limits<T>::is_integer)
	{   
		// source = integer
		return ( static_cast<TqFloatConv>(src) -
				static_cast<TqFloatConv>(std::numeric_limits<T>::min()) )
			/ ( static_cast<TqFloatConv>(std::numeric_limits<T>::max())
				- static_cast<TqFloatConv>(std::numeric_limits<T>::min()));
	}
	else
	{
		// source = floating point
		return static_cast<TqFloatConv>(src);
	}
}

template<typename T>
inline T CqImageChannelTyped<T>::convertFromFloat(TqFloatConv src)
{
	if(std::numeric_limits<T>::is_integer)
	{
		// result = integer
		return static_cast<T>(lround(clamp<TqFloatConv>(src, 0, 1) *
					( static_cast<TqFloatConv>(std::numeric_limits<T>::max())
					- static_cast<TqFloatConv>(std::numeric_limits<T>::min()) )
					+ std::numeric_limits<T>::min()
				));
	}
	else
	{
		// result = floating point
		return static_cast<T>(src);
	}
}

#if 0
template<typename T>
void CqImageChannelTyped<T>::copyFromSameType(const CqImageChannel& source)
{
	assert(m_chanInfo.type == source.m_chanInfo.type);
	TqUint8* srcBuf = source.m_data;
	TqUint8* destBuf = m_data;
	for(TqInt row = 0; row < m_height; ++row)
	{
		for(TqInt col = 0; col < m_width; ++col)
		{
			*reinterpret_cast<T*>(destBuf) = *reinterpret_cast<T*>(srcBuf);
			srcBuf += source.m_stride;
			destBuf += m_stride;
		}
		srcBuf += source.m_rowSkip*source.m_stride;
		destBuf += m_rowSkip*m_stride;
	}
}
#endif

//------------------------------------------------------------------------------
} // namespace Aqsis

#endif // IMAGECHANNEL_H_INCLUDED
