package strucTypes2;

import static org.junit.Assert.*;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.dhbwstuttgart.parser.JavaTXParser;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.Field;
import de.dhbwstuttgart.syntaxtree.FormalParameter;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.SourceFile;
import de.dhbwstuttgart.syntaxtree.statement.Block;
import de.dhbwstuttgart.syntaxtree.statement.Expression;
import de.dhbwstuttgart.syntaxtree.statement.FieldVar;
import de.dhbwstuttgart.syntaxtree.statement.LocalVar;
import de.dhbwstuttgart.syntaxtree.statement.MethodCall;
import de.dhbwstuttgart.syntaxtree.statement.Receiver;
import de.dhbwstuttgart.syntaxtree.statement.Return;
import de.dhbwstuttgart.syntaxtree.statement.Statement;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import org.junit.Test;

/**
 * Dieser Test prï¿½ft nur, ob .java-Dateien fehlerfrei geparst werden. Der
 * dabei erstellte Syntaxbaum wird nicht kontrolliert.
 * 
 * @author janulrich
 *
 */
public class GeneralParserTest2 {
	private static final String rootDirectory = System.getProperty("user.dir") + "/test/strucTypes/";

	@Test
	public void run() {

		/*
		 * Auflistung aller Files die getestet werden sollen
		 */
		List<String> filenames = new ArrayList<String>();
		// filenames.add("FieldInitializationTest.jav");
		// filenames.add("ImportTest.jav");
		// filenames.add("StatementsTest.jav");
		// filenames.add("ImportTestGeneric.jav");
		// filenames.add("BoundedParameter.jav");
		// filenames.add("GenericFieldVarTest.jav");
		// filenames.add("FieldVarTest.jav");

		filenames.add("StructuralTypesSimple3.jav");
		// filenames.add("StructuralTypesField.jav");
		// filenames.add("Main.jav");

		JavaTXParser parser = new JavaTXParser();
		try {
			for (String filename : filenames) {

				System.out.println("Teste: " + filename);

				SourceFile sf = parser.parse(new File(rootDirectory + filename));

				SourceFile sfdebug = sf;

				// Erstellen der Assumptions
				// (Assumption für die Argumente der Methode werden gleich
				// miterstellt)

				// Assumptions
				Map<String, RefTypeOrTPHOrWildcardOrGeneric> fAss = new HashMap<>();
				Map<String, RefTypeOrTPHOrWildcardOrGeneric> mAss = new HashMap<>();
				// Assumptions für die MethodenParameter
				Map<Method, Map<String, RefTypeOrTPHOrWildcardOrGeneric>> argAss = new HashMap<>();
				
				AssTuple assAll = new AssTuple(fAss,mAss,argAss); 

				// Extraktion aller Typen der Methoden
				for (ClassOrInterface cl : sf.getClasses()) {
					for (Field f : cl.getFieldDecl()) {
						String fieldname = f.getName();
						String type = f.getType().toString();
						fAss.put(fieldname, f.getType());
						System.out.println(String.format("Feldname: %s Typ: %s", fieldname, type));
					}

					for (Method m : cl.getMethods()) {
						String methodName = m.getName();
						String returnType = m.getType().toString();
						mAss.put(methodName, m.getType());

						String typeArg = "";
						Map<String, RefTypeOrTPHOrWildcardOrGeneric> ass = new HashMap<>();
						for (FormalParameter p : m.getParameterList().getFormalparalist()) {
							String pName = p.getName();
							String pType = p.getType().toString();
							typeArg = typeArg + String.format("%s : %s", pName, pType);
							ass.put(p.getName(), p.getType());
						}
						argAss.put(m, ass);
						System.out.println(String.format("Methodenname: %s , Arguments: %s ->  ReturnType %s",
								methodName, typeArg, returnType));
					}
				}

				// Als nächstes muss die Expression getypt werden
				for (ClassOrInterface cl : sf.getClasses()) {
					for (Method m : cl.getMethods()) {
						Block block = m.get_Block();

						/*
						 * Wahrscheinlich ist der TPH des Blocks der wahre Typ
						 * und der Typ der methode kann auch kleiner sein
						 */
						System.out.println("Block" + block.getType().toString());

						// ein block hat einen TPH und eine Statement Liste
						// davon wird das erste statement genommen welches ein
						// Return Statement ist

						// TODO: check get 0
						Statement statement = block.getStatements().get(0);

						if (statement.getClass().equals(Return.class)) {
							Return rStatement = (Return) statement;
							Expression expression = rStatement.get_Expression();

							// An dieser Stelle muss ein Rekursiver Aufruf
							// möglich werden
							String exp = HelpMethoden.expression2String(expression);
							System.out.println("Expression:  " + exp);

							String exp_t = HelpMethoden.expression_typed2String(expression);
							System.out.println("Expression_typed: " + exp_t);
							
							List<ConstraintAbstract> constraints = HelpMethoden.TypeExpr(assAll, expression, m, cl); 
							for (ConstraintAbstract c : constraints) {
								System.out.println(c.toString());
							}	
							
							
							
							

						} else {
							System.err.println("Problem");
						}

					}
				}

			}
		} catch (Exception exc) {
			exc.printStackTrace();
			fail();
		}
		assertTrue("Tests durchlaufen", filenames.size() > 0);
	}
}

class HelpMethoden {

	public static String expression2String(Expression expression) {

		// LocalVar
		if (expression.getClass().equals(LocalVar.class)) {
			String resultStr = "";
			LocalVar lvar = (LocalVar) expression;
			resultStr = resultStr + lvar.get_expression();
			return resultStr;
		}
		// FeldVar
		else if (expression.getClass().equals(FieldVar.class)) {
			FieldVar fieldVar = (FieldVar) expression;
			Expression receiver = fieldVar.getReceiver();
			String result = HelpMethoden.expression2String(receiver);
			result = result + "." + fieldVar.getFieldVarName();
			return result;
		}
		// MethodCall
		else if (expression.getClass().equals(MethodCall.class)) {
			MethodCall methodCall = (MethodCall) expression;

			// nur bei MethodCall reciever = Typ Reciver mit eigenem TPH
			Expression receiver = methodCall.get_Receiver();

			String result;
			if (receiver.getClass().equals(Receiver.class)) {
				Receiver r_cast = (Receiver) receiver;
				result = HelpMethoden.expression2String(r_cast.get_Expression());
			} else {
				result = HelpMethoden.expression2String(receiver);
			}

			// Jetzt muss noch die ArgumentListe ermittelt werden
			// Problem, Diese kann alles enthalten
			String argumentsStr = "";
			List<Expression> arguments = methodCall.get_ArgList().getArguments();
			for (Expression arg : arguments) {
				argumentsStr = argumentsStr + " " + HelpMethoden.expression2String(arg);
			}

			result = String.format("%s.%s(%s)", result, methodCall.get_Name(), argumentsStr);
			return result;
		} else {
			System.err.println("Fehler");
			return null;
		}
	}

	public static String expression_typed2String(Expression expression) {

		// LocalVar
		if (expression.getClass().equals(LocalVar.class)) {
			String resultStr = "";
			LocalVar lvar = (LocalVar) expression;
			resultStr = resultStr + lvar.get_expression() + ":" + lvar.getType().toString();
			return resultStr;
		}
		// FeldVar
		else if (expression.getClass().equals(FieldVar.class)) {
			FieldVar fieldVar = (FieldVar) expression;
			Expression receiver = fieldVar.getReceiver();
			String result = HelpMethoden.expression_typed2String(receiver);
			result = result + "." + fieldVar.getFieldVarName() + ":" + fieldVar.getType().toString();
			return result;
		}
		// MethodCall
		else if (expression.getClass().equals(MethodCall.class)) {
			MethodCall methodCall = (MethodCall) expression;

			// nur bei MethodCall reciever = Typ Reciver mit eigenem TPH
			Expression receiver = methodCall.get_Receiver();

			String result;
			if (receiver.getClass().equals(Receiver.class)) {
				Receiver r_cast = (Receiver) receiver;
				result = HelpMethoden.expression_typed2String(r_cast.get_Expression());
			} else {
				result = HelpMethoden.expression_typed2String(receiver) + ":" + receiver.getType().toString();
			}

			// Jetzt muss noch die ArgumentListe ermittelt werden
			// Problem, Diese kann alles enthalten
			String argumentsStr = "";
			List<Expression> arguments = methodCall.get_ArgList().getArguments();
			for (Expression arg : arguments) {
				argumentsStr = argumentsStr + " " + HelpMethoden.expression_typed2String(arg) ;//+ arg.getType();
			}

			result = String.format("%s.%s(%s)", result, methodCall.get_Name(), argumentsStr);
			return result;
		} else {
			System.err.println("Fehler");
			return null;
		}
	}

	
	// Diese Methode übernimmt die Berechnung der Typen
	public static List<ConstraintAbstract> TypeExpr(AssTuple ass, Expression expression, Method method, ClassOrInterface cl) {

		// LocalVar (Constraints für den Algo, Constraints für die Interfaces
		// usw. )
		// LocalVar
		if (expression.getClass().equals(LocalVar.class)) {
			LocalVar lvar = (LocalVar) expression;
			List<ConstraintAbstract> constraints = new ArrayList<ConstraintAbstract>();
			RefTypeOrTPHOrWildcardOrGeneric type = ass.getTypeOfLocalVar(lvar.get_expression(), method); 
			GleichConstraint constraint = new GleichConstraint(lvar.getType(), type); 
			constraints.add(constraint); 
			return constraints; 			
		}
		// FeldVar
		else if (expression.getClass().equals(FieldVar.class)) {
			FieldVar fieldVar = (FieldVar) expression;
			Expression receiver = fieldVar.getReceiver();
			List<ConstraintAbstract> constraints = HelpMethoden.TypeExpr(ass, receiver, method, cl); 
			
			//TODO Hier muss der Constraint angelegt werden. Der Type receiver X besitzt ein feld mit dem Typen Y 
			//TODO constriant(c,f,c') 
			//TODO constraint(TypeKlasse, feldname, typdesfelde) 
			//TODO constriant(typReveier , besitzt ein feld mit dem typen , typdesfeldes); 
			//TODO constriant(typReceiver, name des Feldes , TypDesFeldes). 
			FieldConstraint constraint = new FieldConstraint(receiver.getType(), fieldVar.getFieldVarName() , fieldVar.getType()); 
			constraints.add(constraint); 
			return constraints;		
			
		}
		// MethodCall
		else if (expression.getClass().equals(MethodCall.class)) {
			MethodCall methodCall = (MethodCall) expression;

			// nur bei MethodCall reciever = Typ Reciver mit eigenem TPH
			Expression receiver = methodCall.get_Receiver();

			List<ConstraintAbstract> constraints; 
			if (receiver.getClass().equals(Receiver.class)) {
				Receiver r_cast = (Receiver) receiver;
				constraints = HelpMethoden.TypeExpr(ass, r_cast.get_Expression(), method, cl);
			} else {
				constraints = HelpMethoden.TypeExpr(ass, receiver, method, cl); 
			}
			
			//TODO An dieser Stelle muss noch der MethodenConstraint erstellt werden
			List<RefTypeOrTPHOrWildcardOrGeneric> argumentTypesVars = new ArrayList<>(); 
			List<RefTypeOrTPHOrWildcardOrGeneric> argumentTypes = new ArrayList<>(); 
			for (Expression arg : methodCall.get_ArgList().getArguments()) {
				argumentTypesVars.add(arg.getType()); 
				constraints.addAll(HelpMethoden.TypeExpr(ass, arg, method, cl)); 
			}
			
					
			
			
			MethodConstraint methodConstraint = new MethodConstraint(receiver.getType(), methodCall.get_Name(), argumentTypes, methodCall.getType(), argumentTypesVars );
			
			constraints.add(methodConstraint); 
			return constraints; 
			

			
		} else {
			System.err.println("Fehler");
			return null;
		}

	}

}



/****************************************
 *  Definition der Constraint Klassen
 */

class ConstraintAbstract {

}

class GleichConstraint extends ConstraintAbstract {

	private RefTypeOrTPHOrWildcardOrGeneric a;
	private RefTypeOrTPHOrWildcardOrGeneric b;

	// Type A ist Type B
	public GleichConstraint(RefTypeOrTPHOrWildcardOrGeneric a, RefTypeOrTPHOrWildcardOrGeneric b) {
		this.a = a;
		this.b = b;
	}
	
	public String toString() {
		return String.format("GleichConstraint( %s , %s )"  , a.toString(), b.toString());  
	}
	
}


class FieldConstraint extends ConstraintAbstract {
	
	private RefTypeOrTPHOrWildcardOrGeneric reveiver; 
	private String name_of_field; 
	private RefTypeOrTPHOrWildcardOrGeneric attribut;
	
	public FieldConstraint(RefTypeOrTPHOrWildcardOrGeneric receiver, String name_of_field,
			RefTypeOrTPHOrWildcardOrGeneric attribut) {
		super();
		this.reveiver = receiver;
		this.name_of_field = name_of_field;
		this.attribut = attribut;
	}
	
	public String toString() {
		return String.format("FieldConstraint(%s , %s , %s)" , reveiver, name_of_field, attribut ); 
	}

	public RefTypeOrTPHOrWildcardOrGeneric getReveiver() {
		return reveiver;
	}

	public void setReveiver(RefTypeOrTPHOrWildcardOrGeneric reveiver) {
		this.reveiver = reveiver;
	}

	public String getName_of_field() {
		return name_of_field;
	}

	public void setName_of_field(String name_of_field) {
		this.name_of_field = name_of_field;
	}

	public RefTypeOrTPHOrWildcardOrGeneric getAttribut() {
		return attribut;
	}

	public void setAttribut(RefTypeOrTPHOrWildcardOrGeneric attribut) {
		this.attribut = attribut;
	} 
	

}


class MethodConstraint extends ConstraintAbstract {
	
	/* Der MethodConstraint
	 * constraint( typ_receiver, methodname, arguments_typen ( return_type , typvar von argumenttypen )  
	 */
	
	private RefTypeOrTPHOrWildcardOrGeneric receiver; 
	private String methodName; 
	private List<RefTypeOrTPHOrWildcardOrGeneric> argumentTypes; 
	private RefTypeOrTPHOrWildcardOrGeneric returnType; 
	private List<RefTypeOrTPHOrWildcardOrGeneric> argumentTypeVars;
	
	public MethodConstraint(RefTypeOrTPHOrWildcardOrGeneric receiver, String methodName,
			List<RefTypeOrTPHOrWildcardOrGeneric> argumentTypes, RefTypeOrTPHOrWildcardOrGeneric returnType,
			List<RefTypeOrTPHOrWildcardOrGeneric> argumentTypeVars) {
		super();
		this.receiver = receiver;
		this.methodName = methodName;
		this.argumentTypes = argumentTypes;
		this.returnType = returnType;
		this.argumentTypeVars = argumentTypeVars;
	}
	
	public String toString() {
		return String.format("MethodConstraint( %s, %s , %s (%s , %s ))" , receiver.toString() , methodName, "arg" , returnType.toString() , argumentTypeVars.toString() );  
	}	
}