// This file is part of VSTGUI. It is subject to the license terms 
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE

#pragma once

#include "vstguifwd.h"
#include "cpoint.h"
#include "crect.h"
#include "cresourcedescription.h"
#include "pixelbuffer.h"
#include "platform/iplatformbitmap.h"
#include <vector>

namespace VSTGUI {

//-----------------------------------------------------------------------------
// CBitmap Declaration
//! @brief Encapsulates various platform depended kinds of bitmaps
//-----------------------------------------------------------------------------
class CBitmap : public AtomicReferenceCounted
{
public:
	using BitmapVector = std::vector<PlatformBitmapPtr>;
	using const_iterator = BitmapVector::const_iterator;

	/** Create an image from a resource identifier */
	explicit CBitmap (const CResourceDescription& desc);
	/** Create an image with a given size */
	CBitmap (CCoord width, CCoord height);
	/** Create an image with a given size and scale factor */
	CBitmap (CPoint size, double scaleFactor = 1.);
	explicit CBitmap (const PlatformBitmapPtr& platformBitmap);
	~CBitmap () noexcept override = default;

	//-----------------------------------------------------------------------------
	/// @name CBitmap Methods
	//-----------------------------------------------------------------------------
	//@{
	virtual void draw (CDrawContext* context, const CRect& rect, const CPoint& offset = CPoint (0, 0), float alpha = 1.f);

	/** get the width of the image */
	CCoord getWidth () const;
	/** get the height of the image */
	CCoord getHeight () const;
	/** get size of image */
	CPoint getSize () const;

	/** check if image is loaded */
	bool isLoaded () const { return getPlatformBitmap () ? true : false; }

	const CResourceDescription& getResourceDescription () const { return resourceDesc; }

	PlatformBitmapPtr getPlatformBitmap () const;
	void setPlatformBitmap (const PlatformBitmapPtr& bitmap);

	bool addBitmap (const PlatformBitmapPtr& platformBitmap);
	PlatformBitmapPtr getBestPlatformBitmapForScaleFactor (double scaleFactor) const;

	const_iterator begin () const { return bitmaps.begin (); }
	const_iterator end () const { return bitmaps.end (); }
	//@}

//-----------------------------------------------------------------------------
protected:
	CBitmap ();

	CResourceDescription resourceDesc;
	BitmapVector bitmaps;
};

//-----------------------------------------------------------------------------
/** Description for a multi frame bitmap
 *
 *	@ingroup new_in_4_12
 */
struct CMultiFrameBitmapDescription
{
	/** size of one frame */
	CPoint frameSize {};
	/** number of total frames */
	uint16_t numFrames {};
	/** number of frames per row */
	uint16_t framesPerRow {1};
};

//-----------------------------------------------------------------------------
/** Multi frame bitmap
 *
 *	A bitmap describing multiple frames ordered in rows and columns
 *
 *	The index order is columns and then rows:
 *
 *	1.Row: 1 -> 2 -> 3
 *	2.Row: 4 -> 5 -> 6
 *	...
 *
 *	@ingroup new_in_4_12
 */
class CMultiFrameBitmap : public CBitmap
{
public:
	using CBitmap::CBitmap;

	CMultiFrameBitmap (const CResourceDescription& desc,
					   CMultiFrameBitmapDescription multiFrameDesc);

	/** set the multi frame description
	 *
	 *	@param desc the multi frame description
	 *	@return true if bitmap is big enough for the description
	 */
	bool setMultiFrameDesc (CMultiFrameBitmapDescription desc);
	/** get the mult frame description */
	CMultiFrameBitmapDescription getMultiFrameDesc () const;
	/** get the frame size */
	CPoint getFrameSize () const;
	/** get the number of frames */
	uint16_t getNumFrames () const;
	/** get the number of frames per row */
	uint16_t getNumFramesPerRow () const;
	/** calculate the rect for one frame */
	CRect calcFrameRect (uint32_t frameIndex) const;
	/** draw one frame at the position in the context */
	void drawFrame (CDrawContext* context, uint16_t frameIndex, CPoint pos);
	/** return the frame to display for a normalized value
	 *
	 *	defaults to:
	 *	 normalizedToSteps<float, uint16_t> (value, getNumFrames () - 1);
	 *
	 *	subclasses can adopt this to other value mappings
	 *
	 *	@ingroup new_in_4_12_1
	 */
	virtual uint16_t normalizedValueToFrameIndex (float value) const;
	/** return the normalized value from the frame index
	 *
	 *	defaults to:
	 *	 stepsToNormalized<float, uint16_t> (frameIndex, getNumFrames () - 1);
	 *
	 *	subclasses can adopt this to other value mappings
	 *
	 *	@ingroup new_in_4_12_1
	 */
	virtual float frameIndexToNormalizedValue (uint16_t frameIndex) const;

private:
	CMultiFrameBitmapDescription description;
};

//------------------------------------------------------------------------
/** an injection class for views that draw frames of a CMultiFrameBitmap
 *
 *	a view/control can inherit from this class to support drawing only frames in a range of the
 *	multi-frame bitmap.
 *
 *	@ingroup new_in_4_12_2
 */
template<typename T>
class MultiFrameBitmapView
{
	using This = T;

public:
	/** set the range of the CMultiBitmapFrame this view will use for drawing
	 *
	 *	@param startIndex the first frame to draw
	 *	@param endIndex the last frame to draw
	 */
	void setMultiFrameBitmapRange (int32_t startIndex, int32_t endIndex)
	{
		if (endIndex >= 0 && startIndex > endIndex)
			std::swap (startIndex, endIndex);
		frameStartIndex = startIndex;
		frameEndIndex = endIndex;
		static_cast<This*> (this)->invalid ();
	}

	/** get the range of the CMulitBitmapFrame this view will use for drawing
	 *
	 *	@return a std::pair with the start and end index
	 */
	std::pair<int32_t, int32_t> getMultiFrameBitmapRange () const
	{
		return {frameStartIndex, frameEndIndex};
	}

	/** get the number of frames this view will use for drawing
	 *
	 *	@param mfb the bitmap
	 *	@return the number of frames
	 */
	uint16_t getMultiFrameBitmapRangeLength (const CMultiFrameBitmap& mfb) const
	{
		auto endIndex = frameEndIndex >= 0 ? frameEndIndex : mfb.getNumFrames ();
		return endIndex - frameStartIndex;
	}

	/** get the inverse index
	 *
	 *	@param mfb the bitmap
	 *	@param index the index
	 *	@return the inverse index
	 */
	uint16_t getInverseIndex (const CMultiFrameBitmap& mfb, uint16_t index) const
	{
		auto endIndex = frameEndIndex >= 0 ? frameEndIndex : mfb.getNumFrames () - 1;
		if (index >= frameStartIndex && index <= endIndex)
		{
			return endIndex - (index - frameStartIndex);
		}
		return index;
	}

	/** get the frame index for a normalized value
	 *
	 *	@param mfb the bitmap
	 *	@param normValue the normalized value
	 *	@return the index of the frame for the value
	 */
	uint16_t getMultiFrameBitmapIndex (const CMultiFrameBitmap& mfb, float normValue) const
	{
		if (frameStartIndex == 0 && frameEndIndex < 0)
			return mfb.normalizedValueToFrameIndex (normValue);

		auto startNorm = mfb.frameIndexToNormalizedValue (frameStartIndex);
		auto endNorm = mfb.frameIndexToNormalizedValue (
			frameEndIndex >= 0 ? frameEndIndex : mfb.getNumFrames () - 1);
		normValue = normValue * (endNorm - startNorm) + startNorm;
		return mfb.normalizedValueToFrameIndex (normValue);
	}

	/** get the normalized value for a frame index
	 *
	 *	@param mfb the bitmap
	 *	@param index the frame index
	 *	@return the normalized value
	 */
	float getNormValueFromMultiFrameBitmapIndex (const CMultiFrameBitmap& mfb, uint16_t index) const
	{
		auto startNorm = mfb.frameIndexToNormalizedValue (frameStartIndex);
		auto endNorm = mfb.frameIndexToNormalizedValue (
			frameEndIndex >= 0 ? frameEndIndex : mfb.getNumFrames () - 1);
		auto indexNorm = mfb.frameIndexToNormalizedValue (index);
		return (indexNorm - startNorm) / (endNorm - startNorm);
	}

private:
	int32_t frameStartIndex {0};
	int32_t frameEndIndex {-1};
};

//-----------------------------------------------------------------------------
struct CNinePartTiledDescription
{
	enum
	{
		kPartTopLeft,
		kPartTop,
		kPartTopRight,
		kPartLeft,
		kPartCenter,
		kPartRight,
		kPartBottomLeft,
		kPartBottom,
		kPartBottomRight,
		kPartCount
	};
	
	CCoord left {0.};
	CCoord top {0.};
	CCoord right {0.};
	CCoord bottom {0.};

	CNinePartTiledDescription () = default;
	CNinePartTiledDescription (CCoord left, CCoord top, CCoord right, CCoord bottom)
	: left (left), top (top), right (right), bottom (bottom) {}

	//-----------------------------------------------------------------------------
	inline void calcRects (const CRect& inBitmapRect, CRect outRect[kPartCount]) const
	{
		// Center
		CRect myCenter = outRect[kPartCenter]	(inBitmapRect.left		+ left,
												 inBitmapRect.top		+ top,
												 inBitmapRect.right		- right,
												 inBitmapRect.bottom	- bottom);
		
		// Edges
		outRect[kPartTop]			(myCenter.left,		inBitmapRect.top,	myCenter.right,		myCenter.top);
		outRect[kPartLeft]			(inBitmapRect.left,	myCenter.top,		myCenter.left,		myCenter.bottom);
		outRect[kPartRight]			(myCenter.right,	myCenter.top,		inBitmapRect.right,	myCenter.bottom);
		outRect[kPartBottom]		(myCenter.left,		myCenter.bottom,	myCenter.right,		inBitmapRect.bottom);
		
		// Corners
		outRect[kPartTopLeft]		(inBitmapRect.left,	inBitmapRect.top,	myCenter.left,		myCenter.top);
		outRect[kPartTopRight]		(myCenter.right,	inBitmapRect.top,	inBitmapRect.right,	myCenter.top);
		outRect[kPartBottomLeft]	(inBitmapRect.left,	myCenter.bottom,	myCenter.left,		inBitmapRect.bottom);
		outRect[kPartBottomRight]	(myCenter.right,	myCenter.bottom,	inBitmapRect.right,	inBitmapRect.bottom);
	}
	
};

//-----------------------------------------------------------------------------
// CNinePartTiledBitmap Declaration
/// @brief a nine-part tiled bitmap
/// @ingroup new_in_4_0
//-----------------------------------------------------------------------------
class CNinePartTiledBitmap : public CBitmap
{
public:
	CNinePartTiledBitmap (const CResourceDescription& desc, const CNinePartTiledDescription& offsets);
	CNinePartTiledBitmap (const PlatformBitmapPtr& platformBitmap, const CNinePartTiledDescription& offsets);
	~CNinePartTiledBitmap () noexcept override = default;
	
	//-----------------------------------------------------------------------------
	/// @name Part Offsets
	//-----------------------------------------------------------------------------
	//@{
	void setPartOffsets (const CNinePartTiledDescription& partOffsets) { offsets = partOffsets; }
	const CNinePartTiledDescription& getPartOffsets () const { return offsets; }
	//@}

	void draw (CDrawContext* context, const CRect& rect, const CPoint& offset = CPoint (0, 0), float alpha = 1.f) override;

//-----------------------------------------------------------------------------
protected:
	CNinePartTiledDescription offsets;
};

//------------------------------------------------------------------------
/** Convert between Platform Pixel Accessor pixel format and PixelBuffer format */
template<typename T1, typename T2,
		 typename std::enable_if<
			 std::is_same<PixelBuffer::Format, T2>::value ||
			 std::is_same<IPlatformBitmapPixelAccess::PixelFormat, T2>::value>::type* = nullptr>
inline T1 convert (T2 format)
{
	using PlPixelFormat = IPlatformBitmapPixelAccess::PixelFormat;
	using Format = PixelBuffer::Format;
	static_assert (std::is_same<Format, T1>::value || std::is_same<PlPixelFormat, T1>::value,
				   "Unexpected Format");
	static_assert (!std::is_same<T1, T2>::value, "Unexpected Format");
	static_assert (static_cast<int32_t> (Format::ARGB) == PlPixelFormat::kARGB, "Format Mismatch");
	static_assert (static_cast<int32_t> (Format::ABGR) == PlPixelFormat::kABGR, "Format Mismatch");
	static_assert (static_cast<int32_t> (Format::RGBA) == PlPixelFormat::kRGBA, "Format Mismatch");
	static_assert (static_cast<int32_t> (Format::BGRA) == PlPixelFormat::kBGRA, "Format Mismatch");
	return static_cast<T1> (format);
}

//------------------------------------------------------------------------
// CBitmapPixelAccess
/// @brief direct pixel access to a CBitmap
/// @ingroup new_in_4_0
//------------------------------------------------------------------------
class CBitmapPixelAccess : public AtomicReferenceCounted
{
public:
	/** advance position */
	inline bool operator++ ();
	/** set current position */
	inline bool setPosition (uint32_t x, uint32_t y);
	/** return current x position */
	inline uint32_t getX () const { return x; }
	/** return current y position */
	inline uint32_t getY () const { return y; }
	/** get color of current pixel */
	virtual void getColor (CColor& c) const = 0;
	/** set color of current pixel */
	virtual void setColor (const CColor& c) = 0;

	/** get native color value */
	inline void getValue (uint32_t& value);
	/** set native color value */
	inline void setValue (uint32_t value);

	inline uint32_t getBitmapWidth () const { return maxX+1; }
	inline uint32_t getBitmapHeight () const { return maxY+1; }

	inline IPlatformBitmapPixelAccess* getPlatformBitmapPixelAccess () const { return pixelAccess; }
	/** create an accessor.
		can return 0 if platform implementation does not support this.
		result needs to be forgotten before the CBitmap reflects the change to the pixels */
	static CBitmapPixelAccess* create (CBitmap* bitmap, bool alphaPremultiplied = true);
//-----------------------------------------------------------------------------
protected:
	CBitmapPixelAccess ();
	~CBitmapPixelAccess () noexcept override = default;
	void init (CBitmap* bitmap, IPlatformBitmapPixelAccess* pixelAccess);

	CBitmap* bitmap;
	SharedPointer<IPlatformBitmapPixelAccess> pixelAccess;
	uint8_t* currentPos;
	uint8_t* address;
	uint32_t bytesPerRow;
	uint32_t maxX;
	uint32_t maxY;
	uint32_t x;
	uint32_t y;
};

//------------------------------------------------------------------------
inline bool CBitmapPixelAccess::operator++ ()
{
	if (x < maxX)
	{
		x++;
		currentPos += 4;
		return true;
	}
	else if (y < maxY)
	{
		y++;
		x = 0;
		currentPos = address + y * bytesPerRow;
		return true;
	}
	return false;
}

//------------------------------------------------------------------------
inline bool CBitmapPixelAccess::setPosition (uint32_t _x, uint32_t _y)
{
	if (_x > maxX || _y > maxY)
		return false;
	x = _x;
	y = _y;
	currentPos = address + y * bytesPerRow + x * 4;
	return true;
}

//------------------------------------------------------------------------
inline void CBitmapPixelAccess::getValue (uint32_t& value)
{
	value = *(uint32_t*) (currentPos);
}

//------------------------------------------------------------------------
inline void CBitmapPixelAccess::setValue (uint32_t value)
{
	*(uint32_t*) (currentPos) = value;
}

} // VSTGUI
