package code;
import haxe.ds.StringMap;
import model.Association;
import model.Method;
import model.Diagram;
import model.Field;
import model.Set;

@:expose
class HaxeCode {
	public static function generate(diagram : Diagram) : StringMap<String> {
		var files : StringMap<String> = new StringMap();
		for (set in diagram) {
			if (!Std.isOfType(set, Association) || cast(set, Association).isClass()) {
				files.set(set.className + ".hx", generateClass(set));
			}
		}
		return files;
	}

	public static function generateClass(set : Set) : String {
		var content : String = "";
		if (set.isGeneric()) content += "@:generic\n";
		if (set.isInterface) {
			content += "interface " + set.className + (set.isGeneric() ? "<T>" : "") + " {\n";
		} else {
			content += (set.isAbstract ? "abstract " : "") + "class " + set.className + (set.isGeneric() ? "<T> " : " ") +
				(set.superset != null ? "extends " + set.superset.className + (set.superset.isGeneric() ? "<T> " : " ") : "") + "{\n";
		}
		content += generateFields(set);
		content += generateConstructor(set);
		content += generateMethods(set);
		content += "}\n";
		return content;
	}

	static function generateFields(set : Set) : String {
		var content : String = "";
		for (fields in [set.fields, set.navFields]) {
			for (field in fields) {
				content += "\t" + (field.visibility == Field.ACCESS_PRIVATE || field.visibility == Field.ACCESS_PROTECTED ? "" : "public ") +
					(field.isStatic ? "static " : "") + "var " + field.className + ": " + getType(field.classType) + ";\n";
			}
		}
		return content;
	}

	@SuppressWarnings("checkstyle:CyclomaticComplexity")
	static function generateMethods(set : Set) : String {
		var content : String = "";
		for (methods in [set.accessors, set.methods]) {
			for (method in methods) {
				content += "\n\t" + (method.isStatic && method.isGeneric() ? "@:generic " : "") +
					(method.visibility == Field.ACCESS_PRIVATE || method.visibility == Field.ACCESS_PROTECTED ? "" : "public ") +
					(method.isStatic ? "static " : "") + (Code.overrides(set, method) ? "override " : "") +
					"function " + method.className + (method.isStatic && method.isGeneric() ? "<T>" : "") +
					"(" + Lambda.map(method.params, p -> p.className + ": " + getType(p.classType)).join(", ") + ")" +
					(method.classType == "" ? "" : ": " + getType(method.classType)) + " {\n";
				if (method.className.substr(0, 3) == "get" || method.className.substr(0, 3) == "set") {
					var field : Field = set.getFieldCode(Code.extractFieldName(method.className));
					if (field != null) {
						if (method.className.substr(0, 3) == "set") {
							content += "\t\tthis." + field.className + " = " + field.className + ";\n";
						}
						content += "\t\treturn this." + field.className + (field.classType.substr(0, 5) == "Array" ? ".copy()" : "") + ";\n";
					}
				} else if (method.params.length == 1 && (method.className.substr(0, 3) == "add" || method.className.substr(0, 6) == "remove")) {
					var field : Field  = set.getFieldFromArrayType(method.params[0].classType);
					if (field != null) {
						content += "\t\tthis." + field.className + (method.className.substr(0, 3) == "add" ? ".push(" : ".remove(") +
							method.params[0].className + ");\n";
						content += "\t\treturn this;\n";
					}
				} else if (method.classType != "") {
					content += "\t\treturn " + Code.getDefaultValue(method.classType, "new Array()") + ";\n";
				}
				content += "\t}\n";
			}
		}
		return content;
	}

	static function generateConstructor(set : Set) : String {
		var content : String = "";
		if (set.constructors.length != 0) { //only one allowed
			var method : Method = set.constructors[0];
			content += "\n\tpublic function new(" + Lambda.map(method.params, p -> p.className + ": " + getType(p.classType)).join(", ") + ") {\n";
			if (set.superset != null) {
				var parentFields : Array<Field> = Lambda.filter(method.params, p -> set.getFieldCode(p.className) == null);
				content += "\t\tsuper(" + Lambda.map(parentFields, p -> p.className).join(", ") + ");\n";
			}
			for (param in method.params) {
				var field : Field = set.getFieldCode(param.className);
				if (field != null) {
					content += "\t\tthis." + field.className + " = " + param.className + ";\n";
				}
			}
			for (fields in [set.fields, set.navFields]) {
				for (field in fields) {
					if (!field.isStatic && !Lambda.exists(method.params, p -> field.className == p.className)) {
						content += "\t\tthis." + field.className + " = " + Code.getDefaultValue(field.classType, "new Array()") + ";\n";
					}
				}
			}
			content += "\t}\n";
		}
		return content;
	}

	static function getType(classType : String) : String {
		var languageType : String = classType;
		if (classType == "Time" || classType == "DateTime") {
			languageType = "Date";
		}
		return languageType;
	}
}
