/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "FractoriumPch.h"
#include "qcssscanner.h"

/// <summary>
/// The code in this file did not originate in Fractorium.
/// It was taken either in whole or in part from the source code
/// of Qt Creator. Their license applies.
/// </summary>

using namespace QCss;

struct QCssKnownValue
{
	const char* name;
	quint64 id;
};

static const QCssKnownValue properties[NumProperties - 1] =
{
	{ "-qt-background-role", QtBackgroundRole },
	{ "-qt-block-indent", QtBlockIndent },
	{ "-qt-list-indent", QtListIndent },
	{ "-qt-list-number-prefix", QtListNumberPrefix },
	{ "-qt-list-number-suffix", QtListNumberSuffix },
	{ "-qt-paragraph-type", QtParagraphType },
	{ "-qt-style-features", QtStyleFeatures },
	{ "-qt-table-type", QtTableType },
	{ "-qt-user-state", QtUserState },
	{ "alternate-background-color", QtAlternateBackground },
	{ "background", Background },
	{ "background-attachment", BackgroundAttachment },
	{ "background-clip", BackgroundClip },
	{ "background-color", BackgroundColor },
	{ "background-image", BackgroundImage },
	{ "background-origin", BackgroundOrigin },
	{ "background-position", BackgroundPosition },
	{ "background-repeat", BackgroundRepeat },
	{ "border", Border },
	{ "border-bottom", BorderBottom },
	{ "border-bottom-color", BorderBottomColor },
	{ "border-bottom-left-radius", BorderBottomLeftRadius },
	{ "border-bottom-right-radius", BorderBottomRightRadius },
	{ "border-bottom-style", BorderBottomStyle },
	{ "border-bottom-width", BorderBottomWidth },
	{ "border-color", BorderColor },
	{ "border-image", BorderImage },
	{ "border-left", BorderLeft },
	{ "border-left-color", BorderLeftColor },
	{ "border-left-style", BorderLeftStyle },
	{ "border-left-width", BorderLeftWidth },
	{ "border-radius", BorderRadius },
	{ "border-right", BorderRight },
	{ "border-right-color", BorderRightColor },
	{ "border-right-style", BorderRightStyle },
	{ "border-right-width", BorderRightWidth },
	{ "border-style", BorderStyles },
	{ "border-top", BorderTop },
	{ "border-top-color", BorderTopColor },
	{ "border-top-left-radius", BorderTopLeftRadius },
	{ "border-top-right-radius", BorderTopRightRadius },
	{ "border-top-style", BorderTopStyle },
	{ "border-top-width", BorderTopWidth },
	{ "border-width", BorderWidth },
	{ "bottom", Bottom },
	{ "color", Property::Color },
	{ "float", Float },
	{ "font", QCss::Font },
	{ "font-family", FontFamily },
	{ "font-size", FontSize },
	{ "font-style", FontStyle },
	{ "font-variant", FontVariant },
	{ "font-weight", FontWeight },
	{ "height", Height },
	{ "image", QtImage },
	{ "image-position", QtImageAlignment },
	{ "left", Left },
	{ "line-height", LineHeight },
	{ "list-style", ListStyle },
	{ "list-style-type", ListStyleType },
	{ "margin", Margin },
	{ "margin-bottom", MarginBottom },
	{ "margin-left", MarginLeft },
	{ "margin-right", MarginRight },
	{ "margin-top", MarginTop },
	{ "max-height", MaximumHeight },
	{ "max-width", MaximumWidth },
	{ "min-height", MinimumHeight },
	{ "min-width", MinimumWidth },
	{ "outline", Outline },
	{ "outline-bottom-left-radius", OutlineBottomLeftRadius },
	{ "outline-bottom-right-radius", OutlineBottomRightRadius },
	{ "outline-color", OutlineColor },
	{ "outline-offset", OutlineOffset },
	{ "outline-radius", OutlineRadius },
	{ "outline-style", OutlineStyle },
	{ "outline-top-left-radius", OutlineTopLeftRadius },
	{ "outline-top-right-radius", OutlineTopRightRadius },
	{ "outline-width", OutlineWidth },
	{ "padding", Padding },
	{ "padding-bottom", PaddingBottom },
	{ "padding-left", PaddingLeft },
	{ "padding-right", PaddingRight },
	{ "padding-top", PaddingTop },
	{ "page-break-after", PageBreakAfter },
	{ "page-break-before", PageBreakBefore },
	{ "position", Position },
	{ "right", Right },
	{ "selection-background-color", QtSelectionBackground },
	{ "selection-color", QtSelectionForeground },
	{ "spacing", QtSpacing },
	{ "subcontrol-origin", QtOrigin },
	{ "subcontrol-position", QtPosition },
	{ "text-align", TextAlignment },
	{ "text-decoration", TextDecoration },
	{ "text-indent", TextIndent },
	{ "text-transform", TextTransform },
	{ "text-underline-style", TextUnderlineStyle },
	{ "top", Top },
	{ "vertical-align", VerticalAlignment },
	{ "white-space", Whitespace },
	{ "width", Width }
};

static const QCssKnownValue values[NumKnownValues - 1] =
{
	{ "active", Value_Active },
	{ "alternate-base", Value_AlternateBase },
	{ "always", Value_Always },
	{ "auto", Value_Auto },
	{ "base", Value_Base },
	{ "bold", Value_Bold },
	{ "bottom", Value_Bottom },
	{ "bright-text", Value_BrightText },
	{ "button", Value_Button },
	{ "button-text", Value_ButtonText },
	{ "center", Value_Center },
	{ "circle", Value_Circle },
	{ "dark", Value_Dark },
	{ "dashed", Value_Dashed },
	{ "decimal", Value_Decimal },
	{ "disabled", Value_Disabled },
	{ "disc", Value_Disc },
	{ "dot-dash", Value_DotDash },
	{ "dot-dot-dash", Value_DotDotDash },
	{ "dotted", Value_Dotted },
	{ "double", Value_Double },
	{ "groove", Value_Groove },
	{ "highlight", Value_Highlight },
	{ "highlighted-text", Value_HighlightedText },
	{ "inset", Value_Inset },
	{ "italic", Value_Italic },
	{ "large", Value_Large },
	{ "left", Value_Left },
	{ "light", Value_Light },
	{ "line-through", Value_LineThrough },
	{ "link", Value_Link },
	{ "link-visited", Value_LinkVisited },
	{ "lower-alpha", Value_LowerAlpha },
	{ "lower-roman", Value_LowerRoman },
	{ "lowercase", Value_Lowercase },
	{ "medium", Value_Medium },
	{ "mid", Value_Mid },
	{ "middle", Value_Middle },
	{ "midlight", Value_Midlight },
	{ "native", Value_Native },
	{ "none", Value_None },
	{ "normal", Value_Normal },
	{ "nowrap", Value_NoWrap },
	{ "oblique", Value_Oblique },
	{ "off", Value_Off },
	{ "on", Value_On },
	{ "outset", Value_Outset },
	{ "overline", Value_Overline },
	{ "pre", Value_Pre },
	{ "pre-wrap", Value_PreWrap },
	{ "ridge", Value_Ridge },
	{ "right", Value_Right },
	{ "selected", Value_Selected },
	{ "shadow", Value_Shadow },
	{ "small", Value_Small },
	{ "small-caps", Value_SmallCaps },
	{ "solid", Value_Solid },
	{ "square", Value_Square },
	{ "sub", Value_Sub },
	{ "super", Value_Super },
	{ "text", Value_Text },
	{ "top", Value_Top },
	{ "transparent", Value_Transparent },
	{ "underline", Value_Underline },
	{ "upper-alpha", Value_UpperAlpha },
	{ "upper-roman", Value_UpperRoman },
	{ "uppercase", Value_Uppercase },
	{ "wave", Value_Wave },
	{ "window", Value_Window },
	{ "window-text", Value_WindowText },
	{ "x-large", Value_XLarge },
	{ "xx-large", Value_XXLarge }
};

//Map id to strings as they appears in the 'values' array above
static const short indexOfId[NumKnownValues] = { 0, 41, 48, 42, 49, 54, 35, 26, 70, 71, 25, 43, 5, 63, 47,
												 29, 58, 59, 27, 51, 61, 6, 10, 39, 56, 19, 13, 17, 18, 20, 21, 50, 24, 46, 67, 37, 3, 2, 40, 62, 16,
												 11, 57, 14, 32, 64, 33, 65, 55, 66, 34, 69, 8, 28, 38, 12, 36, 60, 7, 9, 4, 68, 53, 22, 23, 30, 31,
												 1, 15, 0, 52, 45, 44
											   };

QString Value::toString() const
{
	if (type == KnownIdentifier)
	{
		return QLatin1String(values[indexOfId[variant.toInt()]].name);
	}
	else
	{
		return variant.toString();
	}
}

static const QCssKnownValue pseudos[NumPseudos - 1] =
{
	{ "active", PseudoClass_Active },
	{ "adjoins-item", PseudoClass_Item },
	{ "alternate", PseudoClass_Alternate },
	{ "bottom", PseudoClass_Bottom },
	{ "checked", PseudoClass_Checked },
	{ "closable", PseudoClass_Closable },
	{ "closed", PseudoClass_Closed },
	{ "default", PseudoClass_Default },
	{ "disabled", PseudoClass_Disabled },
	{ "edit-focus", PseudoClass_EditFocus },
	{ "editable", PseudoClass_Editable },
	{ "enabled", PseudoClass_Enabled },
	{ "exclusive", PseudoClass_Exclusive },
	{ "first", PseudoClass_First },
	{ "flat", PseudoClass_Flat },
	{ "floatable", PseudoClass_Floatable },
	{ "focus", PseudoClass_Focus },
	{ "has-children", PseudoClass_Children },
	{ "has-siblings", PseudoClass_Sibling },
	{ "horizontal", PseudoClass_Horizontal },
	{ "hover", PseudoClass_Hover },
	{ "indeterminate", PseudoClass_Indeterminate },
	{ "last", PseudoClass_Last },
	{ "left", PseudoClass_Left },
	{ "maximized", PseudoClass_Maximized },
	{ "middle", PseudoClass_Middle },
	{ "minimized", PseudoClass_Minimized },
	{ "movable", PseudoClass_Movable },
	{ "next-selected", PseudoClass_NextSelected },
	{ "no-frame", PseudoClass_Frameless },
	{ "non-exclusive", PseudoClass_NonExclusive },
	{ "off", PseudoClass_Unchecked },
	{ "on", PseudoClass_Checked },
	{ "only-one", PseudoClass_OnlyOne },
	{ "open", PseudoClass_Open },
	{ "pressed", PseudoClass_Pressed },
	{ "previous-selected", PseudoClass_PreviousSelected },
	{ "read-only", PseudoClass_ReadOnly },
	{ "right", PseudoClass_Right },
	{ "selected", PseudoClass_Selected },
	{ "top", PseudoClass_Top },
	{ "unchecked", PseudoClass_Unchecked },
	{ "vertical", PseudoClass_Vertical },
	{ "window", PseudoClass_Window }
};

static const QCssKnownValue origins[NumKnownOrigins - 1] =
{
	{ "border", Origin_Border },
	{ "content", Origin_Content },
	{ "margin", Origin_Margin }, // not in css
	{ "padding", Origin_Padding }
};

static const QCssKnownValue repeats[NumKnownRepeats - 1] =
{
	{ "no-repeat", Repeat_None },
	{ "repeat-x", Repeat_X },
	{ "repeat-xy", Repeat_XY },
	{ "repeat-y", Repeat_Y }
};

static const QCssKnownValue tileModes[NumKnownTileModes - 1] =
{
	{ "repeat", TileMode_Repeat },
	{ "round", TileMode_Round },
	{ "stretch", TileMode_Stretch },
};

static const QCssKnownValue positions[NumKnownPositionModes - 1] =
{
	{ "absolute", PositionMode_Absolute },
	{ "fixed", PositionMode_Fixed },
	{ "relative", PositionMode_Relative },
	{ "static", PositionMode_Static }
};

static const QCssKnownValue attachments[NumKnownAttachments - 1] =
{
	{ "fixed", Attachment_Fixed },
	{ "scroll", Attachment_Scroll }
};

static const QCssKnownValue styleFeatures[NumKnownStyleFeatures - 1] =
{
	{ "background-color", StyleFeature_BackgroundColor },
	{ "background-gradient", StyleFeature_BackgroundGradient },
	{ "none", StyleFeature_None }
};

#if defined(Q_CC_MSVC) && _MSC_VER < 1600
Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCssKnownValue& prop1, const QCssKnownValue& prop2)
{
	return QString::compare(QString::fromLatin1(prop1.name), QLatin1String(prop2.name), Qt::CaseInsensitive) < 0;
}
#endif

Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString& name, const QCssKnownValue& prop) noexcept
{
	return QString::compare(name, QLatin1String(prop.name), Qt::CaseInsensitive) < 0;
}

Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCssKnownValue& prop, const QString& name) noexcept
{
	return QString::compare(QLatin1String(prop.name), name, Qt::CaseInsensitive) < 0;
}

static quint64 findKnownValue(const QString& name, const QCssKnownValue* start, int numValues)
{
	if (start != nullptr)
	{
		const QCssKnownValue* end = &start[numValues - 1];
		const QCssKnownValue* prop = std::lower_bound(start, end, name);

		if (prop != nullptr)
			if ((prop == end) || (name < *prop))
				return 0;

		return prop->id;
	}

	return 0;
}

///////////////////////////////////////////////////////////////////////////////
// Value Extractor
ValueExtractor::ValueExtractor(const QVector<Declaration>& decls, const QPalette& pal)
	: declarations(decls), adjustment(0), fontExtracted(false), pal(pal)
{
}

LengthData ValueExtractor::lengthValue(const Value& v)
{
	QString s = v.variant.toString();
	s.reserve(s.length());
	LengthData data;
	data.unit = LengthData::NONE;

	if (s.endsWith(QLatin1String("px"), Qt::CaseInsensitive))
		data.unit = LengthData::Px;
	else if (s.endsWith(QLatin1String("ex"), Qt::CaseInsensitive))
		data.unit = LengthData::Ex;
	else if (s.endsWith(QLatin1String("em"), Qt::CaseInsensitive))
		data.unit = LengthData::Em;

	if (data.unit != LengthData::NONE)
		s.chop(2);

	data.number = s.toDouble();
	return data;
}

static int lengthValueFromData(const LengthData& data, const QFont& f)
{
	if (data.unit == LengthData::Ex)
		return qRound(QFontMetrics(f).xHeight() * data.number);
	else if (data.unit == LengthData::Em)
		return qRound(QFontMetrics(f).height() * data.number);

	return qRound(data.number);
}

int ValueExtractor::lengthValue(const Declaration& decl)
{
	if (decl.d->parsed.isValid())
		return  lengthValueFromData(qvariant_cast<LengthData>(decl.d->parsed), f);

	if (decl.d->values.count() < 1)
		return 0;

	LengthData const data = lengthValue(decl.d->values.at(0));
	decl.d->parsed = QVariant::fromValue<LengthData>(data);
	return lengthValueFromData(data, f);
}

void ValueExtractor::lengthValues(const Declaration& decl, int* m)
{
	if (m != nullptr)
	{
		if (decl.d->parsed.isValid())
		{
			QList<QVariant> v = decl.d->parsed.toList();

			for (int i = 0; i < 4; i++)
				m[i] = lengthValueFromData(qvariant_cast<LengthData>(v.at(i)), f);

			return;
		}

		LengthData datas[4];
		int i;

		for (i = 0; i < qMin(decl.d->values.count(), 4); i++)
			datas[i] = lengthValue(decl.d->values[i]);

		if (i == 0)
		{
			LengthData zero = { 0.0, LengthData::NONE };
			datas[0] = datas[1] = datas[2] = datas[3] = zero;
		}
		else if (i == 1)
		{
			datas[3] = datas[2] = datas[1] = datas[0];
		}
		else if (i == 2)
		{
			datas[2] = datas[0];
			datas[3] = datas[1];
		}
		else if (i == 3)
		{
			datas[3] = datas[1];
		}

		QList<QVariant> v;

		for (i = 0; i < 4; i++)
		{
			v += QVariant::fromValue<LengthData>(datas[i]);
			m[i] = lengthValueFromData(datas[i], f);
		}

		decl.d->parsed = v;
	}
}

bool ValueExtractor::extractGeometry(int* w, int* h, int* minw, int* minh, int* maxw, int* maxh)
{
	extractFont();
	bool hit = false;

	for (int i = 0; i < declarations.count(); i++)
	{
		const Declaration& decl = declarations.at(i);

		switch (decl.d->propertyId)
		{
			case Width: if (w != nullptr) *w = lengthValue(decl); break;

			case Height: if (h != nullptr)  *h = lengthValue(decl); break;

			case MinimumWidth: if (minw != nullptr)  *minw = lengthValue(decl); break;

			case MinimumHeight: if (minh != nullptr) *minh = lengthValue(decl); break;

			case MaximumWidth: if (maxw != nullptr) *maxw = lengthValue(decl); break;

			case MaximumHeight: if (maxh != nullptr) *maxh = lengthValue(decl); break;

			default: continue;
		}

		hit = true;
	}

	return hit;
}

bool ValueExtractor::extractPosition(int* left, int* top, int* right, int* bottom, QCss::Origin* origin,
									 Qt::Alignment* position, QCss::PositionMode* mode, Qt::Alignment* textAlignment)
{
	extractFont();
	bool hit = false;

	for (int i = 0; i < declarations.count(); i++)
	{
		const Declaration& decl = declarations.at(i);

		switch (decl.d->propertyId)
		{
			case Left: if (left != nullptr)  *left = lengthValue(decl); break;

			case Top: if (top != nullptr) *top = lengthValue(decl); break;

			case Right: if (right != nullptr) *right = lengthValue(decl); break;

			case Bottom: if (bottom != nullptr) *bottom = lengthValue(decl); break;

			case QtOrigin: if (origin != nullptr) *origin = decl.originValue(); break;

			case QtPosition: if (position != nullptr) *position = decl.alignmentValue(); break;

			case TextAlignment: if (textAlignment != nullptr) *textAlignment = decl.alignmentValue(); break;

			case Position: if (mode != nullptr) *mode = decl.positionValue(); break;

			default: continue;
		}

		hit = true;
	}

	return hit;
}

bool ValueExtractor::extractBox(int* margins, int* paddings, int* spacing)
{
	extractFont();
	bool hit = false;

	for (int i = 0; i < declarations.count(); i++)
	{
		const Declaration& decl = declarations.at(i);

		switch (decl.d->propertyId)
		{
			case PaddingLeft: if (paddings != nullptr) paddings[LeftEdge] = lengthValue(decl); break;

			case PaddingRight: if (paddings != nullptr) paddings[RightEdge] = lengthValue(decl); break;

			case PaddingTop: if (paddings != nullptr) paddings[TopEdge] = lengthValue(decl); break;

			case PaddingBottom: if (paddings != nullptr) paddings[BottomEdge] = lengthValue(decl); break;

			case Padding:  if (paddings != nullptr) lengthValues(decl, paddings); break;

			case MarginLeft: if (margins != nullptr) margins[LeftEdge] = lengthValue(decl); break;

			case MarginRight: if (margins != nullptr) margins[RightEdge] = lengthValue(decl); break;

			case MarginTop: if (margins != nullptr) margins[TopEdge] = lengthValue(decl); break;

			case MarginBottom: if (margins != nullptr) margins[BottomEdge] = lengthValue(decl); break;

			case Margin: if (margins != nullptr) lengthValues(decl, margins); break;

			case QtSpacing: if (spacing) *spacing = lengthValue(decl); break;

			default: continue;
		}

		hit = true;
	}

	return hit;
}

int ValueExtractor::extractStyleFeatures()
{
	int features = StyleFeature_None;

	for (int i = 0; i < declarations.count(); i++)
	{
		const Declaration& decl = declarations.at(i);

		if (decl.d->propertyId == QtStyleFeatures)
			features = decl.styleFeaturesValue();
	}

	return features;
}

QSize ValueExtractor::sizeValue(const Declaration& decl)
{
	if (decl.d->parsed.isValid())
	{
		QList<QVariant> v = decl.d->parsed.toList();
		return QSize(lengthValueFromData(qvariant_cast<LengthData>(v.at(0)), f),
					 lengthValueFromData(qvariant_cast<LengthData>(v.at(1)), f));
	}

	LengthData x[2] = { {0, LengthData::NONE }, {0, LengthData::NONE} };

	if (decl.d->values.count() > 0)
		x[0] = lengthValue(decl.d->values.at(0));

	if (decl.d->values.count() > 1)
		x[1] = lengthValue(decl.d->values.at(1));
	else
		x[1] = x[0];

	QList<QVariant> v;
	v << QVariant::fromValue<LengthData>(x[0]) << QVariant::fromValue<LengthData>(x[1]);
	decl.d->parsed = v;
	return QSize(lengthValueFromData(x[0], f), lengthValueFromData(x[1], f));
}

void ValueExtractor::sizeValues(const Declaration& decl, QSize* radii)
{
	if (radii != nullptr)
	{
		radii[0] = sizeValue(decl);

		for (int i = 1; i < 4; i++)
			radii[i] = radii[0];
	}
}

bool ValueExtractor::extractBorder(int* borders, QBrush* colors, BorderStyle* styles,
								   QSize* radii)
{
	extractFont();
	bool hit = false;

	for (int i = 0; i < declarations.count(); i++)
	{
		const Declaration& decl = declarations.at(i);

		switch (decl.d->propertyId)
		{
			case BorderLeftWidth: if (borders != nullptr) borders[LeftEdge] = lengthValue(decl); break;

			case BorderRightWidth: if (borders != nullptr) borders[RightEdge] = lengthValue(decl); break;

			case BorderTopWidth: if (borders != nullptr) borders[TopEdge] = lengthValue(decl); break;

			case BorderBottomWidth: if (borders != nullptr) borders[BottomEdge] = lengthValue(decl); break;

			case BorderWidth: if (borders != nullptr) lengthValues(decl, borders); break;

			case BorderLeftColor: if (colors != nullptr) colors[LeftEdge] = decl.brushValue(pal); break;

			case BorderRightColor: if (colors != nullptr) colors[RightEdge] = decl.brushValue(pal); break;

			case BorderTopColor: if (colors != nullptr) colors[TopEdge] = decl.brushValue(pal); break;

			case BorderBottomColor: if (colors != nullptr) colors[BottomEdge] = decl.brushValue(pal); break;

			case BorderColor: if (colors != nullptr) decl.brushValues(colors, pal); break;

			case BorderTopStyle: if (styles != nullptr) styles[TopEdge] = decl.styleValue(); break;

			case BorderBottomStyle: if (styles != nullptr) styles[BottomEdge] = decl.styleValue(); break;

			case BorderLeftStyle: if (styles != nullptr) styles[LeftEdge] = decl.styleValue(); break;

			case BorderRightStyle: if (styles != nullptr) styles[RightEdge] = decl.styleValue(); break;

			case BorderStyles:  if (styles != nullptr) decl.styleValues(styles); break;
#ifndef QT_OS_ANDROID_GCC_48_WORKAROUND

			case BorderTopLeftRadius: if (radii != nullptr) radii[0] = sizeValue(decl); break;
#else

			case BorderTopLeftRadius: new (radii)QSize(sizeValue(decl)); break;
#endif

			case BorderTopRightRadius: if (radii != nullptr) radii[1] = sizeValue(decl); break;

			case BorderBottomLeftRadius: if (radii != nullptr) radii[2] = sizeValue(decl); break;

			case BorderBottomRightRadius: if (radii != nullptr) radii[3] = sizeValue(decl); break;

			case BorderRadius: if (radii != nullptr) sizeValues(decl, radii); break;

			case BorderLeft:
				if (borders != nullptr && styles != nullptr && colors != nullptr)
					borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]);

				break;

			case BorderTop:
				if (borders != nullptr && styles != nullptr && colors != nullptr)
					borderValue(decl, &borders[TopEdge], &styles[TopEdge], &colors[TopEdge]);

				break;

			case BorderRight:
				if (borders != nullptr && styles != nullptr && colors != nullptr)
					borderValue(decl, &borders[RightEdge], &styles[RightEdge], &colors[RightEdge]);

				break;

			case BorderBottom:
				if (borders != nullptr && styles != nullptr && colors != nullptr)
					borderValue(decl, &borders[BottomEdge], &styles[BottomEdge], &colors[BottomEdge]);

				break;

			case Border:
				if (borders != nullptr && styles != nullptr && colors != nullptr)
				{
					borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]);
					borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge];
					styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge];
					colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge];
				}

				break;

			default: continue;
		}

		hit = true;
	}

	return hit;
}

bool ValueExtractor::extractOutline(int* borders, QBrush* colors, BorderStyle* styles,
									QSize* radii, int* offsets)
{
	extractFont();
	bool hit = false;

	for (int i = 0; i < declarations.count(); i++)
	{
		const Declaration& decl = declarations.at(i);

		switch (decl.d->propertyId)
		{
			case OutlineWidth: if (borders != nullptr) lengthValues(decl, borders); break;

			case OutlineColor: if (colors != nullptr) decl.brushValues(colors, pal); break;

			case OutlineStyle:  if (styles != nullptr) decl.styleValues(styles); break;

			case OutlineTopLeftRadius: if (radii != nullptr) radii[0] = sizeValue(decl); break;

			case OutlineTopRightRadius: if (radii != nullptr) radii[1] = sizeValue(decl); break;

			case OutlineBottomLeftRadius: if (radii != nullptr) radii[2] = sizeValue(decl); break;

			case OutlineBottomRightRadius: if (radii != nullptr) radii[3] = sizeValue(decl); break;

			case OutlineRadius: if (radii != nullptr) sizeValues(decl, radii); break;

			case OutlineOffset: if (offsets != nullptr) lengthValues(decl, offsets); break;

			case Outline:
				if (borders != nullptr && styles != nullptr && colors != nullptr)
				{
					borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]);
					borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge];
					styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge];
					colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge];
				}

				break;

			default: continue;
		}

		hit = true;
	}

	return hit;
}

static Qt::Alignment parseAlignment(const QCss::Value* values, int count)
{
	Qt::Alignment a[2] = { Qt::AlignLeft, Qt::AlignLeft };

	if (values != nullptr)
	{
		for (int i = 0; i < qMin(2, count); i++)
		{
			if (values[i].type != Value::KnownIdentifier)
				break;

			switch (values[i].variant.toInt())
			{
				case Value_Left: a[i] = Qt::AlignLeft; break;

				case Value_Right: a[i] = Qt::AlignRight; break;

				case Value_Top: a[i] = Qt::AlignTop; break;

				case Value_Bottom: a[i] = Qt::AlignBottom; break;

				case Value_Center: a[i] = Qt::AlignCenter; break;

				default: break;
			}
		}
	}

	if (a[0] == Qt::AlignCenter && a[1] != 0 && a[1] != Qt::AlignCenter)
		a[0] = (a[1] == Qt::AlignLeft || a[1] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter;

	if ((a[1] == 0 || a[1] == Qt::AlignCenter) && a[0] != Qt::AlignCenter)
		a[1] = (a[0] == Qt::AlignLeft || a[0] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter;

	return a[0] | a[1];
}

static ColorData parseColorValue(QCss::Value v)
{
	if (v.type == Value::Identifier || v.type == Value::String)
	{
		v.variant.convert(QMetaType(QMetaType::QColor));
		v.type = Value::Color;
	}

	if (v.type == Value::Color)
		return qvariant_cast<QColor>(v.variant);

	if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent)
		return QColor(Qt::transparent);

	if (v.type != Value::Function)
		return ColorData();

	QStringList lst = v.variant.toStringList();

	if (lst.count() != 2)
		return ColorData();

	if ((lst.at(0).compare(QLatin1String("palette"), Qt::CaseInsensitive)) == 0)
	{
		const auto role = findKnownValue(lst.at(1).trimmed(), values, NumKnownValues);

		if (role >= Value_FirstColorRole && role <= Value_LastColorRole)
			return (QPalette::ColorRole)(role - Value_FirstColorRole);

		return ColorData();
	}

	const auto rgb = lst.at(0).startsWith(QLatin1String("rgb"));
	const auto rgba = lst.at(0).startsWith(QLatin1String("rgba"));
	Parser p(lst.at(1));

	if (!p.testExpr())
		return ColorData();

	QVector<QCss::Value> colorDigits;

	if (!p.parseExpr(&colorDigits))
		return ColorData();

	for (int i = 0; i < qMin(colorDigits.count(), 7); i += 2)
	{
		if (colorDigits.at(i).type == Value::Percentage)
		{
			colorDigits[i].variant = colorDigits.at(i).variant.toReal() * (255. / 100.);
			colorDigits[i].type = Value::Number;
		}
		else if (colorDigits.at(i).type != Value::Number)
		{
			return ColorData();
		}
	}

	const auto v1 = colorDigits.at(0).variant.toInt();
	const auto v2 = colorDigits.at(2).variant.toInt();
	const auto v3 = colorDigits.at(4).variant.toInt();
	auto alpha = 255;

	if (colorDigits.count() >= 7)
	{
		int alphaValue = colorDigits.at(6).variant.toInt();

		if (rgba && alphaValue <= 1)
			alpha = colorDigits.at(6).variant.toReal() * 255.;
		else
			alpha = alphaValue;
	}

	return rgb ? QColor::fromRgb(v1, v2, v3, alpha)
		   : QColor::fromHsv(v1, v2, v3, alpha);
}

static QColor colorFromData(const ColorData& c, const QPalette& pal)
{
	if (c.type == ColorData::Color)
	{
		return c.color;
	}
	else if (c.type == ColorData::Role)
	{
		return pal.color(c.role);
	}

	return QColor();
}

static BrushData parseBrushValue(const QCss::Value& v, const QPalette& pal)
{
	const auto c = parseColorValue(v);

	if (c.type == ColorData::Color)
	{
		return QBrush(c.color);
	}
	else if (c.type == ColorData::Role)
	{
		return c.role;
	}

	if (v.type != Value::Function)
		return BrushData();

	QStringList lst = v.variant.toStringList();

	if (lst.count() != 2)
		return BrushData();

	QStringList gradFuncs;
	gradFuncs << QLatin1String("qlineargradient") << QLatin1String("qradialgradient") << QLatin1String("qconicalgradient") << QLatin1String("qgradient");
	int gradType = -1;

	if ((gradType = gradFuncs.indexOf(lst.at(0).toLower())) == -1)
		return BrushData();

	QHash<QString, qreal> vars;
	QVector<QGradientStop> stops;
	int spread = -1;
	QStringList spreads;
	spreads << QLatin1String("pad") << QLatin1String("reflect") << QLatin1String("repeat");
	bool dependsOnThePalette = false;
	Parser parser(lst.at(1));

	while (parser.hasNext())
	{
		parser.skipSpace();

		if (!parser.test(IDENT))
			return BrushData();

		QString attr = parser.lexem();
		parser.skipSpace();

		if (!parser.test(COLON))
			return BrushData();

		parser.skipSpace();

		if (attr.compare(QLatin1String("stop"), Qt::CaseInsensitive) == 0)
		{
			QCss::Value stop, color;
			parser.next();

			if (!parser.parseTerm(&stop)) return BrushData();

			parser.skipSpace();
			parser.next();

			if (!parser.parseTerm(&color)) return BrushData();

			const auto cd = parseColorValue(color);

			if (cd.type == ColorData::Role)
				dependsOnThePalette = true;

			stops.append(QGradientStop(stop.variant.toReal(), colorFromData(cd, pal)));
		}
		else
		{
			parser.next();
			QCss::Value value;
			std::ignore = parser.parseTerm(&value);

			if (attr.compare(QLatin1String("spread"), Qt::CaseInsensitive) == 0)
			{
				spread = spreads.indexOf(value.variant.toString());
			}
			else
			{
				vars[attr] = value.variant.toReal();
			}
		}

		parser.skipSpace();
		std::ignore = parser.test(COMMA);
	}

	if (gradType == 0)
	{
		QLinearGradient lg(vars.value(QLatin1String("x1")), vars.value(QLatin1String("y1")),
						   vars.value(QLatin1String("x2")), vars.value(QLatin1String("y2")));
		lg.setCoordinateMode(QGradient::ObjectBoundingMode);
		lg.setStops(stops);

		if (spread != -1)
			lg.setSpread(QGradient::Spread(spread));

		BrushData bd = QBrush(lg);

		if (dependsOnThePalette)
			bd.type = BrushData::DependsOnThePalette;

		return bd;
	}

	if (gradType == 1)
	{
		QRadialGradient rg(vars.value(QLatin1String("cx")), vars.value(QLatin1String("cy")),
						   vars.value(QLatin1String("radius")), vars.value(QLatin1String("fx")),
						   vars.value(QLatin1String("fy")));
		rg.setCoordinateMode(QGradient::ObjectBoundingMode);
		rg.setStops(stops);

		if (spread != -1)
			rg.setSpread(QGradient::Spread(spread));

		BrushData bd = QBrush(rg);

		if (dependsOnThePalette)
			bd.type = BrushData::DependsOnThePalette;

		return bd;
	}

	if (gradType == 2)
	{
		QConicalGradient cg(vars.value(QLatin1String("cx")), vars.value(QLatin1String("cy")),
							vars.value(QLatin1String("angle")));
		cg.setCoordinateMode(QGradient::ObjectBoundingMode);
		cg.setStops(stops);

		if (spread != -1)
			cg.setSpread(QGradient::Spread(spread));

		BrushData bd = QBrush(cg);

		if (dependsOnThePalette)
			bd.type = BrushData::DependsOnThePalette;

		return bd;
	}

	return BrushData();
}

static QBrush brushFromData(const BrushData& c, const QPalette& pal)
{
	if (c.type == BrushData::Role)
	{
		return pal.color(c.role);
	}
	else
	{
		return c.brush;
	}
}

static BorderStyle parseStyleValue(QCss::Value v)
{
	if (v.type == Value::KnownIdentifier)
	{
		switch (v.variant.toInt())
		{
			case Value_None:
				return BorderStyle_None;

			case Value_Dotted:
				return BorderStyle_Dotted;

			case Value_Dashed:
				return BorderStyle_Dashed;

			case Value_Solid:
				return BorderStyle_Solid;

			case Value_Double:
				return BorderStyle_Double;

			case Value_DotDash:
				return BorderStyle_DotDash;

			case Value_DotDotDash:
				return BorderStyle_DotDotDash;

			case Value_Groove:
				return BorderStyle_Groove;

			case Value_Ridge:
				return BorderStyle_Ridge;

			case Value_Inset:
				return BorderStyle_Inset;

			case Value_Outset:
				return BorderStyle_Outset;

			case Value_Native:
				return BorderStyle_Native;

			default:
				break;
		}
	}

	return BorderStyle_Unknown;
}

void ValueExtractor::borderValue(const Declaration& decl, int* width, QCss::BorderStyle* style, QBrush* color)
{
	if (width != nullptr && style != nullptr && color != nullptr)
	{
		if (decl.d->parsed.isValid())
		{
			BorderData data = qvariant_cast<BorderData>(decl.d->parsed);
			*width = lengthValueFromData(data.width, f);
			*style = data.style;
			*color = data.color.type != BrushData::Invalid ? brushFromData(data.color, pal) : QBrush(QColor());
			return;
		}

		*width = 0;
		*style = BorderStyle_None;
		*color = QColor();

		if (decl.d->values.isEmpty())
			return;

		BorderData data;
		data.width.number = 0;
		data.width.unit = LengthData::NONE;
		data.style = BorderStyle_None;
		int i = 0;

		if (decl.d->values.at(i).type == Value::Length || decl.d->values.at(i).type == Value::Number)
		{
			data.width = lengthValue(decl.d->values.at(i));
			*width = lengthValueFromData(data.width, f);

			if (++i >= decl.d->values.count())
			{
				decl.d->parsed = QVariant::fromValue<BorderData>(data);
				return;
			}
		}

		data.style = parseStyleValue(decl.d->values.at(i));

		if (data.style != BorderStyle_Unknown)
		{
			*style = data.style;

			if (++i >= decl.d->values.count())
			{
				decl.d->parsed = QVariant::fromValue<BorderData>(data);
				return;
			}
		}
		else
		{
			data.style = BorderStyle_None;
		}

		data.color = parseBrushValue(decl.d->values.at(i), pal);
		*color = brushFromData(data.color, pal);

		if (data.color.type != BrushData::DependsOnThePalette)
			decl.d->parsed = QVariant::fromValue<BorderData>(data);
	}
}

static void parseShorthandBackgroundProperty(const QVector<QCss::Value>& values, BrushData* brush, QString* image, Repeat* repeat, Qt::Alignment* alignment, const QPalette& pal)
{
	if (brush != nullptr && image != nullptr && repeat != nullptr && alignment != nullptr)
	{
		*brush = BrushData();
		*image = QString();
		*repeat = Repeat_XY;
		*alignment = Qt::AlignTop | Qt::AlignLeft;

		for (int i = 0; i < values.count(); ++i)
		{
			const QCss::Value& v = values.at(i);

			if (v.type == Value::Uri)
			{
				*image = v.variant.toString();
				continue;
			}
			else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_None)
			{
				*image = QString();
				continue;
			}
			else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent)
			{
				*brush = QBrush(Qt::transparent);
			}

			Repeat repeatAttempt = static_cast<Repeat>(findKnownValue(v.variant.toString(),
								   repeats, NumKnownRepeats));

			if (repeatAttempt != Repeat_Unknown)
			{
				*repeat = repeatAttempt;
				continue;
			}

			if (v.type == Value::KnownIdentifier)
			{
				const int start = i;
				int count = 1;

				if (i < values.count() - 1
						&& values.at(i + 1).type == Value::KnownIdentifier)
				{
					++i;
					++count;
				}

				Qt::Alignment a = parseAlignment(values.constData() + start, count);

				if (int(a) != 0)
				{
					*alignment = a;
					continue;
				}

				i -= count - 1;
			}

			*brush = parseBrushValue(v, pal);
		}
	}
}

bool ValueExtractor::extractBackground(QBrush* brush, QString* image, Repeat* repeat,
									   Qt::Alignment* alignment, Origin* origin, Attachment* attachment,
									   Origin* clip)
{
	bool hit = false;

	if (brush != nullptr && image != nullptr && repeat != nullptr && alignment != nullptr && origin != nullptr && attachment != nullptr && clip != nullptr)
	{
		for (int i = 0; i < declarations.count(); ++i)
		{
			const Declaration& decl = declarations.at(i);

			if (decl.d->values.isEmpty())
				continue;

			const QCss::Value& val = decl.d->values.at(0);

			switch (decl.d->propertyId)
			{
				case BackgroundColor:
					*brush = decl.brushValue();
					break;

				case BackgroundImage:
					if (val.type == Value::Uri)
						*image = val.variant.toString();

					break;

				case BackgroundRepeat:
					if (decl.d->parsed.isValid())
					{
						*repeat = static_cast<Repeat>(decl.d->parsed.toInt());
					}
					else
					{
						*repeat = static_cast<Repeat>(findKnownValue(val.variant.toString(),
													  repeats, NumKnownRepeats));
						decl.d->parsed = *repeat;
					}

					break;

				case BackgroundPosition:
					*alignment = decl.alignmentValue();
					break;

				case BackgroundOrigin:
					*origin = decl.originValue();
					break;

				case BackgroundClip:
					*clip = decl.originValue();
					break;

				case Background:
					if (decl.d->parsed.isValid())
					{
						BackgroundData data = qvariant_cast<BackgroundData>(decl.d->parsed);
						*brush = brushFromData(data.brush, pal);
						*image = data.image;
						*repeat = data.repeat;
						*alignment = data.alignment;
					}
					else
					{
						BrushData brushData;
						parseShorthandBackgroundProperty(decl.d->values, &brushData, image, repeat, alignment, pal);
						*brush = brushFromData(brushData, pal);

						if (brushData.type != BrushData::DependsOnThePalette)
						{
							BackgroundData data = { brushData, *image, *repeat, *alignment };
							decl.d->parsed = QVariant::fromValue<BackgroundData>(data);
						}
					}

					break;

				case BackgroundAttachment:
					*attachment = decl.attachmentValue();
					break;

				default: continue;
			}

			hit = true;
		}
	}

	return hit;
}

static bool setFontSizeFromValue(QCss::Value value, QFont* font, int* fontSizeAdjustment)
{
	if (font != nullptr && fontSizeAdjustment != nullptr)
	{
		if (value.type == Value::KnownIdentifier)
		{
			bool valid = true;

			switch (value.variant.toInt())
			{
				case Value_Small: *fontSizeAdjustment = -1; break;

				case Value_Medium: *fontSizeAdjustment = 0; break;

				case Value_Large: *fontSizeAdjustment = 1; break;

				case Value_XLarge: *fontSizeAdjustment = 2; break;

				case Value_XXLarge: *fontSizeAdjustment = 3; break;

				default: valid = false; break;
			}

			return valid;
		}

		if (value.type != Value::Length)
			return false;

		bool valid = false;
		QString s = value.variant.toString();

		if (s.endsWith(QLatin1String("pt"), Qt::CaseInsensitive))
		{
			s.chop(2);
			value.variant = s;

			if (value.variant.convert(QMetaType((QMetaType::Type)qMetaTypeId<qreal>())))
			{
				font->setPointSizeF(value.variant.toReal());
				valid = true;
			}
		}
		else if (s.endsWith(QLatin1String("px"), Qt::CaseInsensitive))
		{
			s.chop(2);
			value.variant = s;

			if (value.variant.convert(QMetaType(QMetaType::Int)))
			{
				font->setPixelSize(value.variant.toInt());
				valid = true;
			}
		}

		return valid;
	}

	return false;
}

static bool setFontStyleFromValue(const QCss::Value& value, QFont* font)
{
	if (value.type != Value::KnownIdentifier)
		return false ;

	if (font != nullptr)
	{
		switch (value.variant.toInt())
		{
			case Value_Normal: font->setStyle(QFont::StyleNormal); return true;

			case Value_Italic: font->setStyle(QFont::StyleItalic); return true;

			case Value_Oblique: font->setStyle(QFont::StyleOblique); return true;

			default: break;
		}
	}

	return false;
}

static QFont::Weight IntToFontWeight(int i) noexcept
{
	if (i <= QFont::Weight::Thin)
		return QFont::Weight::Thin;
	else if (i <= QFont::Weight::ExtraLight)
		return QFont::Weight::ExtraLight;
	else if (i <= QFont::Weight::Light)
		return QFont::Weight::Light;
	else if (i <= QFont::Weight::Normal)
		return QFont::Weight::Normal;
	else if (i <= QFont::Weight::Medium)
		return	QFont::Weight::Medium;
	else if (i <= QFont::Weight::DemiBold)
		return QFont::Weight::DemiBold;
	else if (i <= QFont::Weight::Bold)
		return QFont::Weight::Bold;
	else if (i <= QFont::Weight::ExtraBold)
		return QFont::Weight::ExtraBold;

	return QFont::Weight::Black;
}

static bool setFontWeightFromValue(const QCss::Value& value, QFont* font)
{
	if (font != nullptr)
	{
		if (value.type == Value::KnownIdentifier)
		{
			switch (value.variant.toInt())
			{
				case Value_Normal: font->setWeight(QFont::Normal); return true;

				case Value_Bold: font->setWeight(QFont::Bold); return true;

				default: break;
			}

			return false;
		}

		if (value.type != Value::Number)
			return false;

		font->setWeight(IntToFontWeight(value.variant.toInt()));
	}

	return true;
}

/** \internal
    parse the font family from the values (starting from index \a start)
    and set it the \a font
    The function returns \c true if a family was extracted.
*/
static bool setFontFamilyFromValues(const QVector<QCss::Value>& values, QFont* font, int start = 0)
{
	QString family;
	bool shouldAddSpace = false;

	for (int i = start; i < values.count(); ++i)
	{
		const QCss::Value& v = values.at(i);

		if (v.type == Value::TermOperatorComma)
		{
			family += QLatin1Char(',');
			shouldAddSpace = false;
			continue;
		}

		const QString str = v.variant.toString();

		if (str.isEmpty())
			break;

		if (shouldAddSpace)
			family += QLatin1Char(' ');

		family += str;
		shouldAddSpace = true;
	}

	if (family.isEmpty())
		return false;

	if (font != nullptr)
		font->setFamily(family);

	return true;
}

static void setTextDecorationFromValues(const QVector<QCss::Value>& values, QFont* font)
{
	if (font != nullptr)
	{
		for (int i = 0; i < values.count(); ++i)
		{
			if (values.at(i).type != Value::KnownIdentifier)
				continue;

			switch (values.at(i).variant.toInt())
			{
				case Value_Underline: font->setUnderline(true); break;

				case Value_Overline: font->setOverline(true); break;

				case Value_LineThrough: font->setStrikeOut(true); break;

				case Value_None:
					font->setUnderline(false);
					font->setOverline(false);
					font->setStrikeOut(false);
					break;

				default: break;
			}
		}
	}
}

static void parseShorthandFontProperty(const QVector<QCss::Value>& values, QFont* font, int* fontSizeAdjustment)
{
	if (font != nullptr)
	{
		font->setStyle(QFont::StyleNormal);
		font->setWeight(QFont::Normal);
		*fontSizeAdjustment = -255;
		int i = 0;

		while (i < values.count())
		{
			if (setFontStyleFromValue(values.at(i), font)
					|| setFontWeightFromValue(values.at(i), font))
				++i;
			else
				break;
		}

		if (i < values.count())
		{
			setFontSizeFromValue(values.at(i), font, fontSizeAdjustment);
			++i;
		}

		if (i < values.count())
		{
			setFontFamilyFromValues(values, font, i);
		}
	}
}

static void setFontVariantFromValue(const QCss::Value& value, QFont* font)
{
	if (font != nullptr)
	{
		if (value.type == Value::KnownIdentifier)
		{
			switch (value.variant.toInt())
			{
				case Value_Normal: font->setCapitalization(QFont::MixedCase); break;

				case Value_SmallCaps: font->setCapitalization(QFont::SmallCaps); break;

				default: break;
			}
		}
	}
}

static void setTextTransformFromValue(const QCss::Value& value, QFont* font)
{
	if (font != nullptr)
	{
		if (value.type == Value::KnownIdentifier)
		{
			switch (value.variant.toInt())
			{
				case Value_None: font->setCapitalization(QFont::MixedCase); break;

				case Value_Uppercase: font->setCapitalization(QFont::AllUppercase); break;

				case Value_Lowercase: font->setCapitalization(QFont::AllLowercase); break;

				default: break;
			}
		}
	}
}

bool ValueExtractor::extractFont(QFont* font, int* fontSizeAdjustment)
{
	if (font != nullptr)
	{
		if (fontExtracted)
		{
			*font = f;
			*fontSizeAdjustment = adjustment;
			return fontExtracted == 1;
		}

		bool hit = false;

		for (int i = 0; i < declarations.count(); ++i)
		{
			const Declaration& decl = declarations.at(i);

			if (decl.d->values.isEmpty())
				continue;

			const QCss::Value& val = decl.d->values.at(0);

			switch (decl.d->propertyId)
			{
				case FontSize: setFontSizeFromValue(val, font, fontSizeAdjustment); break;

				case FontStyle: setFontStyleFromValue(val, font); break;

				case FontWeight: setFontWeightFromValue(val, font); break;

				case FontFamily: setFontFamilyFromValues(decl.d->values, font); break;

				case TextDecoration: setTextDecorationFromValues(decl.d->values, font); break;

				case Font: parseShorthandFontProperty(decl.d->values, font, fontSizeAdjustment); break;

				case FontVariant: setFontVariantFromValue(val, font); break;

				case TextTransform: setTextTransformFromValue(val, font); break;

				default: continue;
			}

			hit = true;
		}

		f = *font;
		adjustment = *fontSizeAdjustment;
		fontExtracted = hit ? 1 : 2;
		return hit;
	}

	return false;
}

bool ValueExtractor::extractPalette(QBrush* fg, QBrush* sfg, QBrush* sbg, QBrush* abg)
{
	bool hit = false;

	if (fg != nullptr && sfg != nullptr && sbg != nullptr && abg != nullptr)
	{
		for (int i = 0; i < declarations.count(); ++i)
		{
			const Declaration& decl = declarations.at(i);

			switch (decl.d->propertyId)
			{
				case Color: *fg = decl.brushValue(pal); break;

				case QtSelectionForeground: *sfg = decl.brushValue(pal); break;

				case QtSelectionBackground: *sbg = decl.brushValue(pal); break;

				case QtAlternateBackground: *abg = decl.brushValue(pal); break;

				default: continue;
			}

			hit = true;
		}
	}

	return hit;
}

void ValueExtractor::extractFont()
{
	if (fontExtracted)
		return;

	int dummy = -255;
	extractFont(&f, &dummy);
}

bool ValueExtractor::extractImage(QIcon* icon, Qt::Alignment* a, QSize* size)
{
	bool hit = false;

	if (icon != nullptr && a != nullptr && size != nullptr)
	{
		for (int i = 0; i < declarations.count(); ++i)
		{
			const Declaration& decl = declarations.at(i);

			switch (decl.d->propertyId)
			{
				case QtImage:
					*icon = decl.iconValue();

					if (decl.d->values.count() > 0 && decl.d->values.at(0).type == Value::Uri)
					{
						// try to pull just the size from the image...
						QImageReader imageReader(decl.d->values.at(0).variant.toString());

						if ((*size = imageReader.size()).isNull())
						{
							// but we'll have to load the whole image if the
							// format doesn't support just reading the size
							*size = imageReader.read().size();
						}
					}

					break;

				case QtImageAlignment: *a = decl.alignmentValue();  break;

				default: continue;
			}

			hit = true;
		}
	}

	return hit;
}

///////////////////////////////////////////////////////////////////////////////
// Declaration
QColor Declaration::colorValue(const QPalette& pal) const
{
	if (d->values.count() != 1)
		return QColor();

	if (d->parsed.isValid())
	{
		if (d->parsed.typeId() == QMetaType::QColor)
			return qvariant_cast<QColor>(d->parsed);

		if (d->parsed.typeId() == QMetaType::Int)
			return pal.color((QPalette::ColorRole)(d->parsed.toInt()));
	}

	ColorData color = parseColorValue(d->values.at(0));

	if (color.type == ColorData::Role)
	{
		d->parsed = QVariant::fromValue<int>(color.role);
		return pal.color((QPalette::ColorRole)(color.role));
	}
	else
	{
		d->parsed = QVariant::fromValue<QColor>(color.color);
		return color.color;
	}
}

QBrush Declaration::brushValue(const QPalette& pal) const
{
	if (d->values.count() != 1)
		return QBrush();

	if (d->parsed.isValid())
	{
		if (d->parsed.typeId() == QMetaType::QBrush)
			return qvariant_cast<QBrush>(d->parsed);

		if (d->parsed.typeId() == QMetaType::Int)
			return pal.color((QPalette::ColorRole)(d->parsed.toInt()));
	}

	BrushData data = parseBrushValue(d->values.at(0), pal);

	if (data.type == BrushData::Role)
	{
		d->parsed = QVariant::fromValue<int>(data.role);
		return pal.color((QPalette::ColorRole)(data.role));
	}
	else
	{
		if (data.type != BrushData::DependsOnThePalette)
			d->parsed = QVariant::fromValue<QBrush>(data.brush);

		return data.brush;
	}
}

void Declaration::brushValues(QBrush* c, const QPalette& pal) const
{
	int needParse = 0x1f; // bits 0..3 say if we should parse the corresponding value.
	// the bit 4 say we need to update d->parsed
	int i = 0;

	if (c != nullptr)
	{
		if (d->parsed.isValid())
		{
			needParse = 0;
			QList<QVariant> v = d->parsed.toList();

			for (i = 0; i < qMin(v.count(), 4); i++)
			{
				if (v.at(i).typeId() == QMetaType::QBrush)
				{
					c[i] = qvariant_cast<QBrush>(v.at(i));
				}
				else if (v.at(i).typeId() == QMetaType::Int)
				{
					c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt()));
				}
				else
				{
					needParse |= (1 << i);
				}
			}
		}

		if (needParse != 0)
		{
			QList<QVariant> v;

			for (i = 0; i < qMin(d->values.count(), 4); i++)
			{
				if (!(needParse & (1 << i)))
					continue;

				BrushData data = parseBrushValue(d->values.at(i), pal);

				if (data.type == BrushData::Role)
				{
					v += QVariant::fromValue<int>(data.role);
					c[i] = pal.color((QPalette::ColorRole)(data.role));
				}
				else
				{
					if (data.type != BrushData::DependsOnThePalette)
					{
						v += QVariant::fromValue<QBrush>(data.brush);
					}
					else
					{
						v += QVariant();
					}

					c[i] = data.brush;
				}
			}

			if (needParse & 0x10)
				d->parsed = v;
		}

		if (i == 0) c[0] = c[1] = c[2] = c[3] = QBrush();
		else if (i == 1) c[3] = c[2] = c[1] = c[0];
		else if (i == 2) c[2] = c[0], c[3] = c[1];
		else if (i == 3) c[3] = c[1];
	}
}

bool Declaration::realValue(qreal* real, const char* unit) const
{
	if (d->values.count() != 1)
		return false;

	const Value& v = d->values.at(0);

	if (unit != nullptr && v.type != Value::Length)
		return false;

	QString s = v.variant.toString();

	if (unit != nullptr)
	{
		if (!s.endsWith(QLatin1String(unit), Qt::CaseInsensitive))
			return false;

		s.chop(qstrlen(unit));
	}

	bool ok = false;
	qreal val = s.toDouble(&ok);

	if (ok && real != nullptr)
		*real = val;

	return ok;
}

static bool intValueHelper(const QCss::Value& v, int* i, const char* unit)
{
	if (unit && v.type != Value::Length)
		return false;

	QString s = v.variant.toString();

	if (unit)
	{
		if (!s.endsWith(QLatin1String(unit), Qt::CaseInsensitive))
			return false;

		s.chop(qstrlen(unit));
	}

	bool ok = false;
	int val = s.toInt(&ok);

	if (ok && i != nullptr)
		*i = val;

	return ok;
}

bool Declaration::intValue(int* i, const char* unit) const
{
	if (d->values.count() != 1)
		return false;

	return intValueHelper(d->values.at(0), i, unit);
}

QSize Declaration::sizeValue() const
{
	if (d->parsed.isValid())
		return qvariant_cast<QSize>(d->parsed);

	int x[2] = { 0, 0 };

	if (d->values.count() > 0)
		intValueHelper(d->values.at(0), &x[0], "px");

	if (d->values.count() > 1)
		intValueHelper(d->values.at(1), &x[1], "px");
	else
		x[1] = x[0];

	QSize size(x[0], x[1]);
	d->parsed = QVariant::fromValue<QSize>(size);
	return size;
}

QRect Declaration::rectValue() const
{
	if (d->values.count() != 1)
		return QRect();

	if (d->parsed.isValid())
		return qvariant_cast<QRect>(d->parsed);

	const QCss::Value& v = d->values.at(0);

	if (v.type != Value::Function)
		return QRect();

	QStringList func = v.variant.toStringList();

	if (func.count() != 2 || func.at(0).compare(QLatin1String("rect")) != 0)
		return QRect();

	QStringList args = func[1].split(QLatin1Char(' '), Qt::SplitBehaviorFlags::SkipEmptyParts);

	if (args.count() != 4)
		return QRect();

	QRect rect(args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3].toInt());
	d->parsed = QVariant::fromValue<QRect>(rect);
	return rect;
}

void Declaration::colorValues(QColor* c, const QPalette& pal) const
{
	int i;

	if (c != nullptr)
	{
		if (d->parsed.isValid())
		{
			QList<QVariant> v = d->parsed.toList();

			for (i = 0; i < qMin(d->values.count(), 4); i++)
			{
				if (v.at(i).typeId() == QMetaType::QColor)
				{
					c[i] = qvariant_cast<QColor>(v.at(i));
				}
				else
				{
					c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt()));
				}
			}
		}
		else
		{
			QList<QVariant> v;

			for (i = 0; i < qMin(d->values.count(), 4); i++)
			{
				ColorData color = parseColorValue(d->values.at(i));

				if (color.type == ColorData::Role)
				{
					v += QVariant::fromValue<int>(color.role);
					c[i] = pal.color((QPalette::ColorRole)(color.role));
				}
				else
				{
					v += QVariant::fromValue<QColor>(color.color);
					c[i] = color.color;
				}
			}

			d->parsed = v;
		}

		if (i == 0) c[0] = c[1] = c[2] = c[3] = QColor();
		else if (i == 1) c[3] = c[2] = c[1] = c[0];
		else if (i == 2) c[2] = c[0], c[3] = c[1];
		else if (i == 3) c[3] = c[1];
	}
}

BorderStyle Declaration::styleValue() const
{
	if (d->values.count() != 1)
		return BorderStyle_None;

	return parseStyleValue(d->values.at(0));
}

void Declaration::styleValues(BorderStyle* s) const
{
	if (s != nullptr)
	{
		int i;

		for (i = 0; i < qMin(d->values.count(), 4); i++)
			s[i] = parseStyleValue(d->values.at(i));

		if (i == 0) s[0] = s[1] = s[2] = s[3] = BorderStyle_None;
		else if (i == 1) s[3] = s[2] = s[1] = s[0];
		else if (i == 2) s[2] = s[0], s[3] = s[1];
		else if (i == 3) s[3] = s[1];
	}
}

Repeat Declaration::repeatValue() const
{
	if (d->parsed.isValid())
		return static_cast<Repeat>(d->parsed.toInt());

	if (d->values.count() != 1)
		return Repeat_Unknown;

	const auto v = findKnownValue(d->values.at(0).variant.toString(),
								  repeats, NumKnownRepeats);
	d->parsed = v;
	return static_cast<Repeat>(v);
}

Origin Declaration::originValue() const
{
	if (d->parsed.isValid())
		return static_cast<Origin>(d->parsed.toInt());

	if (d->values.count() != 1)
		return Origin_Unknown;

	const auto v = findKnownValue(d->values.at(0).variant.toString(),
								  origins, NumKnownOrigins);
	d->parsed = v;
	return static_cast<Origin>(v);
}

PositionMode Declaration::positionValue() const
{
	if (d->parsed.isValid())
		return static_cast<PositionMode>(d->parsed.toInt());

	if (d->values.count() != 1)
		return PositionMode_Unknown;

	const auto v = findKnownValue(d->values.at(0).variant.toString(),
								  positions, NumKnownPositionModes);
	d->parsed = v;
	return static_cast<PositionMode>(v);
}

Attachment Declaration::attachmentValue() const
{
	if (d->parsed.isValid())
		return static_cast<Attachment>(d->parsed.toInt());

	if (d->values.count() != 1)
		return Attachment_Unknown;

	const auto v = findKnownValue(d->values.at(0).variant.toString(),
								  attachments, NumKnownAttachments);
	d->parsed = v;
	return static_cast<Attachment>(v);
}

int Declaration::styleFeaturesValue() const
{
	Q_ASSERT(d->propertyId == QtStyleFeatures);

	if (d->parsed.isValid())
		return d->parsed.toInt();

	int features = StyleFeature_None;

	for (int i = 0; i < d->values.count(); i++)
	{
		features |= static_cast<int>(findKnownValue(d->values.value(i).variant.toString(),
									 styleFeatures, NumKnownStyleFeatures));
	}

	d->parsed = features;
	return features;
}

QString Declaration::uriValue() const
{
	if (d->values.isEmpty() || d->values.at(0).type != Value::Uri)
		return QString();

	return d->values.at(0).variant.toString();
}

Qt::Alignment Declaration::alignmentValue() const
{
	if (d->parsed.isValid())
		return Qt::Alignment(d->parsed.toInt());

	if (d->values.isEmpty() || d->values.count() > 2)
		return Qt::AlignLeft | Qt::AlignTop;

	Qt::Alignment v = parseAlignment(d->values.constData(), d->values.count());
	d->parsed = int(v);
	return v;
}

void Declaration::borderImageValue(QString* image, int* cuts,
								   TileMode* h, TileMode* v) const
{
	if (image != nullptr && cuts != nullptr && h != nullptr && v != nullptr)
	{
		*image = uriValue();

		for (int i = 0; i < 4; i++)
			cuts[i] = -1;

		*h = *v = TileMode_Stretch;

		if (d->values.count() < 2)
			return;

		if (d->values.at(1).type == Value::Number)   // cuts!
		{
			int i;

			for (i = 0; i < qMin(d->values.count() - 1, 4); i++)
			{
				const Value& v = d->values.at(i + 1);

				if (v.type != Value::Number)
					break;

				cuts[i] = v.variant.toString().toInt();
			}

			if (i == 0) cuts[0] = cuts[1] = cuts[2] = cuts[3] = 0;
			else if (i == 1) cuts[3] = cuts[2] = cuts[1] = cuts[0];
			else if (i == 2) cuts[2] = cuts[0], cuts[3] = cuts[1];
			else if (i == 3) cuts[3] = cuts[1];
		}

		if (d->values.last().type == Value::Identifier)
		{
			*v = static_cast<TileMode>(findKnownValue(d->values.last().variant.toString(),
									   tileModes, NumKnownTileModes));
		}

		if (d->values[d->values.count() - 2].type == Value::Identifier)
		{
			*h = static_cast<TileMode>
				 (findKnownValue(d->values[d->values.count() - 2].variant.toString(),
								 tileModes, NumKnownTileModes));
		}
		else
			*h = *v;
	}
}

QIcon Declaration::iconValue() const
{
	if (d->parsed.isValid())
		return qvariant_cast<QIcon>(d->parsed);

	QIcon icon;

	for (int i = 0; i < d->values.count();)
	{
		const Value& value = d->values.at(i++);

		if (value.type != Value::Uri)
			break;

		QString uri = value.variant.toString();
		QIcon::Mode mode = QIcon::Normal;
		QIcon::State state = QIcon::Off;

		for (int j = 0; j < 2; j++)
		{
			if (i != d->values.count() && d->values.at(i).type == Value::KnownIdentifier)
			{
				switch (d->values.at(i).variant.toInt())
				{
					case Value_Disabled: mode = QIcon::Disabled; break;

					case Value_Active: mode = QIcon::Active; break;

					case Value_Selected: mode = QIcon::Selected; break;

					case Value_Normal: mode = QIcon::Normal; break;

					case Value_On: state = QIcon::On; break;

					case Value_Off: state = QIcon::Off; break;

					default: break;
				}

				++i;
			}
			else
			{
				break;
			}
		}

		// QIcon is soo broken
		if (icon.isNull())
			icon = QIcon(uri);
		else
			icon.addPixmap(uri, mode, state);

		if (i == d->values.count())
			break;

		if (d->values.at(i).type == Value::TermOperatorComma)
			i++;
	}

	d->parsed = QVariant::fromValue<QIcon>(icon);
	return icon;
}

///////////////////////////////////////////////////////////////////////////////
// Selector
int Selector::specificity() const
{
	int val = 0;

	for (int i = 0; i < basicSelectors.count(); ++i)
	{
		const BasicSelector& sel = basicSelectors.at(i);

		if (!sel.elementName.isEmpty())
			val += 1;

		val += (sel.pseudos.count() + sel.attributeSelectors.count()) * 0x10;
		val += sel.ids.count() * 0x100;
	}

	return val;
}

QString Selector::pseudoElement() const
{
	const BasicSelector& bs = basicSelectors.last();

	if (!bs.pseudos.isEmpty() && bs.pseudos.at(0).type == PseudoClass_Unknown)
		return bs.pseudos.at(0).name;

	return QString();
}

quint64 Selector::pseudoClass(quint64* negated) const
{
	const BasicSelector& bs = basicSelectors.last();

	if (bs.pseudos.isEmpty())
		return PseudoClass_Unspecified;

	quint64 pc = PseudoClass_Unknown;

	for (int i = !pseudoElement().isEmpty(); i < bs.pseudos.count(); i++)
	{
		const Pseudo& pseudo = bs.pseudos.at(i);

		if (pseudo.type == PseudoClass_Unknown)
			return PseudoClass_Unknown;

		if (!pseudo.negated)
			pc |= pseudo.type;
		else if (negated)
			*negated |= pseudo.type;
	}

	return pc;
}

///////////////////////////////////////////////////////////////////////////////
// StyleSheet
void StyleSheet::buildIndexes(Qt::CaseSensitivity nameCaseSensitivity)
{
	QVector<StyleRule> universals;

	for (int i = 0; i < styleRules.count(); ++i)
	{
		const StyleRule& rule = styleRules.at(i);
		QVector<Selector> universalsSelectors;

		for (int j = 0; j < rule.selectors.count(); ++j)
		{
			const Selector& selector = rule.selectors.at(j);

			if (selector.basicSelectors.isEmpty())
				continue;

			if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation)
			{
				if (selector.basicSelectors.count() != 1)
					continue;
			}
			else if (selector.basicSelectors.count() <= 1)
			{
				continue;
			}

			const BasicSelector& sel = selector.basicSelectors.at(selector.basicSelectors.count() - 1);

			if (!sel.ids.isEmpty())
			{
				StyleRule nr;
				nr.selectors += selector;
				nr.declarations = rule.declarations;
				nr.order = i;
				idIndex.insert(sel.ids.at(0), nr);
			}
			else if (!sel.elementName.isEmpty())
			{
				StyleRule nr;
				nr.selectors += selector;
				nr.declarations = rule.declarations;
				nr.order = i;
				QString name = sel.elementName;

				if (nameCaseSensitivity == Qt::CaseInsensitive)
					name = name.toLower();

				nameIndex.insert(name, nr);
			}
			else
			{
				universalsSelectors += selector;
			}
		}

		if (!universalsSelectors.isEmpty())
		{
			StyleRule nr;
			nr.selectors = universalsSelectors;
			nr.declarations = rule.declarations;
			nr.order = i;
			universals << nr;
		}
	}

	styleRules = universals;
}

///////////////////////////////////////////////////////////////////////////////
// StyleSelector
StyleSelector::~StyleSelector()
{
}

bool StyleSelector::nodeNameEquals(NodePtr node, const QString& nodeName) const
{
	return nodeNames(node).contains(nodeName, nameCaseSensitivity);
}

QStringList StyleSelector::nodeIds(NodePtr node) const
{
	return QStringList(attribute(node, QLatin1String("id")));
}

bool StyleSelector::selectorMatches(const Selector& selector, NodePtr node)
{
	if (selector.basicSelectors.isEmpty())
		return false;

	if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation)
	{
		if (selector.basicSelectors.count() != 1)
			return false;

		return basicSelectorMatches(selector.basicSelectors.at(0), node);
	}

	if (selector.basicSelectors.count() <= 1)
		return false;

	int i = selector.basicSelectors.count() - 1;
	node = duplicateNode(node);
	bool match = true;
	BasicSelector sel = selector.basicSelectors.at(i);

	do
	{
		match = basicSelectorMatches(sel, node);

		if (!match)
		{
			if (sel.relationToNext == BasicSelector::MatchNextSelectorIfParent
					|| i == selector.basicSelectors.count() - 1) // first element must always match!
				break;
		}

		if (match || sel.relationToNext != BasicSelector::MatchNextSelectorIfAncestor)
			--i;

		if (i < 0)
			break;

		sel = selector.basicSelectors.at(i);

		if (sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor
				|| sel.relationToNext == BasicSelector::MatchNextSelectorIfParent)
		{
			const auto nextParent = parentNode(node);
			freeNode(node);
			node = nextParent;
		}
		else if (sel.relationToNext == BasicSelector::MatchNextSelectorIfPreceeds)
		{
			const auto previousSibling = previousSiblingNode(node);
			freeNode(node);
			node = previousSibling;
		}

		if (isNullNode(node))
		{
			match = false;
			break;
		}
	}
	while (i >= 0 && (match || sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor));

	freeNode(node);
	return match;
}

bool StyleSelector::basicSelectorMatches(const BasicSelector& sel, NodePtr node)
{
	if (!sel.attributeSelectors.isEmpty())
	{
		if (!hasAttributes(node))
			return false;

		for (int i = 0; i < sel.attributeSelectors.count(); ++i)
		{
			const QCss::AttributeSelector& a = sel.attributeSelectors.at(i);
			const QString attrValue = attribute(node, a.name);

			if (attrValue.isNull())
				return false;

			if (a.valueMatchCriterium == QCss::AttributeSelector::MatchContains)
			{
				QStringList lst = attrValue.split(QLatin1Char(' '));

				if (!lst.contains(a.value))
					return false;
			}
			else if (
				(a.valueMatchCriterium == QCss::AttributeSelector::MatchEqual
				 && attrValue != a.value)
				||
				(a.valueMatchCriterium == QCss::AttributeSelector::MatchBeginsWith
				 && !attrValue.startsWith(a.value))
			)
				return false;
		}
	}

	if (!sel.elementName.isEmpty()
			&& !nodeNameEquals(node, sel.elementName))
		return false;

	if (!sel.ids.isEmpty()
			&& sel.ids != nodeIds(node))
		return false;

	return true;
}

void StyleSelector::matchRule(NodePtr node, const StyleRule& rule, StyleSheetOrigin origin,
							  int depth, QMap<uint, StyleRule>* weightedRules)
{
	for (int j = 0; j < rule.selectors.count(); ++j)
	{
		const Selector& selector = rule.selectors.at(j);

		if (selectorMatches(selector, node))
		{
			uint const weight = rule.order
								+ selector.specificity() * 0x100
								+ (uint(origin) + depth) * 0x100000;
			StyleRule newRule = rule;

			if (rule.selectors.count() > 1)
			{
				newRule.selectors.resize(1);
				newRule.selectors[0] = selector;
			}

			//We might have rules with the same weight if they came from a rule with several selectors
			if (weightedRules != nullptr)
				weightedRules->insert(weight, newRule);
		}
	}
}

// Returns style rules that are in ascending order of specificity
// Each of the StyleRule returned will contain exactly one Selector
QVector<StyleRule> StyleSelector::styleRulesForNode(NodePtr node)
{
	QVector<StyleRule> rules;

	if (styleSheets.isEmpty())
		return rules;

	QMap<uint, StyleRule> weightedRules; // (spec, rule) that will be sorted below

	//prune using indexed stylesheet
	for (int sheetIdx = 0; sheetIdx < styleSheets.count(); ++sheetIdx)
	{
		const StyleSheet& styleSheet = styleSheets.at(sheetIdx);

		for (int i = 0; i < styleSheet.styleRules.count(); ++i)
		{
			matchRule(node, styleSheet.styleRules.at(i), styleSheet.origin, styleSheet.depth, &weightedRules);
		}

		if (!styleSheet.idIndex.isEmpty())
		{
			QStringList ids = nodeIds(node);

			for (int i = 0; i < ids.count(); i++)
			{
				const QString& key = ids.at(i);
				QMultiHash<QString, StyleRule>::const_iterator it = styleSheet.idIndex.constFind(key);

				while (it != styleSheet.idIndex.constEnd() && it.key() == key)
				{
					matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules);
					++it;
				}
			}
		}

		if (!styleSheet.nameIndex.isEmpty())
		{
			QStringList names = nodeNames(node);

			for (int i = 0; i < names.count(); i++)
			{
				QString name = names.at(i);

				if (nameCaseSensitivity == Qt::CaseInsensitive)
					name = name.toLower();

				QMultiHash<QString, StyleRule>::const_iterator it = styleSheet.nameIndex.constFind(name);

				while (it != styleSheet.nameIndex.constEnd() && it.key() == name)
				{
					matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules);
					++it;
				}
			}
		}

		if (!medium.isEmpty())
		{
			for (int i = 0; i < styleSheet.mediaRules.count(); ++i)
			{
				if (styleSheet.mediaRules.at(i).media.contains(medium, Qt::CaseInsensitive))
				{
					for (int j = 0; j < styleSheet.mediaRules.at(i).styleRules.count(); ++j)
					{
						matchRule(node, styleSheet.mediaRules.at(i).styleRules.at(j), styleSheet.origin,
								  styleSheet.depth, &weightedRules);
					}
				}
			}
		}
	}

	rules.reserve(weightedRules.count());
	QMap<uint, StyleRule>::const_iterator it = weightedRules.constBegin();

	for ( ; it != weightedRules.constEnd() ; ++it)
		rules += *it;

	return rules;
}

// for qtexthtmlparser which requires just the declarations with Enabled state
// and without pseudo elements
QVector<Declaration> StyleSelector::declarationsForNode(NodePtr node, const char* extraPseudo)
{
	QVector<Declaration> decls;
	QVector<StyleRule> rules = styleRulesForNode(node);

	for (int i = 0; i < rules.count(); i++)
	{
		const Selector& selector = rules.at(i).selectors.at(0);
		const QString pseudoElement = selector.pseudoElement();

		if (extraPseudo && pseudoElement == QLatin1String(extraPseudo))
		{
			decls += rules.at(i).declarations;
			continue;
		}

		if (!pseudoElement.isEmpty()) // skip rules with pseudo elements
			continue;

		quint64 const pseudoClass = selector.pseudoClass();

		if (pseudoClass == PseudoClass_Enabled || pseudoClass == PseudoClass_Unspecified)
			decls += rules.at(i).declarations;
	}

	return decls;
}

QString Symbol::lexem() const
{
	QString result;

	if (len > 0)
		result.reserve(len);

	for (int i = 0; i < len; ++i)
	{
		if (text.at(start + i) == QLatin1Char('\\') && i < len - 1)
			++i;

		result += text.at(start + i);
	}

	return result;
}

Parser::Parser(const QString& css, bool isFile)
{
	init(css, isFile);
}

Parser::Parser() noexcept
{
	index = 0;
	errorIndex = -1;
	hasEscapeSequences = false;
}

void Parser::init(const QString& css, bool isFile)
{
	QString styleSheet = css;

	if (isFile)
	{
		QFile file(css);

		if (file.open(QFile::ReadOnly))
		{
			sourcePath = QFileInfo(styleSheet).absolutePath() + QLatin1Char('/');
			QTextStream stream(&file);
			styleSheet = stream.readAll();
		}
		else
		{
			qWarning() << "QCss::Parser - Failed to load file " << css;
			styleSheet.clear();
		}
	}
	else
	{
		sourcePath.clear();
	}

	hasEscapeSequences = false;
	symbols.resize(0);
	symbols.reserve(8);
	Scanner::scan(Scanner::preprocess(styleSheet, &hasEscapeSequences), &symbols);
	index = 0;
	errorIndex = -1;
}

bool Parser::parse(StyleSheet* styleSheet, Qt::CaseSensitivity nameCaseSensitivity)
{
	if (testTokenAndEndsWith(ATKEYWORD_SYM, QLatin1String("charset")))
	{
		if (!next(STRING)) return false;

		if (!next(SEMICOLON)) return false;
	}

	while (test(S) || test(CDO) || test(CDC)) {}

	if (styleSheet != nullptr)
	{
		while (testImport())
		{
			ImportRule rule;

			if (!parseImport(&rule)) return false;

			styleSheet->importRules.append(rule);

			while (test(S) || test(CDO) || test(CDC)) {}
		}

		do
		{
			if (testMedia())
			{
				MediaRule rule;

				if (!parseMedia(&rule)) return false;

				styleSheet->mediaRules.append(rule);
			}
			else if (testPage())
			{
				PageRule rule;

				if (!parsePage(&rule)) return false;

				styleSheet->pageRules.append(rule);
			}
			else if (testRuleset())
			{
				StyleRule rule;

				if (!parseRuleset(&rule)) return false;

				styleSheet->styleRules.append(rule);
			}
			else if (test(ATKEYWORD_SYM))
			{
				if (!until(RBRACE)) return false;
			}
			else if (hasNext())
			{
				return false;
			}

			while (test(S) || test(CDO) || test(CDC)) {}
		}
		while (hasNext());

		styleSheet->buildIndexes(nameCaseSensitivity);
	}

	return true;
}

Symbol Parser::errorSymbol()
{
	if (errorIndex == -1) return Symbol();

	return symbols.at(errorIndex);
}

static inline void removeOptionalQuotes(QString* str)
{
	if (str != nullptr)
	{
		if (!str->startsWith(QLatin1Char('\''))
				&& !str->startsWith(QLatin1Char('\"')))
			return;

		str->remove(0, 1);
		str->chop(1);
	}
}

bool Parser::parseImport(ImportRule* importRule)
{
	if (importRule != nullptr)
	{
		skipSpace();

		if (test(STRING))
		{
			importRule->href = lexem();
		}
		else
		{
			if (!testAndParseUri(&importRule->href)) return false;
		}

		removeOptionalQuotes(&importRule->href);
		skipSpace();

		if (testMedium())
		{
			if (!parseMedium(&importRule->media)) return false;

			while (test(COMMA))
			{
				skipSpace();

				if (!parseNextMedium(&importRule->media)) return false;
			}
		}

		if (!next(SEMICOLON)) return false;

		skipSpace();
	}

	return true;
}

bool Parser::parseMedia(MediaRule* mediaRule)
{
	if (mediaRule != nullptr)
	{
		do
		{
			skipSpace();

			if (!parseNextMedium(&mediaRule->media)) return false;
		}
		while (test(COMMA));

		if (!next(LBRACE)) return false;

		skipSpace();

		while (testRuleset())
		{
			StyleRule rule;

			if (!parseRuleset(&rule)) return false;

			mediaRule->styleRules.append(rule);
		}

		if (!next(RBRACE)) return false;

		skipSpace();
	}

	return true;
}

bool Parser::parseMedium(QStringList* media)
{
	if (media != nullptr)
	{
		media->append(lexem());
		skipSpace();
	}

	return true;
}

bool Parser::parsePage(PageRule* pageRule)
{
	if (pageRule != nullptr)
	{
		skipSpace();

		if (testPseudoPage())
			if (!parsePseudoPage(&pageRule->selector)) return false;

		skipSpace();

		if (!next(LBRACE)) return false;

		do
		{
			skipSpace();
			Declaration decl;

			if (!parseNextDeclaration(&decl)) return false;

			if (!decl.isEmpty())
				pageRule->declarations.append(decl);
		}
		while (test(SEMICOLON));

		if (!next(RBRACE)) return false;

		skipSpace();
	}

	return true;
}

bool Parser::parsePseudoPage(QString* selector)
{
	if (!next(IDENT)) return false;

	*selector = lexem();
	return true;
}

bool Parser::parseNextOperator(Value* value)
{
	if (!hasNext()) return true;

	if (value != nullptr)
	{
		switch (next())
		{
			case SLASH: value->type = Value::TermOperatorSlash; skipSpace(); break;

			case COMMA: value->type = Value::TermOperatorComma; skipSpace(); break;

			default: prev(); break;
		}
	}

	return true;
}

bool Parser::parseCombinator(BasicSelector::Relation* relation)
{
	if (relation != nullptr)
	{
		*relation = BasicSelector::NoRelation;

		if (lookup() == S)
		{
			*relation = BasicSelector::MatchNextSelectorIfAncestor;
			skipSpace();
		}
		else
		{
			prev();
		}

		if (test(PLUS))
		{
			*relation = BasicSelector::MatchNextSelectorIfPreceeds;
		}
		else if (test(GREATER))
		{
			*relation = BasicSelector::MatchNextSelectorIfParent;
		}

		skipSpace();
	}

	return true;
}

bool Parser::parseProperty(Declaration* decl)
{
	if (decl != nullptr)
	{
		decl->d->property = lexem();
		decl->d->propertyId = static_cast<Property>(findKnownValue(decl->d->property, properties, NumProperties));
		skipSpace();
	}

	return true;
}

bool Parser::parseRuleset(StyleRule* styleRule)
{
	if (styleRule != nullptr)
	{
		Selector sel;

		if (!parseSelector(&sel)) return false;

		styleRule->selectors.append(sel);

		while (test(COMMA))
		{
			skipSpace();
			Selector sel;

			if (!parseNextSelector(&sel)) return false;

			styleRule->selectors.append(sel);
		}

		skipSpace();

		if (!next(LBRACE)) return false;

		const int declarationStart = index;

		do
		{
			skipSpace();
			Declaration decl;
			const int rewind = index;

			if (!parseNextDeclaration(&decl))
			{
				index = rewind;
				const bool foundSemicolon = until(SEMICOLON);
				const int semicolonIndex = index;
				index = declarationStart;
				const bool foundRBrace = until(RBRACE);

				if (foundSemicolon && semicolonIndex < index)
				{
					decl = Declaration();
					index = semicolonIndex - 1;
				}
				else
				{
					skipSpace();
					return foundRBrace;
				}
			}

			if (!decl.isEmpty())
				styleRule->declarations.append(decl);
		}
		while (test(SEMICOLON));

		if (!next(RBRACE)) return false;

		skipSpace();
	}

	return true;
}

bool Parser::parseSelector(Selector* sel)
{
	if (sel != nullptr)
	{
		BasicSelector basicSel;

		if (!parseSimpleSelector(&basicSel)) return false;

		while (testCombinator())
		{
			if (!parseCombinator(&basicSel.relationToNext)) return false;

			if (!testSimpleSelector()) break;

			sel->basicSelectors.append(basicSel);
			basicSel = BasicSelector();

			if (!parseSimpleSelector(&basicSel)) return false;
		}

		sel->basicSelectors.append(basicSel);
	}

	return true;
}

bool Parser::parseSimpleSelector(BasicSelector* basicSel)
{
	if (basicSel == nullptr)
		return false;

	int minCount = 0;

	if (lookupElementName())
	{
		if (!parseElementName(&basicSel->elementName)) return false;
	}
	else
	{
		prev();
		minCount = 1;
	}

	bool onceMore;
	int count = 0;

	do
	{
		onceMore = false;

		if (test(HASH))
		{
			QString theid = lexem();
			// chop off leading #
			theid.remove(0, 1);
			basicSel->ids.append(theid);
			onceMore = true;
		}
		else if (testClass())
		{
			onceMore = true;
			AttributeSelector a;
			a.name = QLatin1String("class");
			a.valueMatchCriterium = AttributeSelector::MatchContains;

			if (!parseClass(&a.value)) return false;

			basicSel->attributeSelectors.append(a);
		}
		else if (testAttrib())
		{
			onceMore = true;
			AttributeSelector a;

			if (!parseAttrib(&a)) return false;

			basicSel->attributeSelectors.append(a);
		}
		else if (testPseudo())
		{
			onceMore = true;
			Pseudo ps;

			if (!parsePseudo(&ps)) return false;

			basicSel->pseudos.append(ps);
		}

		if (onceMore) ++count;
	}
	while (onceMore);

	return count >= minCount;
}

bool Parser::parseClass(QString* name)
{
	if (!next(IDENT)) return false;

	if (name != nullptr)
		*name = lexem();

	return true;
}

bool Parser::parseElementName(QString* name)
{
	if (name != nullptr)
	{
		switch (lookup())
		{
			case STAR: name->clear(); break;

			case IDENT: *name = lexem(); break;

			default: return false;
		}
	}

	return true;
}

bool Parser::parseAttrib(AttributeSelector* attr)
{
	skipSpace();

	if (!next(IDENT)) return false;

	if (attr != nullptr)
	{
		attr->name = lexem();
		skipSpace();

		if (test(EQUAL))
		{
			attr->valueMatchCriterium = AttributeSelector::MatchEqual;
		}
		else if (test(INCLUDES))
		{
			attr->valueMatchCriterium = AttributeSelector::MatchContains;
		}
		else if (test(DASHMATCH))
		{
			attr->valueMatchCriterium = AttributeSelector::MatchBeginsWith;
		}
		else
		{
			return next(RBRACKET);
		}

		skipSpace();

		if (!test(IDENT) && !test(STRING)) return false;

		attr->value = unquotedLexem();
		skipSpace();
	}

	return next(RBRACKET);
}

bool Parser::parsePseudo(Pseudo* pseudo)
{
	std::ignore = test(COLON);

	if (pseudo != nullptr)
	{
		pseudo->negated = test(EXCLAMATION_SYM);

		if (test(IDENT))
		{
			pseudo->name = lexem();
			pseudo->type = static_cast<quint64>(findKnownValue(pseudo->name, pseudos, NumPseudos));
			return true;
		}

		if (!next(FUNCTION)) return false;

		pseudo->function = lexem();
		// chop off trailing parenthesis
		pseudo->function.chop(1);
		skipSpace();

		if (!test(IDENT)) return false;

		pseudo->name = lexem();
		skipSpace();
	}

	return next(RPAREN);
}

bool Parser::parseNextDeclaration(Declaration* decl)
{
	if (!testProperty())
		return true; // not an error!

	if (decl != nullptr)
	{
		if (!parseProperty(decl)) return false;

		if (!next(COLON)) return false;

		skipSpace();

		if (!parseNextExpr(&decl->d->values)) return false;

		if (testPrio())
			if (!parsePrio(decl)) return false;
	}

	return true;
}

bool Parser::testPrio()
{
	const int rewind = index;

	if (!test(EXCLAMATION_SYM)) return false;

	skipSpace();

	if (!test(IDENT))
	{
		index = rewind;
		return false;
	}

	if (lexem().compare(QLatin1String("important"), Qt::CaseInsensitive) != 0)
	{
		index = rewind;
		return false;
	}

	return true;
}

bool Parser::parsePrio(Declaration* declaration)
{
	if (declaration != nullptr)
		declaration->d->important = true;

	skipSpace();
	return true;
}

bool Parser::parseExpr(QVector<Value>* values)
{
	Value val;

	if (!parseTerm(&val)) return false;

	if (values != nullptr)
	{
		values->append(val);
		bool onceMore;

		do
		{
			onceMore = false;
			val = Value();

			if (!parseNextOperator(&val)) return false;

			if (val.type != QCss::Value::Unknown)
				values->append(val);

			if (testTerm())
			{
				onceMore = true;
				val = Value();

				if (!parseTerm(&val)) return false;

				values->append(val);
			}
		}
		while (onceMore);
	}

	return true;
}

bool Parser::testTerm()
{
	return test(PLUS) || test(MINUS)
		   || test(NUMBER)
		   || test(PERCENTAGE)
		   || test(LENGTH)
		   || test(STRING)
		   || test(IDENT)
		   || testHexColor()
		   || testFunction();
}

bool Parser::parseTerm(Value* value)
{
	QString str = lexem();
	bool haveUnary = false;

	if (lookup() == PLUS || lookup() == MINUS)
	{
		haveUnary = true;

		if (!hasNext()) return false;

		next();
		str += lexem();
	}

	if (value != nullptr)
	{
		value->variant = str;
		value->type = QCss::Value::String;

		switch (lookup())
		{
			case NUMBER:
				value->type = Value::Number;
				value->variant.convert(QMetaType(QMetaType::Double));
				break;

			case PERCENTAGE:
				value->type = Value::Percentage;
				str.chop(1); // strip off %
				value->variant = str;
				break;

			case LENGTH:
				value->type = Value::Length;
				break;

			case STRING:
				if (haveUnary) return false;

				value->type = Value::String;
				str.chop(1);
				str.remove(0, 1);
				value->variant = str;
				break;

			case IDENT:
			{
				if (haveUnary) return false;

				value->type = Value::Identifier;
				const int theid = findKnownValue(str, values, NumKnownValues);

				if (theid != 0)
				{
					value->type = Value::KnownIdentifier;
					value->variant = theid;
				}

				break;
			}

			default:
			{
				if (haveUnary) return false;

				prev();

				if (testHexColor())
				{
					QColor col;

					if (!parseHexColor(&col)) return false;

					value->type = Value::Color;
					value->variant = col;
				}
				else if (testFunction())
				{
					QString name, args;

					if (!parseFunction(&name, &args)) return false;

					if (name == QLatin1String("url"))
					{
						value->type = Value::Uri;
						removeOptionalQuotes(&args);

						if (QFileInfo(args).isRelative() && !sourcePath.isEmpty())
						{
							args.prepend(sourcePath);
						}

						value->variant = args;
					}
					else
					{
						value->type = Value::Function;
						value->variant = QStringList() << name << args;
					}
				}
				else
				{
					return recordError();
				}

				return true;
			}
		}

		skipSpace();
	}

	return true;
}

bool Parser::parseFunction(QString* name, QString* args)
{
	if (name != nullptr && args != nullptr)
	{
		*name = lexem();
		name->chop(1);
		skipSpace();
		const int start = index;

		if (!until(RPAREN)) return false;

		for (int i = start; i < index - 1; ++i)
			args->append(symbols.at(i).lexem());

		/*
			if (!nextExpr(&arguments)) return false;
			if (!next(RPAREN)) return false;
		*/
		skipSpace();
	}

	return true;
}

bool Parser::parseHexColor(QColor* col)
{
	if (col != nullptr)
	{
		col->setNamedColor(lexem());

		if (!col->isValid())
		{
			qWarning("QCssParser::parseHexColor: Unknown color name '%s'", lexem().toLatin1().constData());
			return false;
		}

		skipSpace();
	}

	return true;
}

bool Parser::testAndParseUri(QString* uri)
{
	const int rewind = index;

	if (!testFunction()) return false;

	QString name, args;

	if (!parseFunction(&name, &args))
	{
		index = rewind;
		return false;
	}

	if (name.toLower() != QLatin1String("url"))
	{
		index = rewind;
		return false;
	}

	if (uri != nullptr)
	{
		*uri = args;
		removeOptionalQuotes(uri);
	}

	return true;
}

bool Parser::testSimpleSelector()
{
	return testElementName()
		   || (test(HASH))
		   || testClass()
		   || testAttrib()
		   || testPseudo();
}

bool Parser::next(QCss::TokenType t)
{
	if (hasNext() && next() == t)
		return true;

	return recordError();
}

bool Parser::test(QCss::TokenType t) noexcept
{
	if (index >= symbols.count())
		return false;

	if (symbols.at(index).token == t)
	{
		++index;
		return true;
	}

	return false;
}

QString Parser::unquotedLexem() const
{
	QString s = lexem();

	if (lookup() == STRING)
	{
		s.chop(1);
		s.remove(0, 1);
	}

	return s;
}

QString Parser::lexemUntil(QCss::TokenType t)
{
	QString lexem;

	while (hasNext() && next() != t)
		lexem += symbol().lexem();

	return lexem;
}

bool Parser::until(QCss::TokenType target, QCss::TokenType target2) noexcept
{
	int braceCount = 0;
	int brackCount = 0;
	int parenCount = 0;

	if (index)
	{
		switch (symbols.at(index - 1).token)
		{
			case LBRACE: ++braceCount; break;

			case LBRACKET: ++brackCount; break;

			case FUNCTION:
			case LPAREN: ++parenCount; break;

			default: ;
		}
	}

	while (index < symbols.size())
	{
		QCss::TokenType const t = symbols.at(index++).token;

		switch (t)
		{
			case LBRACE: ++braceCount; break;

			case RBRACE: --braceCount; break;

			case LBRACKET: ++brackCount; break;

			case RBRACKET: --brackCount; break;

			case FUNCTION:
			case LPAREN: ++parenCount; break;

			case RPAREN: --parenCount; break;

			default: break;
		}

		if ((t == target || (target2 != NONE && t == target2))
				&& braceCount <= 0
				&& brackCount <= 0
				&& parenCount <= 0)
			return true;

		if (braceCount < 0 || brackCount < 0 || parenCount < 0)
		{
			--index;
			break;
		}
	}

	return false;
}

bool Parser::testTokenAndEndsWith(QCss::TokenType t, QLatin1String str)
{
	if (!test(t)) return false;

	if (!lexem().endsWith(str, Qt::CaseInsensitive))
	{
		prev();
		return false;
	}

	return true;
}