// 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

#include "cparamdisplay.h"
#include "../cbitmap.h"
#include "../cframe.h"
#include "../cstring.h"
#include "../cgraphicspath.h"
#include "../cdrawcontext.h"
#include <string>

namespace VSTGUI {

//------------------------------------------------------------------------
// CParamDisplay
//------------------------------------------------------------------------
/*! @class CParamDisplay
Define a rectangle view where a text-value can be displayed with a given font and color.
The user can specify its convert function (from float to char) by default the string format is "%2.2f".
The text-value is centered in the given rect.
*/
CParamDisplay::CParamDisplay (const CRect& size, CBitmap* background, int32_t inStyle)
: CControl (size, nullptr, -1, background)
, horiTxtAlign (kCenterText)
, style (inStyle)
, valuePrecision (2)
, roundRectRadius (6.)
, frameWidth (1.)
, textRotation (0.)
{
	setBit (style, kAntialias, true);
	backOffset (0, 0);

	fontID      = kNormalFont; fontID->remember ();
	fontColor   = kWhiteCColor;
	backColor   = kBlackCColor;
	frameColor  = kBlackCColor;
	shadowColor = kRedCColor;
	if (hasBit (style, kNoDrawStyle))
		setDirty (false);
}

//------------------------------------------------------------------------
CParamDisplay::CParamDisplay (const CParamDisplay& v)
: CControl (v)
, valueToStringFunction (v.valueToStringFunction)
, horiTxtAlign (v.horiTxtAlign)
, style (v.style)
, valuePrecision (v.valuePrecision)
, fontID (v.fontID)
, fontColor (v.fontColor)
, backColor (v.backColor)
, frameColor (v.frameColor)
, shadowColor (v.shadowColor)
, textInset (v.textInset)
, backOffset (v.backOffset)
, roundRectRadius (v.roundRectRadius)
, frameWidth (v.frameWidth)
, textRotation (v.textRotation)
{
	fontID->remember ();
}

//------------------------------------------------------------------------
CParamDisplay::~CParamDisplay () noexcept
{
	if (fontID)
		fontID->forget ();
}

//------------------------------------------------------------------------
bool CParamDisplay::removed (CView* parent)
{
	return CControl::removed (parent);
}

//------------------------------------------------------------------------
void CParamDisplay::setStyle (int32_t val)
{
	setBit (val, kAntialias, hasBit (style, kAntialias));
	if (style != val)
	{
		style = val;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
int32_t CParamDisplay::getStyle () const
{
	auto tmp = style;
	setBit (tmp, kAntialias, false);
	return tmp;
}

//------------------------------------------------------------------------
void CParamDisplay::setPrecision (uint8_t precision)
{
	if (valuePrecision != precision)
	{
		valuePrecision = precision;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::setValueToStringFunction2 (const ValueToStringFunction2& valueToStringFunc)
{
	valueToStringFunction = valueToStringFunc;
}

//------------------------------------------------------------------------
void CParamDisplay::setValueToStringFunction2 (ValueToStringFunction2&& valueToStringFunc)
{
	valueToStringFunction = std::move (valueToStringFunc);
}

//------------------------------------------------------------------------
void CParamDisplay::setValueToStringFunction (const ValueToStringFunction& func)
{
	if (!func)
	{
		setValueToStringFunction2 (nullptr);
		return;
	}
	setValueToStringFunction2 ([=] (float value, std::string& str, CParamDisplay* display) {
		char string[256];
		string[0] = 0;
		if (func (value, string, display))
		{
			str = string;
			return true;
		}
		return false;
	});
}

//------------------------------------------------------------------------
void CParamDisplay::setValueToStringFunction (ValueToStringFunction&& func)
{
	setValueToStringFunction (func);
}

//------------------------------------------------------------------------
bool CParamDisplay::getFocusPath (CGraphicsPath& outPath)
{
	if (wantsFocus ())
	{
        auto lineWidth = getFrameWidth ();
        if (lineWidth < 0.)
            lineWidth = 1.;
		CCoord focusWidth = getFrame ()->getFocusWidth ();
		CRect r (getViewSize ());
		if (hasBit (style, kRoundRectStyle))
		{
			r.inset (lineWidth / 2., lineWidth / 2.);
			outPath.addRoundRect (r, roundRectRadius);
			outPath.closeSubpath ();
			r.extend (focusWidth, focusWidth);
			outPath.addRoundRect (r, roundRectRadius);
		}
		else
		{
			r.inset (lineWidth / 2., lineWidth / 2.);
			outPath.addRect (r);
			r.extend (focusWidth, focusWidth);
			outPath.addRect (r);
		}
	}
	return true;
}

//------------------------------------------------------------------------
void CParamDisplay::draw (CDrawContext *pContext)
{
	if (hasBit (style, kNoDrawStyle))
		return;

	std::string string;

	bool converted = false;
	if (valueToStringFunction)
		converted = valueToStringFunction (value, string, this);
	if (!converted)
	{
		char tmp[255];
		char precisionStr[10];
		snprintf (precisionStr, 10, "%%.%hhuf", valuePrecision);
		snprintf (tmp, 255, precisionStr, value);
		string = tmp;
	}

	drawBack (pContext);
	drawPlatformText (pContext, UTF8String (string));
	setDirty (false);
}

//------------------------------------------------------------------------
void CParamDisplay::drawBack (CDrawContext* pContext, CBitmap* newBack)
{
	pContext->setDrawMode (kAliasing);
	auto lineWidth = getFrameWidth ();
	if (lineWidth < 0.)
		lineWidth = pContext->getHairlineSize ();
	if (newBack)
	{
		newBack->draw (pContext, getViewSize (), backOffset);
	}
	else if (getDrawBackground ())
	{
		getDrawBackground ()->draw (pContext, getViewSize (), backOffset);
	}
	else
	{
		if (!getTransparency ())
		{
			bool strokePath = !(hasBit (style, (k3DIn|k3DOut|kNoFrame)));
			pContext->setFillColor (backColor);
			if (hasBit (style, kRoundRectStyle))
			{
				CRect pathRect = getViewSize ();
				pathRect.inset (lineWidth/2., lineWidth/2.);
				SharedPointer<CGraphicsPath> path = owned (pContext->createRoundRectGraphicsPath (pathRect, roundRectRadius));
				if (path)
				{
					pContext->setDrawMode (kAntiAliasing);
					pContext->drawGraphicsPath (path, CDrawContext::kPathFilled);
					if (strokePath)
					{
						pContext->setLineStyle (kLineSolid);
						pContext->setLineWidth (lineWidth);
						pContext->setFrameColor (frameColor);
						pContext->drawGraphicsPath (path, CDrawContext::kPathStroked);
					}
				}
			}
			else
			{
				pContext->setDrawMode (kAntiAliasing);
				SharedPointer<CGraphicsPath> path = owned (pContext->createGraphicsPath ());
				if (path)
				{
					CRect frameRect = getViewSize ();
					if (strokePath)
						frameRect.inset (lineWidth/2., lineWidth/2.);
					path->addRect (frameRect);
					pContext->drawGraphicsPath (path, CDrawContext::kPathFilled);
					if (strokePath)
					{
						pContext->setLineStyle (kLineSolid);
						pContext->setLineWidth (lineWidth);
						pContext->setFrameColor (frameColor);
						pContext->drawGraphicsPath (path, CDrawContext::kPathStroked);
					}
				}
				else
				{
					pContext->drawRect (getViewSize (), kDrawFilled);
			
					if (strokePath)
					{
						CRect frameRect = getViewSize ();
						frameRect.inset (lineWidth/2., lineWidth/2.);

						pContext->setLineStyle (kLineSolid);
						pContext->setLineWidth (lineWidth);
						pContext->setFrameColor (frameColor);
						pContext->drawRect (frameRect);
					}
				}
			}
		}
	}
	// draw the frame for the 3D effect
	if (hasBit (style, (k3DIn|k3DOut)))
	{
		CRect r (getViewSize ());
		r.inset (lineWidth/2., lineWidth/2.);
		pContext->setDrawMode (kAliasing);
		pContext->setLineWidth (lineWidth);
		pContext->setLineStyle (kLineSolid);
		if (hasBit (style, k3DIn))
			pContext->setFrameColor (backColor);
		else
			pContext->setFrameColor (frameColor);

		CPoint p;
		SharedPointer<CGraphicsPath> path = owned (pContext->createGraphicsPath ());
		if (path)
		{
			path->beginSubpath (p (r.left, r.bottom));
			path->addLine (p (r.left, r.top));
			path->addLine (p (r.right, r.top));
			pContext->drawGraphicsPath (path, CDrawContext::kPathStroked);
		}
		else
		{
			pContext->drawLine (CPoint (r.left, r.bottom), CPoint (r.left, r.top));
			pContext->drawLine (CPoint (r.left, r.top), CPoint (r.right, r.top));
		}

		if (hasBit (style, k3DIn))
			pContext->setFrameColor (frameColor);
		else
			pContext->setFrameColor (backColor);

		path = owned (pContext->createGraphicsPath ());
		if (path)
		{
			path->beginSubpath (p (r.right, r.top));
			path->addLine (p (r.right, r.bottom));
			path->addLine (p (r.left, r.bottom));
			pContext->drawGraphicsPath (path, CDrawContext::kPathStroked);
		}
		else
		{
			pContext->drawLine (CPoint (r.right, r.top), CPoint (r.right, r.bottom));
			pContext->drawLine (CPoint (r.right, r.bottom), CPoint (r.left, r.bottom));
		}
	}
}

//------------------------------------------------------------------------
void CParamDisplay::drawPlatformText (CDrawContext* pContext, const UTF8String& string)
{
	drawPlatformText (pContext, string, getViewSize ());
}

//------------------------------------------------------------------------
void CParamDisplay::drawPlatformText (CDrawContext* pContext, const UTF8String& string,
									  const CRect& size)
{
	if (!hasBit (style, kNoTextStyle))
	{
		pContext->saveGlobalState ();
		CRect textRect (size);
		textRect.inset (textInset.x, textInset.y);

		drawClipped (pContext, textRect, [&] () {
			CPoint center (textRect.getCenter ());
			CGraphicsTransform transform;
			transform.rotate (textRotation, center);
			CDrawContext::Transform ctxTransform (*pContext, transform);

			pContext->setDrawMode (kAntiAliasing);
			pContext->setFont (fontID);

			// draw darker text (as shadow)
			if (hasBit (style, kShadowText))
			{
				CRect newSize (textRect);
				newSize.offset (shadowTextOffset);
				pContext->setFontColor (shadowColor);
				pContext->drawString (string.getPlatformString (), newSize, horiTxtAlign,
									  hasBit (style, kAntialias));
			}
			pContext->setFontColor (fontColor);
			pContext->drawString (string.getPlatformString (), textRect, horiTxtAlign,
								  hasBit (style, kAntialias));
		});
		pContext->restoreGlobalState ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::setFont (CFontRef inFontID)
{
	if (fontID)
		fontID->forget ();
	fontID = inFontID;
	if (fontID)
		fontID->remember ();
	drawStyleChanged ();
}

//------------------------------------------------------------------------
void CParamDisplay::setFontColor (CColor color)
{
	// to force the redraw
	if (fontColor != color)
	{
		fontColor = color;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::setBackColor (CColor color)
{
	// to force the redraw
	if (backColor != color)
	{
		backColor = color;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::setFrameColor (CColor color)
{
	// to force the redraw
	if (frameColor != color)
	{
		frameColor = color;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::setShadowColor (CColor color)
{
	// to force the redraw
	if (shadowColor != color)
	{
		shadowColor = color;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::setShadowTextOffset (const CPoint& offset)
{
	if (shadowTextOffset != offset)
	{
		shadowTextOffset = offset;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::setHoriAlign (CHoriTxtAlign hAlign)
{
	// to force the redraw
	if (horiTxtAlign != hAlign)
	{
		horiTxtAlign = hAlign;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::setTextInset (const CPoint& p)
{
	if (textInset != p)
	{
		textInset = p;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::setTextRotation (double angle)
{
	while (angle < 0.)
		angle += 360.;
	while (angle > 360.)
		angle -= 360.;
	if (textRotation != angle)
	{
		textRotation = angle;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::setRoundRectRadius (const CCoord& radius)
{
	if (roundRectRadius != radius)
	{
		roundRectRadius = radius;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::setFrameWidth (const CCoord& width)
{
	if (frameWidth != width)
	{
		frameWidth = width;
		drawStyleChanged ();
	}
}

//------------------------------------------------------------------------
void CParamDisplay::drawStyleChanged ()
{
	setDirty ();
}

//------------------------------------------------------------------------
void CParamDisplay::setBackOffset (const CPoint &offset)
{
	backOffset = offset;
}

//-----------------------------------------------------------------------------
void CParamDisplay::copyBackOffset ()
{
	backOffset (getViewSize ().left, getViewSize ().top);
}

} // VSTGUI
