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

@:expose
class PhpCode {
	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 + ".php", generateClass(set));
			}
		}
		return files;
	}

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

	static function addImports(set : Set) : String {
		var imports : Array<String> = new Array();
		for (fields in [set.fields, set.navFields]) {
			for (field in fields) {
				var classType : String = field.classType.indexOf("Array") == 0 ? field.classType.substring(6, field.classType.length - 1) : field.classType;
				if (getType(classType).charCodeAt(0) <= 90 && classType != set.className && getType(classType) != "DateTime" &&
					!Lambda.has(imports, "require_once('" + classType + ".php');")) {
					imports.push("require_once('" + classType + ".php');");
				}
			}
		}
		if (set.superset != null && !Lambda.has(imports, "require_once('" + set.superset.className + ".php');")) {
			imports.push("require_once('" + set.superset.className + ".php');");
		}
		var content : String = imports.join("\n") + "\n\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 ? "private " :
								(field.visibility == Field.ACCESS_PROTECTED ? "protected " : "public ")) +
					(field.isStatic ? "static " : "") + getType(field.classType) + " $" + field.className + ";\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.visibility == Field.ACCESS_PRIVATE ? "private " :
									(method.visibility == Field.ACCESS_PROTECTED ? "protected " : "public ")) +
					(method.isStatic ? "static " : "") + "function " + method.className + "(" +
					Lambda.map(method.params, p -> getType(p.classType) + " $" + p.className).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\t$this->" + field.className + " = $" + field.className + ";\n";
						}
						if (field.classType.substr(0, 5) == "Array") {
							content += "\t\treturn $this->" + field.className + "->clone();\n";
						} else {
							content += "\t\treturn $this->" + field.className + ";\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\t$this->" + field.className + (method.className.substr(0, 3) == "add" ? "->add(" : "->remove(") +
							method.params[0].className + ");\n";
						content += "\t\treturn $this;\n";
					}
				} else if (method.classType != "") {
					content += "\t\treturn " + Code.getDefaultValue(method.classType, "[]") + ";\n";
				}
				content += "\t}\n";
			}
		}
		return content;
	}

	static function generateConstructors(set : Set) : String {
		var content : String = "";
		for (method in set.constructors) {
			content += "\n\tfunction __construct(" + Lambda.map(method.params, p -> getType(p.classType) + " $" + p.className).join(", ") + ") {\n";
			if (set.superset != null) {
				var parentFields : Array<Field> = Lambda.filter(method.params, p -> set.getFieldCode(p.className) == null);
				content += "\t\tparrent::__construct(" + 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\t$this->" + 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\t$this->" + field.className + " = " + Code.getDefaultValue(field.classType, "[]") + ";\n";
					}
				}
			}
			content += "\t}\n";
		}
		return content;
	}

	static function getType(classType : String) : String {
		var languageType : String = classType;
		if (classType.substr(0, 5) == "Array") {
			languageType = "array/*" + classType.substr(5) + "*/";
		} else if (classType == "Int" || classType == "Float" || classType == "Double" || classType == "String") {
			languageType = classType.toLowerCase();
		} else if (classType == "Date" || classType == "Time" || classType == "DateTime") {
			languageType = "DateTime";
		} else if (classType == "Bool") {
			languageType = "boolean";
		} else if (classType == "T") {
			languageType = "mixed";
		}
		return languageType;
	}
}
