// 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 "../../lib/cbitmap.h"
#include "../../lib/cfont.h"
#include "../../lib/cgradient.h"
#include "../../lib/platform/platformfactory.h"
#include "../base64codec.h"
#include "../cstream.h"
#include "../uiattributes.h"
#include "../uiviewcreator.h"
#include "locale.h"
#include "parsecolor.h"
#include "scalefactorutils.h"
#include "uinode.h"
#include <list>
#include <string>
#include <sstream>

namespace VSTGUI {
namespace Detail {

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
UINode::UINode (const std::string& _name, const SharedPointer<UIAttributes>& _attributes,
                bool needsFastChildNameAttributeLookup)
: name (_name), attributes (_attributes), flags (0)
{
	if (needsFastChildNameAttributeLookup)
		children = makeOwned<UIDescListWithFastFindAttributeNameChild> ();
	else
		children = makeOwned<UIDescList> ();
	if (attributes == nullptr)
		attributes = makeOwned<UIAttributes> ();
}

//-----------------------------------------------------------------------------
UINode::UINode (const std::string& _name, const SharedPointer<UIDescList>& _children,
                const SharedPointer<UIAttributes>& _attributes)
: name (_name), attributes (_attributes), children (_children), flags (0)
{
	vstgui_assert (children != nullptr);
	if (attributes == nullptr)
		attributes = makeOwned<UIAttributes> ();
}

//-----------------------------------------------------------------------------
UINode::UINode (const UINode& n)
: name (n.name)
, data (n.data)
, attributes (makeOwned<UIAttributes> (*n.attributes))
, children (makeOwned<UIDescList> (*n.children))
, flags (n.flags)
{
}

//-----------------------------------------------------------------------------
UINode::~UINode () noexcept
{
}

//-----------------------------------------------------------------------------
bool UINode::hasChildren () const
{
	return !children->empty ();
}

//-----------------------------------------------------------------------------
void UINode::childAttributeChanged (UINode* child, const char* attributeName,
                                    const char* oldAttributeValue)
{
	children->nodeAttributeChanged (child, attributeName, oldAttributeValue);
}

//-----------------------------------------------------------------------------
void UINode::sortChildren ()
{
	children->sort ();
}

//------------------------------------------------------------------------
void UINode::setData (DataStorage&& newData)
{
	data = std::move (newData);
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
UICommentNode::UICommentNode (const std::string& comment) : UINode ("comment")
{
	data = comment;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
UIVariableNode::UIVariableNode (const std::string& name,
                                const SharedPointer<UIAttributes>& attributes)
: UINode (name, attributes), type (kUnknown), number (0)
{
	const std::string* typeStr = attributes->getAttributeValue ("type");
	const std::string* valueStr = attributes->getAttributeValue ("value");
	if (typeStr)
	{
		if (*typeStr == "number")
			type = kNumber;
		else if (*typeStr == "string")
			type = kString;
	}
	if (valueStr)
	{
		Detail::Locale localeResetter;

		const char* strPtr = valueStr->c_str ();
		if (type == kUnknown)
		{
			char* endPtr = nullptr;
			double numberCheck = strtod (strPtr, &endPtr);
			if (endPtr == strPtr + strlen (strPtr))
			{
				number = numberCheck;
				type = kNumber;
			}
			else
				type = kString;
		}
		else if (type == kNumber)
		{
			number = strtod (strPtr, nullptr);
		}
	}
}

//-----------------------------------------------------------------------------
UIVariableNode::Type UIVariableNode::getType () const
{
	return type;
}

//-----------------------------------------------------------------------------
double UIVariableNode::getNumber () const
{
	return number;
}

//-----------------------------------------------------------------------------
const std::string& UIVariableNode::getString () const
{
	const std::string* value = attributes->getAttributeValue ("value");
	if (value)
		return *value;
	static std::string kEmpty;
	return kEmpty;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
UIControlTagNode::UIControlTagNode (const std::string& name,
                                    const SharedPointer<UIAttributes>& attributes)
: UINode (name, attributes), tag (-1)
{
}

//-----------------------------------------------------------------------------
int32_t UIControlTagNode::getTag ()
{
	if (tag == -1)
	{
		const std::string* tagStr = attributes->getAttributeValue ("tag");
		if (tagStr)
		{
			if (tagStr->size () == 6 && (*tagStr)[0] == '\'' && (*tagStr)[5] == '\'')
			{
				char c1 = (*tagStr)[1];
				char c2 = (*tagStr)[2];
				char c3 = (*tagStr)[3];
				char c4 = (*tagStr)[4];
				tag = ((((int32_t)c1) << 24) | (((int32_t)c2) << 16) | (((int32_t)c3) << 8) |
				       (((int32_t)c4) << 0));
			}
			else
			{
				char* endPtr = nullptr;
				tag = (int32_t)strtol (tagStr->c_str (), &endPtr, 10);
				if (endPtr != tagStr->c_str () + tagStr->length ())
					tag = -1;
			}
		}
	}
	return tag;
}

//-----------------------------------------------------------------------------
void UIControlTagNode::setTag (int32_t newTag)
{
	tag = newTag;
}

//-----------------------------------------------------------------------------
const std::string* UIControlTagNode::getTagString () const
{
	return attributes->getAttributeValue ("tag");
}

//-----------------------------------------------------------------------------
void UIControlTagNode::setTagString (const std::string& str)
{
	attributes->setAttribute ("tag", str);
	tag = -1;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
UIBitmapNode::UIBitmapNode (const std::string& name, const SharedPointer<UIAttributes>& attributes)
: UINode (name, attributes), bitmap (nullptr), filterProcessed (false), scaledBitmapsAdded (false)
{
}

//-----------------------------------------------------------------------------
UIBitmapNode::~UIBitmapNode () noexcept
{
	if (bitmap)
		bitmap->forget ();
}

//-----------------------------------------------------------------------------
void UIBitmapNode::freePlatformResources ()
{
	if (bitmap)
		bitmap->forget ();
	bitmap = nullptr;
}

//-----------------------------------------------------------------------------
bool UIBitmapNode::imagesEqual (IPlatformBitmap* b1, IPlatformBitmap* b2)
{
	if (b1 == b2)
		return true;
	if (b1->getSize () != b2->getSize () || b1->getScaleFactor () != b2->getScaleFactor ())
		return false;
	auto ac1 = b1->lockPixels (true);
	if (!ac1)
		return false;
	auto ac2 = b2->lockPixels (true);
	if (!ac2)
		return false;
	auto rowBytes = ac1->getBytesPerRow ();
	if (rowBytes != ac2->getBytesPerRow ())
		return false;
	if (ac1->getPixelFormat () != ac2->getPixelFormat ())
		return false;
	auto adr1 = ac1->getAddress ();
	if (!adr1)
		return false;
	auto adr2 = ac2->getAddress ();
	if (!adr2)
		return false;
	uint32_t rows = static_cast<uint32_t> (b1->getSize ().y);
	for (uint32_t y = 0; y < rows; ++y, adr1 += rowBytes, adr2 += rowBytes)
	{
		if (memcmp (adr1, adr2, rowBytes) != 0)
			return false;
	}
	return true;
}

//-----------------------------------------------------------------------------
bool UIBitmapNode::hasXMLData () const
{
	return getChildren ().findChildNode ("data") != nullptr;
}

//-----------------------------------------------------------------------------
void UIBitmapNode::createXMLData (const std::string& pathHint)
{
	UINode* node = getChildren ().findChildNode ("data");
	if (node)
	{
		if (node->getData ().empty ())
		{
			getChildren ().remove (node);
			node = nullptr;
		}
		else if (auto bm = getBitmap (pathHint))
		{
			if (auto platformBitmap = bm->getPlatformBitmap ())
			{
				if (auto dataBitmap = createBitmapFromDataNode ())
				{
					if (!imagesEqual (platformBitmap, dataBitmap))
					{
						removeXMLData ();
						node = nullptr;
					}
				}
			}
		}
	}
	if (node == nullptr)
	{
		if (auto bm = getBitmap (pathHint))
		{
			if (auto platformBitmap = bm->getPlatformBitmap ())
			{
				auto buffer =
				    getPlatformFactory ().createBitmapMemoryPNGRepresentation (platformBitmap);
				if (!buffer.empty ())
				{
					auto result = Base64Codec::encode (buffer.data (),
					                                   static_cast<uint32_t> (buffer.size ()));
					UINode* dataNode = new UINode ("data");
					dataNode->getAttributes ()->setAttribute ("encoding", "base64");
					dataNode->getData ().append (reinterpret_cast<const char*> (result.data.get ()),
					                             static_cast<std::streamsize> (result.dataSize));
					getChildren ().add (dataNode);
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
void UIBitmapNode::removeXMLData ()
{
	UINode* node = getChildren ().findChildNode ("data");
	if (node)
		getChildren ().remove (node);
}

//-----------------------------------------------------------------------------
CBitmap* UIBitmapNode::createBitmap (const std::string& str, const BitmapVariant& variant) const
{

	if (auto partDesc = std::get_if<CNinePartTiledDescription> (&variant))
		return new CNinePartTiledBitmap (CResourceDescription (str.data ()), *partDesc);
	else if (auto multiFrameDesc = std::get_if<CMultiFrameBitmapDescription> (&variant))
		return new CMultiFrameBitmap (CResourceDescription (str.data ()), *multiFrameDesc);
	return new CBitmap (CResourceDescription (str.c_str ()));
}

//------------------------------------------------------------------------
UINode* UIBitmapNode::dataNode () const
{
	UINode* node = getChildren ().findChildNode ("data");
	return (node && !node->getData ().empty ()) ? node : nullptr;
}

//------------------------------------------------------------------------
PlatformBitmapPtr UIBitmapNode::createBitmapFromDataNode () const
{
	if (auto node = dataNode ())
	{
		auto codecStr = node->getAttributes ()->getAttributeValue ("encoding");
		if (codecStr && *codecStr == "base64")
		{
			auto result = Base64Codec::decode (node->getData ());
			if (auto platformBitmap = getPlatformFactory ().createBitmapFromMemory (
			        result.data.get (), result.dataSize))
			{
				double scaleFactor = 1.;
				if (attributes->getDoubleAttribute ("scale-factor", scaleFactor))
					platformBitmap->setScaleFactor (scaleFactor);
				return platformBitmap;
			}
		}
	}
	return nullptr;
}

//-----------------------------------------------------------------------------
CBitmap* UIBitmapNode::getBitmap (const std::string& pathHint)
{
	if (bitmap == nullptr)
	{
		const std::string* path = attributes->getAttributeValue ("path");
		if (path)
		{
			BitmapVariant bitmapVariant;
			int32_t tmpValue {};
			CRect offsets;
			if (attributes->getRectAttribute ("nineparttiled-offsets", offsets))
			{
				bitmapVariant = CNinePartTiledDescription (offsets.left, offsets.top, offsets.right,
														   offsets.bottom);
			}
			else if (attributes->getIntegerAttribute ("multiframe-num-frames", tmpValue))
			{
				CMultiFrameBitmapDescription multiFrameDesc {};
				multiFrameDesc.numFrames = static_cast<uint16_t> (tmpValue);
				if (attributes->getIntegerAttribute ("mulitframe-frames-per-row", tmpValue))
					multiFrameDesc.framesPerRow = static_cast<uint16_t> (tmpValue);
				attributes->getPointAttribute ("multiframe-size", multiFrameDesc.frameSize);
				bitmapVariant = multiFrameDesc;
			}
			bitmap = createBitmap (*path, bitmapVariant);
			if (bitmap->getPlatformBitmap () == nullptr && pathIsAbsolute (pathHint))
			{
				std::string absPath = pathHint;
				if (removeLastPathComponent (absPath))
				{
					absPath += "/" + *path;
					if (auto platformBitmap =
					        getPlatformFactory ().createBitmapFromPath (absPath.c_str ()))
					{
						bitmap->setPlatformBitmap (platformBitmap);
					}
				}
			}
		}
		if (bitmap && bitmap->getPlatformBitmap () == nullptr)
		{
			if (auto platformBitmap = createBitmapFromDataNode ())
				bitmap->setPlatformBitmap (platformBitmap);
		}
		if (bitmap && path && bitmap->getPlatformBitmap () &&
		    bitmap->getPlatformBitmap ()->getScaleFactor () == 1.)
		{
			double scaleFactor = 1.;
			if (Detail::decodeScaleFactorFromName (*path, scaleFactor))
			{
				bitmap->getPlatformBitmap ()->setScaleFactor (scaleFactor);
				attributes->setDoubleAttribute ("scale-factor", scaleFactor);
			}
		}
	}
	return bitmap;
}

//-----------------------------------------------------------------------------
void UIBitmapNode::setBitmap (UTF8StringPtr bitmapName)
{
	std::string name (bitmapName);
	attributes->setAttribute ("path", name);
	if (bitmap)
		bitmap->forget ();
	bitmap = nullptr;
	double scaleFactor = 1.;
	if (Detail::decodeScaleFactorFromName (name, scaleFactor))
		attributes->setDoubleAttribute ("scale-factor", scaleFactor);
	removeXMLData ();
}

//-----------------------------------------------------------------------------
void UIBitmapNode::setMultiFrameDesc (const CMultiFrameBitmapDescription* desc)
{
	if (bitmap)
	{
		if (auto mfb = dynamic_cast<CMultiFrameBitmap*> (bitmap); mfb && desc)
		{
			mfb->setMultiFrameDesc (*desc);
		}
		else
		{
			bitmap->forget ();
			bitmap = nullptr;
		}
	}
	if (desc)
	{
		attributes->setPointAttribute ("multiframe-size", desc->frameSize);
		attributes->setIntegerAttribute ("multiframe-num-frames", desc->numFrames);
		attributes->setIntegerAttribute ("mulitframe-frames-per-row", desc->framesPerRow);
	}
	else
	{
		attributes->removeAttribute ("multiframe-size");
		attributes->removeAttribute ("multiframe-num-frames");
		attributes->removeAttribute ("mulitframe-frames-per-row");
	}
}

//-----------------------------------------------------------------------------
void UIBitmapNode::setNinePartTiledOffset (const CRect* offsets)
{
	if (bitmap)
	{
		auto* tiledBitmap = dynamic_cast<CNinePartTiledBitmap*> (bitmap);
		if (offsets && tiledBitmap)
		{
			tiledBitmap->setPartOffsets (CNinePartTiledDescription (
			    offsets->left, offsets->top, offsets->right, offsets->bottom));
		}
		else
		{
			bitmap->forget ();
			bitmap = nullptr;
		}
	}
	if (offsets)
		attributes->setRectAttribute ("nineparttiled-offsets", *offsets);
	else
		attributes->removeAttribute ("nineparttiled-offsets");
}

//-----------------------------------------------------------------------------
void UIBitmapNode::invalidBitmap ()
{
	if (bitmap)
		bitmap->forget ();
	bitmap = nullptr;
	filterProcessed = false;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
UIFontNode::UIFontNode (const std::string& name, const SharedPointer<UIAttributes>& attributes)
: UINode (name, attributes), font (nullptr)
{
}

//-----------------------------------------------------------------------------
UIFontNode::~UIFontNode () noexcept
{
	if (font)
		font->forget ();
}

//-----------------------------------------------------------------------------
void UIFontNode::freePlatformResources ()
{
	if (font)
		font->forget ();
	font = nullptr;
}

//-----------------------------------------------------------------------------
CFontRef UIFontNode::getFont ()
{
	if (font == nullptr)
	{
		const std::string* nameAttr = attributes->getAttributeValue ("font-name");
		const std::string* sizeAttr = attributes->getAttributeValue ("size");
		const std::string* boldAttr = attributes->getAttributeValue ("bold");
		const std::string* italicAttr = attributes->getAttributeValue ("italic");
		const std::string* underlineAttr = attributes->getAttributeValue ("underline");
		const std::string* strikethroughAttr = attributes->getAttributeValue ("strike-through");
		if (nameAttr)
		{
			int32_t size = 12;
			if (sizeAttr)
				size = (int32_t)strtol (sizeAttr->c_str (), nullptr, 10);
			int32_t fontStyle = 0;
			if (boldAttr && *boldAttr == "true")
				fontStyle |= kBoldFace;
			if (italicAttr && *italicAttr == "true")
				fontStyle |= kItalicFace;
			if (underlineAttr && *underlineAttr == "true")
				fontStyle |= kUnderlineFace;
			if (strikethroughAttr && *strikethroughAttr == "true")
				fontStyle |= kStrikethroughFace;
			if (attributes->hasAttribute ("alternative-font-names"))
			{
				std::list<std::string> fontNames;
				getPlatformFactory ().getAllFontFamilies ([&fontNames] (const std::string& name) {
					fontNames.push_back (name);
					return true;
				});
				if (std::find (fontNames.begin (), fontNames.end (), *nameAttr) == fontNames.end ())
				{
					std::vector<std::string> alternativeFontNames;
					attributes->getStringArrayAttribute ("alternative-font-names",
					                                     alternativeFontNames);
					for (auto& alternateFontName : alternativeFontNames)
					{
						auto trimmedString = trim (UTF8String (alternateFontName));
						if (std::find (fontNames.begin (), fontNames.end (),
						               trimmedString.getString ()) != fontNames.end ())
						{
							font = new CFontDesc (trimmedString.data (), size, fontStyle);
							break;
						}
					}
				}
			}
			if (font == nullptr)
				font = new CFontDesc (nameAttr->c_str (), size, fontStyle);
		}
	}
	return font;
}

//-----------------------------------------------------------------------------
void UIFontNode::setFont (CFontRef newFont)
{
	if (font)
		font->forget ();
	font = newFont;
	font->remember ();

	std::string name (*attributes->getAttributeValue ("name"));
	std::string alternativeNames;
	getAlternativeFontNames (alternativeNames);

	attributes->removeAll ();
	attributes->setAttribute ("name", name);
	attributes->setAttribute ("font-name", newFont->getName ().getString ());
	std::stringstream str;
	str << newFont->getSize ();
	attributes->setAttribute ("size", str.str ());
	if (newFont->getStyle () & kBoldFace)
		attributes->setAttribute ("bold", "true");
	if (newFont->getStyle () & kItalicFace)
		attributes->setAttribute ("italic", "true");
	if (newFont->getStyle () & kUnderlineFace)
		attributes->setAttribute ("underline", "true");
	if (newFont->getStyle () & kStrikethroughFace)
		attributes->setAttribute ("strike-through", "true");

	setAlternativeFontNames (alternativeNames.c_str ());
}

//-----------------------------------------------------------------------------
void UIFontNode::setAlternativeFontNames (UTF8StringPtr fontNames)
{
	if (fontNames && fontNames[0] != 0)
	{
		attributes->setAttribute ("alternative-font-names", fontNames);
	}
	else
	{
		attributes->removeAttribute ("alternative-font-names");
	}
}

//-----------------------------------------------------------------------------
bool UIFontNode::getAlternativeFontNames (std::string& fontNames)
{
	const std::string* value = attributes->getAttributeValue ("alternative-font-names");
	if (value)
	{
		fontNames = *value;
		return true;
	}
	return false;
}

//-----------------------------------------------------------------------------
UIColorNode::UIColorNode (const std::string& name, const SharedPointer<UIAttributes>& attributes)
: UINode (name, attributes)
{
	color.alpha = 255;
	const std::string* red = attributes->getAttributeValue ("red");
	const std::string* green = attributes->getAttributeValue ("green");
	const std::string* blue = attributes->getAttributeValue ("blue");
	const std::string* alpha = attributes->getAttributeValue ("alpha");
	const std::string* rgb = attributes->getAttributeValue ("rgb");
	const std::string* rgba = attributes->getAttributeValue ("rgba");
	if (red)
		color.red = (uint8_t)strtol (red->c_str (), nullptr, 10);
	if (green)
		color.green = (uint8_t)strtol (green->c_str (), nullptr, 10);
	if (blue)
		color.blue = (uint8_t)strtol (blue->c_str (), nullptr, 10);
	if (alpha)
		color.alpha = (uint8_t)strtol (alpha->c_str (), nullptr, 10);
	if (rgb)
		parseColor (*rgb, color);
	if (rgba)
		parseColor (*rgba, color);
}

//-----------------------------------------------------------------------------
void UIColorNode::setColor (const CColor& newColor)
{
	std::string name (*attributes->getAttributeValue ("name"));
	attributes->removeAll ();
	attributes->setAttribute ("name", name);

	std::string colorString;
	UIViewCreator::colorToString (newColor, colorString, nullptr);
	attributes->setAttribute ("rgba", colorString);
	color = newColor;
}

//-----------------------------------------------------------------------------
UIGradientNode::UIGradientNode (const std::string& name,
                                const SharedPointer<UIAttributes>& attributes)
: UINode (name, attributes)
{
}

//-----------------------------------------------------------------------------
void UIGradientNode::freePlatformResources ()
{
	gradient = nullptr;
}

//-----------------------------------------------------------------------------
CGradient* UIGradientNode::getGradient ()
{
	if (gradient == nullptr)
	{
		GradientColorStopMap colorStops;
		double start;
		CColor color;
		for (auto& colorNode : getChildren ())
		{
			if (colorNode->getName () == "color-stop")
			{
				const std::string* rgba = colorNode->getAttributes ()->getAttributeValue ("rgba");
				if (rgba == nullptr ||
				    colorNode->getAttributes ()->getDoubleAttribute ("start", start) == false)
					continue;
				if (parseColor (*rgba, color) == false)
					continue;
				colorStops.emplace (start, color);
			}
		}
		if (colorStops.size () > 1)
			gradient = owned (CGradient::create (colorStops));
	}
	return gradient;
}

//-----------------------------------------------------------------------------
void UIGradientNode::setGradient (CGradient* g)
{
	gradient = g;
	getChildren ().removeAll ();
	if (gradient == nullptr)
		return;

	const GradientColorStopMap colorStops = gradient->getColorStops ();
	for (const auto& colorStop : colorStops)
	{
		UINode* node = new UINode ("color-stop");
		node->getAttributes ()->setDoubleAttribute ("start", colorStop.first);
		std::string colorString;
		UIViewCreator::colorToString (colorStop.second, colorString, nullptr);
		node->getAttributes ()->setAttribute ("rgba", colorString);
		getChildren ().add (node);
	}
}

} // Detail
} // VSTGUI
