#!/usr/bin/env rdmd

import std.algorithm;
import std.ascii;
import std.file;
import std.regex;
import std.stdio;
import std.string;

struct ConstantCompletion
{
	string[] identifiers;
	string ddoc;
}

struct SingleConstantCompletion
{
	string identifier;
	string ddoc;

	string toString() const
	{
		return "ConstantCompletion(\"" ~ identifier ~ "\", `" ~ ddoc.replaceAll(ctRegex!"`+",
				"` ~ \"$0\" ~ `") ~ "`)";
	}
}

unittest
{
	ConstantCompletion completion;
	completion.ddoc = "'abc``def'";
	assert(completion.toString == q{ConstantCompletion("", `'abc` ~ "``" ~ `def'`)});
}

ConstantCompletion[] parsePragmas(string ddoc)
{
	ConstantCompletion[] completions;

	bool foundTerminator;

	ConstantCompletion current;
	bool inInlineCode;
	bool seekingToFirst = true;
	string indent;

	void addCurrent()
	{
		if (seekingToFirst)
		{
			current = ConstantCompletion.init;
			return;
		}
		// ret still has `$(DD content)` around it, strip that
		if (current.ddoc.startsWith("$(DD"))
		{
			current.ddoc = current.ddoc["$(DD".length .. $].strip;
			if (current.ddoc.endsWith(")"))
				current.ddoc = current.ddoc[0 .. $ - 1].stripRight;
		}
		completions ~= current;
		current = ConstantCompletion.init;
	}

	foreach (line; ddoc.lineSplitter!(KeepTerminator.yes))
	{
		auto strippedLine = line.stripLeft;
		if (strippedLine.startsWith("$(SPEC_SUBNAV_PREV_NEXT"))
		{
			addCurrent();
			// end of macros
			foundTerminator = true;
			break;
		}
		else if (strippedLine.startsWith("$(DT $(LNAME2 "))
		{
			addCurrent();
			seekingToFirst = false;
			indent = line[0 .. $ - strippedLine.length];
			string identifierLine = strippedLine.stripRight; // fully stripped
			auto closing = identifierLine.indexOfAny("),");
			if (closing == -1)
				closing = identifierLine.length;
			current.identifiers = [identifierLine["$(DT $(LNAME2".length .. closing].strip];
		}
		else if (!seekingToFirst)
		{
			if (line.startsWith("---")) // code blocks aren't indented
				inInlineCode = !inInlineCode;

			if (inInlineCode)
				current.ddoc ~= line;
			else
			{
				if (line.startsWith(indent)) // strip indentation equal to DT (section header)
					current.ddoc ~= line[indent.length .. $];
				else
					current.ddoc ~= line;
			}
		}
	}
	if (!foundTerminator)
		throw new Exception("Could not find '$(SPEC_SUBNAV_PREV_NEXT' line in pragma.dd, format of the file has changed and code needs to be adjusted.");

	return completions;
}

ConstantCompletion[] parseTraits(string ddoc)
{
	ConstantCompletion[] completions;

	bool foundTerminator;

	ConstantCompletion current;
	bool inInlineCode;
	bool seekingToFirst = true;
	string indent;

	void addCurrent()
	{
		current.ddoc = current.ddoc.strip;
		if (seekingToFirst || current == ConstantCompletion.init)
		{
			current = ConstantCompletion.init;
			return;
		}
		completions ~= current;
		current = ConstantCompletion.init;
	}

	foreach (line; ddoc.lineSplitter!(KeepTerminator.yes))
	{
		if (line.stripLeft.startsWith("$(SPEC_SUBNAV_PREV_NEXT"))
		{
			addCurrent();
			foundTerminator = true;
			break;
		}
		else if (line.startsWith("$(H2 $(LNAME2"))
		{
			addCurrent();
			seekingToFirst = true;
		}
		else if (line.canFind("$(GNAME "))
		{
			addCurrent();
			ptrdiff_t i = line.indexOf("$(GNAME ");
			while (i != -1)
			{
				auto closing = line.indexOfAny("),", i);
				current.identifiers ~= line[i + "$(GNAME ".length .. closing].strip;
				i = line.indexOf("$(GNAME ", closing);
			}
			seekingToFirst = false;

			if (current.identifiers.length == 1 && current.identifiers[0][0].isUpper)
				seekingToFirst = true; // not considering capitalized identifiers traits (TraitsKeyword, TraitsExpression, etc.)
		}
		else if (!seekingToFirst)
		{
			if (line.startsWith("---"))
				inInlineCode = !inInlineCode;
			if (inInlineCode)
				current.ddoc ~= line;
			else
			{
				if (!current.ddoc.length)
					indent = line[0 .. $ - line.stripLeft.length];
				if (line.startsWith(indent))
					current.ddoc ~= line[indent.length .. $];
				else
					current.ddoc ~= line;
			}
		}
	}
	if (!foundTerminator)
		throw new Exception("Could not find '$(SPEC_SUBNAV_PREV_NEXT' line in traits.dd, format of the file has changed and code needs to be adjusted.");

	return completions;
}

void main()
{
	immutable pragmaDDoc = readText("pragma.dd");
	immutable traitsDDoc = readText("traits.dd");

	auto pragmas = parsePragmas(pragmaDDoc);
	auto traits = parseTraits(traitsDDoc);

	string part1 = `//
//
// this file is auto generated by constants-gen/generator.d, do not edit manually.
//
//

module dcd.common.constants2;

import dcd.common.constants : ConstantCompletion;

/**
 * Pragma arguments
 */
immutable ConstantCompletion[] pragmas = [
	// generated from pragma.dd`;
	string part2 = `];

/**
 * Traits arguments
 */
immutable ConstantCompletion[] traits = [
	// generated from traits.dd`;

	string part3 = "];";

	auto file = File("../common/src/dcd/common/constants2.d", "w");
	file.writeln(part1);
	foreach (pragma_; pragmas.sorted)
		file.writeln('\t', pragma_, ",");
	file.writeln(part2);
	foreach (trait; traits.sorted)
		file.writeln('\t', trait, ",");
	file.writeln(part3);
}

auto sorted(T)(T range)
{
	return range.map!(a => a.identifiers.map!(identifier => SingleConstantCompletion(identifier,
			a.ddoc))).join.sort!"a.identifier < b.identifier";
}
