#include "syntax_highlighter.h"
#include "qt_utils.h"

Highlighter::Highlighter(QTextDocument* parent) : QSyntaxHighlighter(parent)
{
}

void Highlighter::addRule(const QString& pattern, const QBrush& brush)
{
	HighlightingRule rule;
	rule.pattern = QRegularExpression(pattern);
	rule.format.setForeground(brush);
	highlightingRules.append(rule);
}

void Highlighter::highlightBlock(const QString& text)
{
	for (const HighlightingRule& rule : highlightingRules)
	{
		QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
		while (matchIterator.hasNext())
		{
			const QRegularExpressionMatch match = matchIterator.next();
			setFormat(match.capturedStart(), match.capturedLength(), rule.format);
		}
	}
	setCurrentBlockState(0);

	if (commentStartExpression.pattern().isEmpty() || commentEndExpression.pattern().isEmpty())
		return;

	int startIndex = 0;
	if (previousBlockState() != 1)
		startIndex = text.indexOf(commentStartExpression);

	while (startIndex >= 0)
	{
		const QRegularExpressionMatch match = commentEndExpression.match(text, startIndex);
		const int endIndex = match.capturedStart();
		int commentLength;

		if (endIndex == -1)
		{
			setCurrentBlockState(1);
			commentLength = text.length() - startIndex;
		}
		else
		{
			commentLength = endIndex - startIndex + match.capturedLength();
		}
		setFormat(startIndex, commentLength, multiLineCommentFormat);
		startIndex = text.indexOf(commentStartExpression, startIndex + commentLength);
	}
}

LogHighlighter::LogHighlighter(QTextDocument* parent) : Highlighter(parent)
{
	const QColor color = gui::utils::get_foreground_color();

	//addRule("^[^·].*$", gui::utils::get_label_color("log_level_always", Qt::darkCyan, Qt::cyan)); // unused for now
	addRule("^·F.*$", gui::utils::get_label_color("log_level_fatal", Qt::darkMagenta, Qt::magenta));
	addRule("^·E.*$", gui::utils::get_label_color("log_level_error", Qt::red, Qt::red));
	addRule("^·U.*$", gui::utils::get_label_color("log_level_todo", Qt::darkYellow, Qt::darkYellow));
	addRule("^·S.*$", gui::utils::get_label_color("log_level_success", Qt::darkGreen, Qt::green));
	addRule("^·W.*$", gui::utils::get_label_color("log_level_warning", Qt::darkYellow, Qt::darkYellow));
	addRule("^·!.*$", gui::utils::get_label_color("log_level_notice", color, color));
	addRule("^·T.*$", gui::utils::get_label_color("log_level_trace", color, color));
}

AsmHighlighter::AsmHighlighter(QTextDocument* parent) : Highlighter(parent)
{
	addRule("^\\b[A-Z0-9]+\\b",       Qt::darkBlue);    // Instructions
	addRule("-?R\\d[^,;\\s]*",        Qt::darkRed);     // -R0.*
	addRule("-?H\\d[^,;\\s]*",        Qt::red);         // -H1.*
	addRule("-?v\\[\\d\\]*[^,;\\s]*", Qt::darkCyan);    // -v[xyz].*
	addRule("-?o\\[\\d\\]*[^,;\\s]*", Qt::darkMagenta); // -o[xyz].*
	addRule("-?c\\[\\d\\]*[^,;\\s]*", Qt::darkYellow);  // -c[xyz].*
	addRule("#[^\\n]*",               Qt::darkGreen);   // Single line comment
}

GlslHighlighter::GlslHighlighter(QTextDocument* parent) : Highlighter(parent)
{
	const QStringList keywordPatterns = QStringList()
		// Selection-Iteration-Jump Statements:
		<< "if"     << "else"  << "switch"   << "case"    << "default"
		<< "for"    << "while" << "do"       << "foreach" //?
		<< "return" << "break" << "continue" << "discard"
		// Misc:
		<< "void"    << "char"     << "short"     << "long"      << "template"
		<< "class"   << "struct"   << "union"     << "enum"
		<< "static"  << "virtual"  << "inline"    << "explicit"
		<< "public"  << "private"  << "protected" << "namespace"
		<< "typedef" << "typename" << "signed"    << "unsigned"
		<< "friend"  << "operator" << "signals"   << "slots"
		// Qualifiers:
		<< "in"     << "packed" << "precision" << "const"     << "smooth"        << "sample"
		<< "out"    << "shared" << "highp"     << "invariant" << "noperspective" << "centroid"
		<< "inout"  << "std140" << "mediump"   << "uniform"   << "flat"          << "patch"
		<< "buffer" << "std430" << "lowp"
		<< "image"
		// Removed Qualifiers?
		<< "attribute" << "varying"
		// Memory Qualifiers
		<< "coherent" << "volatile" << "restrict" << "readonly" << "writeonly"
		// Layout Qualifiers:
		//<< "subroutine" << "layout"     << "xfb_buffer" << "textureGrad"  << "texture" << "dFdx" << "dFdy"
		//<< "location"   << "component"  << "binding"    << "offset"
		//<< "xfb_offset" << "xfb_stride" << "vertices"   << "max_vertices"
		// Scalars and Vectors:
		<< "bool"  << "int"   << "uint"  << "float" << "double"
		<< "bvec2" << "ivec2" << "uvec2" << "vec2"  << "dvec2"
		<< "bvec3" << "ivec3" << "uvec3" << "vec3"  << "dvec3"
		<< "bvec4" << "ivec4" << "uvec4" << "vec4"  << "dvec4"
		// Matrices:
		<< "mat2" << "mat2x3" << "mat2x4"
		<< "mat3" << "mat3x2" << "mat3x4"
		<< "mat4" << "mat4x2" << "mat4x3"
		// Sampler Types:
		<< "sampler1D"        << "isampler1D"        << "usampler1D"
		<< "sampler2D"        << "isampler2D"        << "usampler2D"
		<< "sampler3D"        << "isampler3D"        << "usampler3D"
		<< "samplerCube"      << "isamplerCube"      << "usamplerCube"
		<< "sampler2DRect"    << "isampler2DRect"    << "usampler2DRect"
		<< "sampler1DArray"   << "isampler1DArray"   << "usampler1DArray"
		<< "sampler2DArray"   << "isampler2DArray"   << "usampler2DArray"
		<< "samplerCubeArray" << "isamplerCubeArray" << "usamplerCubeArray"
		<< "samplerBuffer"    << "isamplerBuffer"    << "usamplerBuffer"
		<< "sampler2DMS"      << "isampler2DMS"      << "usampler2DMS"
		<< "sampler2DMSArray" << "isampler2DMSArray" << "usampler2DMSArray"
		// Shadow Samplers:
		<< "sampler1DShadow"
		<< "sampler2DShadow"
		<< "samplerCubeShadow"
		<< "sampler2DRectShadow"
		<< "sampler1DArrayShadow"
		<< "sampler2DArrayShadow"
		<< "samplerCubeArrayShadow"
		// Image Types:
		<< "image1D"        << "iimage1D"        << "uimage1D"
		<< "image2D"        << "iimage2D"        << "uimage2D"
		<< "image3D"        << "iimage3D"        << "uimage3D"
		<< "imageCube"      << "iimageCube"      << "uimageCube"
		<< "image2DRect"    << "iimage2DRect"    << "uimage2DRect"
		<< "image1DArray"   << "iimage1DArray"   << "uimage1DArray"
		<< "image2DArray"   << "iimage2DArray"   << "uimage2DArray"
		<< "imageCubeArray" << "iimageCubeArray" << "uimageCubeArray"
		<< "imageBuffer"    << "iimageBuffer"    << "uimageBuffer"
		<< "image2DMS"      << "iimage2DMS"      << "uimage2DMS"
		<< "image2DMSArray" << "iimage2DMSArray" << "uimage2DMSArray"
		// Image Formats:
		// Floating-point:  // Signed integer:
		<< "rgba32f"        << "rgba32i"
		<< "rgba16f"        << "rgba16i"
		<< "rg32f"          << "rgba8i"
		<< "rg16f"          << "rg32i"
		<< "r11f_g11f_b10f" << "rg16i"
		<< "r32f"           << "rg8i"
		<< "r16f"           << "r32i"
		<< "rgba16"         << "r16i"
		<< "rgb10_a2"       << "r8i"
		<< "rgba8"          << "r8ui"
		<< "rg16"           // Unsigned integer:
		<< "rg8"            << "rgba32ui"
		<< "r16"            << "rgba16ui"
		<< "r8"             << "rgb10_a2ui"
		<< "rgba16_snorm"   << "rgba8ui"
		<< "rgba8_snorm"    << "rg32ui"
		<< "rg16_snorm"     << "rg16ui"
		<< "rg8_snorm"      << "rg8ui"
		<< "r16_snorm"      << "r32ui"
		<< "r8_snorm"       << "r16ui";

	for (const QString& pattern : keywordPatterns)
		addRule("\\b" + pattern + "\\b",   Qt::darkBlue);    // normal words like: soka, nani, or gomen

	addRule("\\bGL_(?:[A-Z]|_)+\\b",       Qt::darkMagenta); // constants like: GL_OMAE_WA_MOU_SHINDEIRU
	addRule("\\bgl_(?:[A-Z]|[a-z]|_)+\\b", Qt::darkCyan);    // reserved types like: gl_exploooooosion
	addRule("\\B#[^\\s]+\\b",              Qt::darkGray);    // preprocessor instructions like: #waifu megumin
	addRule("//[^\\n]*",                   Qt::darkGreen);   // Single line comment

	// Multi line comment
	multiLineCommentFormat.setForeground(Qt::darkGreen);
	commentStartExpression = QRegularExpression("/\\*");
	commentEndExpression = QRegularExpression("\\*/");
}

AnsiHighlighter::AnsiHighlighter(QPlainTextEdit* text_edit)
	: Highlighter(text_edit ? text_edit->document() : nullptr)
{
	update_colors(text_edit);
}

void AnsiHighlighter::update_colors(QPlainTextEdit* text_edit)
{
	m_foreground_color = gui::utils::get_foreground_color(text_edit);
	m_background_color = gui::utils::get_background_color(text_edit);

	m_foreground_colors[30] = gui::utils::adjust_color_for_background(Qt::black, m_background_color);
	m_foreground_colors[31] = gui::utils::adjust_color_for_background(Qt::red, m_background_color);
	m_foreground_colors[32] = gui::utils::adjust_color_for_background(Qt::darkGreen, m_background_color);
	m_foreground_colors[33] = gui::utils::adjust_color_for_background(Qt::darkYellow, m_background_color);
	m_foreground_colors[34] = gui::utils::adjust_color_for_background(Qt::darkBlue, m_background_color);
	m_foreground_colors[35] = gui::utils::adjust_color_for_background(Qt::darkMagenta, m_background_color);
	m_foreground_colors[36] = gui::utils::adjust_color_for_background(Qt::darkCyan, m_background_color);
	m_foreground_colors[37] = gui::utils::adjust_color_for_background(Qt::lightGray, m_background_color);
	m_foreground_colors[39] = m_foreground_color;
	m_foreground_colors[90] = gui::utils::adjust_color_for_background(Qt::darkGray, m_background_color);
	m_foreground_colors[91] = gui::utils::adjust_color_for_background(Qt::red, m_background_color);
	m_foreground_colors[92] = gui::utils::adjust_color_for_background(Qt::green, m_background_color);
	m_foreground_colors[93] = gui::utils::adjust_color_for_background(Qt::yellow, m_background_color);
	m_foreground_colors[94] = gui::utils::adjust_color_for_background(Qt::blue, m_background_color);
	m_foreground_colors[95] = gui::utils::adjust_color_for_background(Qt::magenta, m_background_color);
	m_foreground_colors[96] = gui::utils::adjust_color_for_background(Qt::cyan, m_background_color);
	m_foreground_colors[97] = gui::utils::adjust_color_for_background(Qt::white, m_background_color);

	m_escape_format.setForeground(gui::utils::adjust_color_for_background(Qt::darkGray, m_background_color));
	m_escape_format.setFontItalic(true);
}

void AnsiHighlighter::highlightBlock(const QString& text)
{
	QTextCharFormat current_format;
	current_format.setForeground(m_foreground_color);

	int pos = 0;
	auto it = ansi_re.globalMatch(text);
	while (it.hasNext())
	{
		const auto match = it.next();
		const int start = match.capturedStart();
		const int length = match.capturedLength();

		// Apply current format to the chunk before this escape sequence
		if (start > pos)
		{
			setFormat(pos, start - pos, current_format);
		}

		// Highlight the escape sequence itself
		setFormat(start, length, m_escape_format);

		// Parse SGR parameters and update currentFormat
		const QRegularExpressionMatch pm = param_re.match(match.captured());
		if (pm.hasMatch())
		{
			const QString params = pm.captured(1);
			if (params.isEmpty())
			{
				// empty or just \x1b[m = reset
				current_format = QTextCharFormat();
				current_format.setForeground(m_foreground_color);
			}
			else
			{
				const QStringList codes = params.split(';', Qt::SkipEmptyParts);
				for (const QString& c : codes)
				{
					bool ok = false;
					const int code = c.toInt(&ok);
					if (!ok) continue;
					switch (code)
					{
					case 0:
						current_format = QTextCharFormat();
						current_format.setForeground(m_foreground_color);
						break;
					case 1:
						current_format.setFontWeight(QFont::Bold);
						break;
					case 3:
						current_format.setFontItalic(true);
						break;
					case 4:
						current_format.setFontUnderline(true);
						break;
					default:
						// Background and extended colors not yet handled
						if (const auto it = m_foreground_colors.find(code); it != m_foreground_colors.cend())
						{
							current_format.setForeground(it->second);
						}
						break;
					}
				}
			}
		}

		pos = start + length;
	}

	// Apply remaining format
	if (pos < text.length())
	{
		setFormat(pos, text.length() - pos, current_format);
	}
}
