// SDLang-D
// Written in the D programming language.

module sdlang.util;

import std.algorithm;
import std.array;
import std.conv;
import std.datetime;
import std.range;
import std.stdio;
import std.string;

import sdlang.exception;
import sdlang.token;

enum sdlangVersion = "0.9.1";

alias immutable(ubyte)[] ByteString;

auto startsWith(T)(string haystack, T needle)
	if( is(T:ByteString) || is(T:string) )
{
	return std.algorithm.startsWith( cast(ByteString)haystack, cast(ByteString)needle );
}

struct Location
{
	string file; /// Filename (including path)
	int line; /// Zero-indexed
	int col;  /// Zero-indexed, Tab counts as 1
	size_t index; /// Index into the source

	this(int line, int col, int index)
	{
		this.line  = line;
		this.col   = col;
		this.index = index;
	}

	this(string file, int line, int col, int index)
	{
		this.file  = file;
		this.line  = line;
		this.col   = col;
		this.index = index;
	}

	/// Convert to string. Optionally takes output range as a sink.
	string toString()
	{
		Appender!string sink;
		this.toString(sink);
		return sink.data;
	}

	///ditto
	void toString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char))
	{
		sink.put(file);
		sink.put("(");
		sink.put(to!string(line+1));
		sink.put(":");
		sink.put(to!string(col+1));
		sink.put(")");
	}
}

struct FullName
{
	string namespace;
	string name;

	/// Convert to string. Optionally takes output range as a sink.
	string toString()
	{
		if(namespace == "")
			return name;

		Appender!string sink;
		this.toString(sink);
		return sink.data;
	}

	///ditto
	void toString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char))
	{
		if(namespace != "")
		{
			sink.put(namespace);
			sink.put(":");
		}

		sink.put(name);
	}

	///
	static string combine(string namespace, string name)
	{
		return FullName(namespace, name).toString();
	}
	///
	@("FullName.combine example")
	unittest
	{
		assert(FullName.combine("", "name") == "name");
		assert(FullName.combine("*", "name") == "*:name");
		assert(FullName.combine("namespace", "name") == "namespace:name");
	}

	///
	static FullName parse(string fullName)
	{
		FullName result;
		
		auto parts = fullName.findSplit(":");
		if(parts[1] == "") // No colon
		{
			result.namespace = "";
			result.name      = parts[0];
		}
		else
		{
			result.namespace = parts[0];
			result.name      = parts[2];
		}

		return result;
	}
	///
	@("FullName.parse example")
	unittest
	{
		assert(FullName.parse("name") == FullName("", "name"));
		assert(FullName.parse("*:name") == FullName("*", "name"));
		assert(FullName.parse("namespace:name") == FullName("namespace", "name"));
	}

	/// Throws with appropriate message if this.name is "*".
	/// Wildcards are only supported for namespaces, not names.
	void ensureNoWildcardName(string extaMsg = null)
	{
		if(name == "*")
			throw new ArgumentException(`Wildcards ("*") only allowed for namespaces, not names. `~extaMsg);
	}
}
struct Foo { string foo; }

void removeIndex(E)(ref E[] arr, ptrdiff_t index)
{
	arr = arr[0..index] ~ arr[index+1..$];
}

void trace(string file=__FILE__, size_t line=__LINE__, TArgs...)(TArgs args)
{
	version(sdlangTrace)
	{
		writeln(file, "(", line, "): ", args);
		stdout.flush();
	}
}

string toString(TypeInfo ti)
{
	if     (ti == typeid( bool         )) return "bool";
	else if(ti == typeid( string       )) return "string";
	else if(ti == typeid( dchar        )) return "dchar";
	else if(ti == typeid( int          )) return "int";
	else if(ti == typeid( long         )) return "long";
	else if(ti == typeid( float        )) return "float";
	else if(ti == typeid( double       )) return "double";
	else if(ti == typeid( real         )) return "real";
	else if(ti == typeid( Date         )) return "Date";
	else if(ti == typeid( DateTimeFrac )) return "DateTimeFrac";
	else if(ti == typeid( DateTimeFracUnknownZone )) return "DateTimeFracUnknownZone";
	else if(ti == typeid( SysTime      )) return "SysTime";
	else if(ti == typeid( Duration     )) return "Duration";
	else if(ti == typeid( ubyte[]      )) return "ubyte[]";
	else if(ti == typeid( typeof(null) )) return "null";

	return "{unknown}";
}

enum BOM {
	UTF8,           /// UTF-8
	UTF16LE,        /// UTF-16 (little-endian)
	UTF16BE,        /// UTF-16 (big-endian)
	UTF32LE,        /// UTF-32 (little-endian)
	UTF32BE,        /// UTF-32 (big-endian)
}

enum NBOM = __traits(allMembers, BOM).length;
immutable ubyte[][NBOM] ByteOrderMarks =
[
	[0xEF, 0xBB, 0xBF],         //UTF8
	[0xFF, 0xFE],               //UTF16LE
	[0xFE, 0xFF],               //UTF16BE
	[0xFF, 0xFE, 0x00, 0x00],   //UTF32LE
	[0x00, 0x00, 0xFE, 0xFF]    //UTF32BE
];
