/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2014  Georg Rudoy
 *
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************/

#include "htmlhighlighter.h"

namespace LC
{
namespace LHTR
{
	HtmlHighlighter::HtmlHighlighter (QTextDocument *doc)
	: QSyntaxHighlighter { doc }
	{
		auto set = [this] (Construct c, const QTextCharFormat& fmt)
		{
			Formats_ [static_cast<int> (c)] = fmt;
		};
		QTextCharFormat entityFormat;
		entityFormat.setForeground ({ { 0, 128, 0 } });
		entityFormat.setFontWeight (QFont::Bold);
		set (Construct::Entity, entityFormat);

		QTextCharFormat tagFormat;
		tagFormat.setFontWeight (QFont::Bold);
		set (Construct::Tag, tagFormat);

		QTextCharFormat commentFormat;
		commentFormat.setForeground ({ { 128, 10, 74 } });
		commentFormat.setFontItalic (true);
		set (Construct::Comment, commentFormat);

		QTextCharFormat attrNameFormat;
		attrNameFormat.setForeground ({ { 10, 128, 10 } });
		set (Construct::AttrName, attrNameFormat);

		QTextCharFormat attrValueFormat;
		attrValueFormat.setForeground ({ { 128, 10, 74 } });
		set (Construct::AttrValue, attrValueFormat);
	}

	namespace
	{
		bool IsStartComment (int pos, const QString& str)
		{
			if (pos + 4 > str.size ())
				return false;

			return str.at (pos) == '<' &&
					str.at (pos + 1) == '!' &&
					str.at (pos + 2) == '-' &&
					str.at (pos + 3) == '-';
		}
	}

	void HtmlHighlighter::highlightBlock (const QString& block)
	{
		auto state = static_cast<State> (previousBlockState ());
		int start = 0;
		int pos = 0;
		const auto blockLen = block.size ();

		auto getFmt = [this] (Construct c) { return Formats_ [static_cast<int> (c)]; };

		while (pos < blockLen)
		{
			switch (state)
			{
			case State::NoState:
			case State::Normal:
				while (pos < blockLen)
				{
					const auto& ch = block.at (pos);
					if (ch == '<')
					{
						state = IsStartComment (pos, block) ? State::Comment : State::Tag;
						break;
					}
					else if (ch == '&')
					{
						start = pos;
						const auto endIdx = block.indexOf (';', pos + 1);
						if (endIdx > 0)
						{
							pos = endIdx + 1;
							setFormat (start, pos - start, getFmt (Construct::Entity));
						}
						else
							pos = blockLen;
					}
					else
						++pos;
				}
				break;
			case State::Comment:
			{
				start = pos;
				const auto endIdx = block.indexOf ("-->", pos);
				if (endIdx <= 0)
					pos = blockLen;
				else
				{
					pos = endIdx;
					state = State::Normal;
				}
				setFormat (start, pos - start, getFmt (Construct::Comment));
				break;
			}
			case State::Tag:
			{
				start = pos;
				while (pos < blockLen)
				{
					const auto ch = block.at (pos++);
					if (ch == ' ' && block.at (pos) != '/')
					{
						state = State::AttrName;
						break;
					}
					else if (ch == '>')
					{
						state = State::Normal;
						break;
					}
				}

				setFormat (start, pos - start, getFmt (Construct::Tag));
				break;
			}
			case State::AttrName:
			{
				start = pos;
				while (pos < blockLen)
				{
					const auto ch = block.at (pos++);
					if (ch == '=')
					{
						state = State::AttrValue;
						break;
					}
				}
				setFormat (start, pos - start, getFmt (Construct::AttrName));
				break;
			}
			case State::AttrValue:
			{
				start = pos;
				QChar quoteChar;
				while (pos < blockLen)
				{
					const auto ch = block.at (pos++);
					if (quoteChar == ch)
					{
						state = State::Tag;
						break;
					}
					else if (quoteChar.isNull ())
						quoteChar = ch;
				}
				setFormat (start, pos - start, getFmt (Construct::AttrValue));
				break;
			}
			}
		}

		setCurrentBlockState (static_cast<int> (state));
	}
}
}
