//          Copyright Brian Schott (Sir Alaran) 2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

module types;

import std.stdio;
import std.array;
import std.range;
import std.algorithm;
import std.typecons;

/**
 * Returns: s with any quote characters backslash-escaped
 */
string escapeJSON(string s)
{
	return s.replace("\"", "\\\"");
}

unittest { assert(escapeJSON("abc\"def") == "abc\\\"def"); }

/**
 * Writes a string in JSON fromat to the given file
 * Params:
 *     f = the file to write to
 *     name = the name of the json attribute
 *     value = the value of the json attribute
 *     indent = the indent level
 */
void writeJSONString(File f, const string name, const string value, uint indent = 0)
{
	f.write(std.array.replicate("  ", indent), "\"", name, "\" : \"", escapeJSON(value), "\"");
}

/**
 * Writes a string array in JSON format to the given file
 *     f = the file to write to
 *     name = the name of the json attribute
 *     values = the strings that should be written
 *     indent = the indent level
 */
void writeJSONString(File f, const string name, const string[] values, uint indent = 0)
{
	f.writeln(std.array.replicate("  ", indent), "\"", name, "\" : [");
	foreach(i, v; values)
	{
		f.write(std.array.replicate("  ", indent + 1), "\"", escapeJSON(v), "\"");
		if (i + 1 < values.length)
			f.writeln(",");
		else
			f.writeln();
	}
	f.write(std.array.replicate("  ", indent), "]");
}

/**
 * Attributes common to everything interesting
 */
abstract class Base
{
public:

	/// Sybol name
	string name;

	/// Line number of declaration
	uint line;

	/// Attributes such as "ref", "const", etc.
	string[] attributes;

	/// Protection level such as "public", protected, etc.
	string protection;

	/// See_also: writeJSONString
	void writeJSONTo(File f, uint indent) const
	{
		f.writeln(std.array.replicate("  ", indent + 1), "{");
		printMembers(f, indent + 2);
		f.write("\n", std.array.replicate("  ", indent + 1), "}");
	}

protected:

	void printMembers(File f, uint indent = 0) const
	{
		writeJSONString(f, "name", name, indent);
		f.writeln(",");
		f.write(std.array.replicate("  ", indent), "\"line\" : ", line);
		f.writeln(",");
		writeJSONString(f, "protection", protection, indent);
		f.writeln(",");
		writeJSONString(f, "attributes", attributes, indent);
	}
}

/**
 * Varible declaration
 */
class Variable : Base
{
public:

	/// Variable type
	string type;

protected:

	override void printMembers(File f, uint indent = 0) const
	{
		super.printMembers(f, indent);
		f.writeln(",");
		writeJSONString(f, "type", type, indent);
	}
}

/**
 * Base class for any type that can be a template
 */
abstract class Templateable : Base
{
public:

	/// Template constraint, which may be null
	string constraint;

	/// Template parameters, may be empty
	string[] templateParameters;

protected:

	override void printMembers(File f, uint indent = 0) const
	{
		super.printMembers(f, indent);
		f.writeln(",");
		writeJSONString(f, "constraint", constraint, indent);
		f.writeln(",");
		writeJSONString(f, "templateParameters", templateParameters, indent);
	}
}

/**
 * Stuff common to struct, interface, and class.
 */
class Struct : Templateable
{
public:

	/// List of methods
	Function[] functions;

	/// List of member variables; may be empty
	Variable[] variables;

	/// Source code character position of the beginning of the struct body
	size_t bodyStart;

	/// Source code character position of the end of the struct body
	size_t bodyEnd;

	string getMemberType(string name) const
	{
		foreach (f; functions)
			if (f.name == name)
				return f.returnType;
		foreach (v; variables)
			if (v.name == name)
				return v.type;
		return null;
	}

protected:

	override void printMembers(File f, uint indent = 0) const
	{
		super.printMembers(f, indent);
		f.writeln(",\n", std.array.replicate("  ", indent), "\"functions\" : [");
		foreach(i, fun; functions)
		{
			fun.writeJSONTo(f, indent);
			if (i + 1 < functions.length)
				f.writeln(",");
			else
				f.writeln();
		}
		f.writeln(std.array.replicate("  ", indent), "],\n", std.array.replicate("  ", indent), "\"variables\" : [");
		foreach(i, var; variables)
		{
			var.writeJSONTo(f, indent);
			if (i + 1 < variables.length)
				f.writeln(",");
			else
				f.writeln();
		}
		f.write(std.array.replicate("  ", indent), "]");
	}
}

/**
 * Functions and delegates
 */
class Function : Templateable
{
public:

	/// Function return type
	string returnType;

	/// Parameter list; may be empty
	Variable[] parameters;

protected:
	override void printMembers(File f, uint indent) const
	{
		super.printMembers(f, indent);
		f.write(",\n");
		f.writeln(std.array.replicate("  ", indent), "\"parameters\" : [");
		foreach(i, params; parameters)
		{
			params.writeJSONTo(f, indent);
			if (i + 1 < parameters.length)
				f.writeln(",");
			else
				f.writeln();
		}

		f.write(std.array.replicate("  ", indent), "],\n");
		writeJSONString(f, "returnType", returnType, indent);
	}
}

/**
 * class and interface
 */
class Inherits : Struct
{
public:

	/**
	 * List of interfaces and classes that this inherits or implements; may
	 * be empty
	 */
	string[] baseClasses;

protected:

	override void printMembers(File f, uint indent = 0) const
	{
		super.printMembers(f, indent);
		f.writeln(",");
		writeJSONString(f, "baseClasses", baseClasses, indent);
	}
}

/**
 * enum member
 */
struct EnumMember
{
	uint line;
	string name;
}

/**
 * enum
 */
class Enum : Base
{
public:

	/// Base type for this enum
	string type;

	/// Enum members; may be empty
	EnumMember[] members;

protected:

	override void printMembers(File f, uint indent = 0) const
	{
		super.printMembers(f, indent);
		f.writeln(",");
		writeJSONString(f, "type", type, indent);
		f.writeln(",\n", std.array.replicate("  ", indent), "\"members\" : [");
		foreach(i, member; members)
		{
			f.writeln(std.array.replicate("  ", indent + 1), "{");
			writeJSONString(f, "name", member.name, indent + 2);
			f.writeln(",");
			f.writeln(std.array.replicate("  ", indent + 2), "\"line\" : ", member.line);
			f.write(std.array.replicate("  ", indent + 1), "}");
			if (i + 1 < members.length)
				f.writeln(",");
			else
				f.writeln();
		}
		f.write(std.array.replicate("  ", indent), "]");
	}
}

/**
 * Module is a container class for the other classes
 */
class Module
{
public:

	/// Module name. Will be blank if there is no module statement
	string name;

	/// List of interfaces declared in this module
	Inherits[] interfaces;

	/// List of classes declared in this module
	Inherits[] classes;

	/// List of functions declared in this module
	Function[] functions;

	/// List of unions declared in this module
	Struct[] unions;

	/// List of variables declared in this module
	Variable[] variables;

	/// List of structs declared in this module
	Struct[] structs;

	/// List of enums declared in this module
	Enum[] enums;

	/// List of other modules that are imported by this one
	string[] imports;

	/// Combine this module with another one
	void merge(Module other)
	{
		interfaces.insertInPlace(interfaces.length, other.interfaces);
		classes.insertInPlace(classes.length, other.classes);
		functions.insertInPlace(functions.length, other.functions);
		unions.insertInPlace(unions.length, other.unions);
		variables.insertInPlace(variables.length, other.variables);
		structs.insertInPlace(structs.length, other.structs);
		enums.insertInPlace(enums.length, other.enums);
		imports.insertInPlace(imports.length, other.imports);
	}

	/**
	 * Prints a JSON representation of this module to the given file
	 */
	void writeJSONTo(File f) const
	{
		uint indent = 0;
		f.writeln("{");
		writeJSONString(f, "name", name, indent + 1);
		f.writeln(",");
		writeJSONString(f, "imports", imports, indent + 1);
		f.writeln(",\n  \"interfaces\" : [");
		foreach(i, inter; interfaces)
		{
			inter.writeJSONTo(f, indent + 1);
			if (i + 1 < interfaces.length)
				f.writeln(",");
			else
				f.writeln();
		}
		f.writeln("  ],\n  \"classes\" : [");
		foreach(i, cl; classes)
		{
			cl.writeJSONTo(f, indent + 1);
			if (i + 1 < classes.length)
				f.writeln(",");
			else
				f.writeln();
		}
		f.writeln("  ],\n  \"structs\" : [");
		foreach(i, str; structs)
		{
			str.writeJSONTo(f, indent + 1);
			if (i + 1 < structs.length)
				f.writeln(",");
			else
				f.writeln();
		}
		f.writeln("  ],\n  \"structs\" : [");
		foreach(i, un; unions)
		{
			un.writeJSONTo(f, indent + 1);
			if (i + 1 < unions.length)
				f.writeln(",");
			else
				f.writeln();
		}
		f.writeln("  ],\n  \"functions\" : [");
		foreach(i, fun; functions)
		{
			fun.writeJSONTo(f, indent + 1);
			if (i + 1 < functions.length)
				f.writeln(",");
			else
				f.writeln();
		}
		f.writeln("  ],\n  \"variables\" : [");
		foreach(i, var; variables)
		{
			var.writeJSONTo(f, indent + 1);
			if (i + 1 < variables.length)
				f.writeln(",");
			else
				f.writeln();
		}
		f.writeln("  ],\n  \"enums\" : [");
		foreach(i, en; enums)
		{
			en.writeJSONTo(f, indent + 1);
			if (i + 1 < enums.length)
				f.writeln(",");
			else
				f.writeln();
		}
		f.writeln("  ]\n}");
	}
}

immutable(string[][string]) typeProperties;
immutable(string[]) floatProperties;
immutable(string[]) integralProperties;
immutable(string[]) commonProperties;
immutable(string[]) arrayProperties;

static this()
{
	floatProperties = ["alignof", "dig", "epsilon", "im", "infinity", "init",
		"mangleof", "mant_dig", "max", "max_10_exp", ".max_­exp", "min_10_­exp",
		"min_­exp", "min_nor­mal", "nan",	"re", "sizeof"
	];

	integralProperties = ["alignof", "init", "mangleof", "max",
		"min", "sizeof", "stringof"
	];

	commonProperties = [
		"alignof",
		"init",
		"mangleof",
		"stringof"
	];

	arrayProperties = [
		"alignof",
		"init",
		"length",
		"mangleof",
		"ptr",
		"stringof",
	];

	typeProperties = [
		"bool" : commonProperties,
		"byte" : integralProperties,
		"ubyte" : integralProperties,
		"short" : integralProperties,
		"ushort" : integralProperties,
		"int" : integralProperties,
		"uint" : integralProperties,
		"long" : integralProperties,
		"ulong" : integralProperties,
		"cent" : integralProperties,
		"ucent" : integralProperties,
		"float" : floatProperties,
		"dou­ble" : floatProperties,
		"real" : floatProperties,
		"ifloat" : floatProperties,
		"idou­ble" : floatProperties,
		"ireal" : floatProperties,
		"cfloat" : floatProperties,
		"cdou­ble" : floatProperties,
		"creal" : floatProperties,
		"char" : commonProperties,
		"wchar" : commonProperties,
		"dchar" : commonProperties,
		"ptrdiff_t" : integralProperties,
		"size_t" : integralProperties,
		"string" : arrayProperties,
		"wstring" : arrayProperties,
		"dstring" : arrayProperties
	];
}

class CompletionContext
{
public:

	this(Module mod)
	{
		this.currentModule = mod;
	}

	Tuple!(string, string)[string] getMembersOfType(string name)
	{
		foreach (m; chain(modules, [currentModule]))
		{
			foreach (s; chain(m.structs, m.interfaces, m.classes, m.unions))
			{
				if (s.name != name)
					continue;
				Tuple!(string, string)[string] typeMap;
				foreach(var; s.variables)
					typeMap[var.name] = Tuple!(string, string)(var.type, "?1");
				foreach(fun; s.functions)
					typeMap[fun.name] = Tuple!(string, string)(fun.returnType, "?2");
				return typeMap;
			}
			foreach (Enum e; m.enums)
			{
				if (e.name != name)
					continue;
				Tuple!(string, string)[string] typeMap;
				foreach (member; e.members)
					typeMap[member.name] = Tuple!(string, string)(e.type, "?1");
				return typeMap;
			}
		}
		return null;
	}

	Struct[] getStructsContaining(size_t cursorPosition)
	{
		auto app = appender!(Struct[])();
		foreach(s; chain(currentModule.structs, currentModule.interfaces,
			currentModule.classes, currentModule.unions))
		{
			if (s.bodyStart <= cursorPosition && s.bodyEnd >= cursorPosition)
				app.put(s);
		}
		return app.data();
	}

	void addModule(Module mod)
	{
		modules ~= mod;
	}

	Module currentModule;
	Module[] modules;
}