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

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

	public static function generateClass(set : Set) : String {
		var content : String = "";
		content += addImports(set);
		if (set.isInterface) {
			content += "public interface " + set.className + (set.isGeneric() ? "<T>" : "") + " {\n";
		} else {
			content += "public " + (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 += generateConstructors(set);
		content += generateMethods(set);
		content += "}\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) {
				if (field.classType.substr(0, 5) == "Array" && !Lambda.has(imports, "java.util.List")) {
					imports.push("java.util.List");
					imports.push("java.util.ArrayList");
				} else if (field.classType.substr(0, 4) == "Date" && !Lambda.has(imports, "java.time.LocalDate")) {
					imports.push("java.time.LocalDate");
				} else if (field.classType.substr(0, 4) == "Time" && !Lambda.has(imports, "java.time.LocalTime")) {
					imports.push("java.time.LocalTime");
				} else if (field.classType.substr(0, 8) == "DateTime" && !Lambda.has(imports, "java.time.LocalDateTime")) {
					imports.push("java.time.LocalDateTime");
				}
			}
		}
		var content : String = "";
		for (impt in imports) {
			content += "import " + impt + ";\n";
		}
		if (imports.length != 0) 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 ? "protected " : "public ")) +
					(field.isStatic ? "static " : "") + getType(field.classType, true) + " " + 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) {
				if (Code.overrides(set, method)) content += "\n\t@Override";
				content += "\n\t" + (method.visibility == Field.ACCESS_PRIVATE ? "" : (method.visibility == Field.ACCESS_PROTECTED ? "protected " : "public ")) +
					(method.isStatic ? "static " : "") + (method.isStatic && method.isGeneric() ? "<T>" : "") +
					(method.classType == "" ? "void " : getType(method.classType) + " ") + method.className + "(" +
					Lambda.map(method.params, p -> getType(p.classType) + " " + p.className).join(", ") + ") {\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";
						}
						if (field.classType.substr(0, 5) == "Array") {
							content += "\t\treturn (" + getType(field.classType) + ") new " + getType(field.classType, true) + " (this." + field.className + ");\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\tthis." + 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, "new " + getType(method.classType) + "()") + ";\n";
				}
				content += "\t}\n";
			}
		}
		return content;
	}

	static function generateConstructors(set : Set) : String {
		var content : String = "";
		for (method in set.constructors) {
			content += "\n\tpublic " + set.className + "(" + 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\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" + getType(field.classType) + "()") + ";\n";
					}
				}
			}
			content += "\t}\n";
		}
		return content;
	}

	static function getType(classType : String, implementedType : Bool = false) : String {
		var languageType : String = classType;
		if (classType.substr(0, 5) == "Array") {
			languageType = (implementedType ? "Array" : "") + "List" + classType.substr(5);
		} else if (classType == "Int" || classType == "Float" || classType == "Double") {
			languageType = classType.toLowerCase();
		} else if (classType == "Bool") {
			languageType = "boolean";
		} else if (classType == "Date") {
			languageType = "LocalDate";
		} else if (classType == "Time") {
			languageType = "LocalTime";
		} else if (classType == "DateTime") {
			languageType = "LocalDateTime";
		}
		return languageType;
	}
}
