package code;
import sys.FileSystem;
import sys.io.File;
import haxe.io.Path;
import haxe.ds.Either;
import model.ClassDiagram;
import model.OClass;
import model.Obj; //Field
using StringTools;

class HxModuleParser {
	var content : String;
	var cd : ClassDiagram;
	var i : Int;	//content current index
	var c : String; //current char (content[i])
	var cc : Int;	//char code
	var p : String; //previous char
	var n : String; //next char
	var pkg : String;
	var cls : OClass;

	public function new(content: String, cd : ClassDiagram) {
		this.content = content;
		this.cd = cd;
	}

	public function parse() {
		content = prepare();
		var subBlk : Int = 0;
		pkg = "";
		cls = null;
		initChr();
		while (!eof()) {
			if (c == "{") {
				subBlk++; nextChr();
			} else if (c == "}") {
				subBlk--; nextChr();
			} else if (c == " ") {
				nextChr();
			} else {
				if (subBlk == 0) {
					switch (readKeyWord()) {
						case "package": pkg = readIdentifier();
						case "typedef":
							cls = new OClass(readIdentifier(), pkg, true);
							cd.classes.set(cls.name, cls);
						case "interface":
							cls = new OClass(readIdentifier(), pkg, false, true, true);
							cd.classes.set(cls.name, cls);
						case "class": addClass();
						//~ default: nextChr();
					}
				} else if (subBlk == 1) {
					var word : String = readKeyWord();
					switch (word) {
						case "var": addVar();
						case "function": addFunction();
						case "public" | "private" | "static" | "inline": addField(word);
						//~ default: nextChr();
					}
				//~ } else {
					//~ nextChr();
				}
				while (c != ";" && c != "{" && c != "}") nextChr(); //go to end
				if (c == ";") nextChr(); //of instruction
			}
		}
	}

	function addClass() {
		var parent : String = "";
		var interfaces : Array<String> = new Array();
		var name : String = readIdentifier();
		var next : String = readKeyWord();
		if (next == "extends") {
			parent = readIdentifier();
			next = readKeyWord();
		}
		cls = new OClass(name, pkg, parent == "" ? null : Left(parent));
		cd.classes.set(cls.name, cls);
		if (next == "implements") {
			do {
				nextChr(); //skip first space or comma separator","
				cls.interfaces.push(Left(readIdentifier()));
			} while (c == ",");
		}
	}

	function addField(next : String) {
		var mods : Array<String> = new Array();
		while (next == "public" || next == "private" || next == "inline" || next == "static") {
			mods.push(next);
			next = readKeyWord();
		}
		var name : String = null;
		if (next == "var" || next == "function") {
			if (next == "var") {
				name = addVar();
			} else {
				name = addFunction();
			}
			if (mods.indexOf("static") != -1) {
				cls.staticFields.push(name);
			}
			if (mods.indexOf("public") != -1) {
				cls.publicFields.push(name);
			}
		}
	}

	function addFunction() : String {
		var name : String = readIdentifier();
		var m : Method = { name : name, type : "", params : new Array() };
		if (c == " ") nextChr();
		do {
			nextChr();
			var fName : String = readIdentifier();
			var fType : String = readType();
			m.params.push({ name : fName, type : fType });
			var subObj = 0;
			while (subObj != 0 && c != "," && c != ")") { //skip ' = { x : ..., y : ... }'
				if (c == "{") subObj++;
				else if (c == "}") subObj--;
				nextChr();
			}
		} while (c == ",");
		nextChr(); //'('
		m.type = readType();
		cls.methods.push(m);
		return name;
	}

	function addVar() : String {
		var name : String = readIdentifier();
		cls.fields.push({ name : name, type : readType() });
		return name;
	}

	function prepare() : String {
		var str : String = "";
		initChr();
		while (!eof()) {
			if (c == "/" && n == "/") { //eol comment
				nextChr(); nextChr();
				while (!eof() && !(c == "\r" || c == "\n")) nextChr();
				nextChr();
			} else if (c == "/" && n == "*") { //multiline comment
				nextChr(); nextChr();
				while (!eof() && (c != "/" || p != "*")) nextChr();
				nextChr();
			} else if (c == "~" && n == "/") { //regular expression
				nextChr(); nextChr();
				while (!eof() && (c != "/" || p != "\\")) nextChr();
				nextChr();
				str += "regexp";
			} else if (c == "\"") { //double-quoted string
				nextChr();
				while (!eof() && (c != "\"" || p == "\\")) nextChr();
				nextChr();
				str += "str";
			} else if (c == "\'") { //single-quoted string
				nextChr();
				while (!eof() && (c != "\'" || p == "\\")) nextChr();
				nextChr();
				str += "str";
			} else if (c == "\r" || c == "\n" || c == "\t" || c == " ") { //text formatting
				c = " ";
				if (p != " ") {
					str += c;
				}
				nextChr();
			} else {
				str += c;
				nextChr();
			}
		}
		return str;
	}

	function eof() : Bool {
		return i == content.length;
	}

	function initChr() {
		i = 0;
		p = "";
		if (content.length > 1) {
			c = content.charAt(i);
			cc = content.charCodeAt(i);
			n = content.charAt(i + 1);
		}
	}

	function nextChr() {
		if (!eof()) {
			i++;
			p = c;
			c = n;
			cc = content.charCodeAt(i);
			n = content.charAt(i + 1);
		}
	}

	function readIdentifier() : String {
		if (c == " ") nextChr();
		var word : String = "";
		while (!eof() && (cc >= 48 && cc <= 47 || cc >= 65 && cc <= 90 || cc >= 97 && cc <= 122 || c == "_" || c == "?")) {
						/*		[0-9]					[A-Z]					[a-z]			*/
			word += c;
			nextChr();
		}
		return word;
	}

	function readKeyWord() : String {
		if (c == " ") nextChr();
		var word : String = "";
		while (!eof() && (cc >= 97 && cc <= 122)) { //a-z
			word += c;
			nextChr();
		}
		return word;
	}

	function readType() : String { //takes ':'
		var word : String = "";
		if (c == " ") nextChr();
		if (c == ":") {
			nextChr();
			if (c == " ") nextChr();
			var subType : Int = 0;
			while (!eof() && cc >= 48 && cc <= 47 || cc >= 65 && cc <= 90 || cc >= 97 && cc <= 122 || c == "_" || c == "<" || c == ">" || //0-9, A-Z, a-z
					subType != 0 && (c == "," || c == " ")) {
				word += c;
				if (c == "<") subType++;
				else if (c == ">") subType--;
				nextChr();
			}
		}
		return word;
	}
}

class HxParser {
	public static function analyse(src: String) : ClassDiagram {
		var cd : ClassDiagram = new ClassDiagram();
		if (FileSystem.isDirectory(src)) {
			parseDir(src, cd); //recursive
		} else {
			new HxModuleParser(File.getContent(src), cd).parse();
		}
		return cd;
	}

	static function parseDir(src : String, cd : ClassDiagram) {
		for (fd in FileSystem.readDirectory(src)) {
			var fdPath : String = src + "/" + fd;
			if (FileSystem.isDirectory(fdPath)) {
				parseDir(fdPath, cd);
			} else {
				new HxModuleParser(File.getContent(fdPath), cd).parse();
			}
		}
	}
}
