package de.dhbwstuttgart.strucTypes;

import java.util.ArrayList;
import java.util.List;

import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.Field;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.statement.ArgumentList;
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;

public class Algo_TypeExpr {

	/*
	 * 
	 * Hier sind die Ergebnisse
	 * 
	 * 
	 */
	// Ergebnisse: Expression Typed und Constraint-Set
	List<TC_Abstract> res_constriant_set;
	// TODO Hier muss noch ein passendes Datenformat gefunden werden
	String res_expression_typed;

	public Algo_TypeExpr() {

	}

	// übergabe der assumptions und der Statments der Methode
	public Algo_TypeExpr(List<AS_Abstract> ass, List<Statement> statements) {

		this.res_constriant_set = new ArrayList<TC_Abstract>();
		res_expression_typed = "";

		// Methode enthält kein Statement
		if (statements.size() == 0) {
			System.out.println("Not Implemmented");
		}

		// Methode enthält nur ein Return Statement das ein feld zurückgibt
		if (statements.size() == 1) {
			if (statements.get(0).getClass().equals(Return.class)) {
				Return exp_rt = (Return) statements.get(0);
				if (exp_rt.get_Expression().getClass().equals(LocalVar.class)) {
					LocalVar lvar = (LocalVar) exp_rt.get_Expression();

					String feldname = lvar.get_expression();

					for (AS_Abstract as : ass) {
						if (as.getClass().equals(AS_Argument.class)) {
							AS_Argument as_arg = (AS_Argument) as;
							if (as_arg.fp.getName().equals(feldname)) {
								res_expression_typed = feldname + " : " + as_arg.tv_alpha.toString();
								// res_constraint_set
							}
						}
					}
				}
			}
		}

		// Methode nur ein Return statment das ein feld zurückgibt und eine
		// Methode aufruft

	}

	/*
	 * Methode generiert Quellcode aus einer Methode Letztendlich wird in der
	 * Methode der gesamte Datentyp durchwandert und erstellt Nach dem selben
	 * Schema muss auch die generierung der Constriants erfolgen:
	 */
	public static String expression2code(List<AS_Abstract> ass, List<Statement> statements) {
		String res = "";

		// Leere methode
		if (statements.size() == 0) {
			res = "---";
		}

		// Methode mit einer Return Expression
		if (statements.size() == 1) {
			if (statements.get(0).getClass() == Return.class) {
				Return ret = (Return) statements.get(0);
				if (ret.getClass().equals(Statement.class)) {
					Statement ret2 = (Statement) ret.get_Expression();
					res = statement2code(ass, ret2);
				} else if (ret.getClass().equals(FieldVar.class)) {

					// Neu Implementiert - funktioniert es ??
					Expression ex = (Expression) ret;
					FieldVar fvar = (FieldVar) ex;

					String exp_str = fvar.getFieldVarName();

					// Statement Cast evtl. problematisch
					List<Statement> liste = new ArrayList<Statement>();
					liste.add((Statement) fvar.receiver);
					String reciever_str = expression2code(ass, liste);

					return reciever_str + "." + exp_str;

				}
			}
		}
		return res;
	}

	public static String statement2code(List<AS_Abstract> ass, Statement statement) {

		// TODO: Else if

		if (statement.getClass().equals(LocalVar.class)) {
			LocalVar lvar = (LocalVar) statement;
			String exp_str = lvar.get_expression();
			return exp_str;
		}

		// fieldvar muss ausgelagert werden weil es von Expression ableitett
		if (statement.getClass().equals(FieldVar.class)) {
			// Neu Implementiert - funktioniert es ??
			Expression ex = (Expression) statement;
			FieldVar fvar = (FieldVar) ex;

			String exp_str = fvar.getFieldVarName();

			// Statement Cast evtl. problematisch
			String reciever_str = statement2code(ass, (Statement) fvar.receiver);

			return reciever_str + "." + exp_str;
		}

		if (statement.getClass().equals(MethodCall.class)) {
			MethodCall mtcall = (MethodCall) statement;
			Statement receiver = (Statement) mtcall.get_Receiver().get_Expression();
			String str = statement2code(ass, receiver);
			ArgumentList list = mtcall.get_ArgList();
			String arg_str = "";
			for (Expression exp : list.getArguments()) {
				// TODO Cast evtl ist falsch
				Statement st = (Statement) exp;
				arg_str = arg_str + statement2code(ass, st);
			}
			// Todo Implementing Types:
			return str + "." + mtcall.get_Name() + "( " + arg_str + " )";
		}
		return "Fehler";
	}

	public List<TC_Abstract> generate(List<AS_Abstract> ass, List<Statement> exp) {

		// Methoden enhält keine Statements
		if (exp.size() == 0) {
			this.res_constriant_set = null;
			this.res_expression_typed = null;
		}

		// Methoden enthält ein Return Statement
		if (exp.size() == 1) {
			Statement std = (Statement) exp.get(0);
			if (std.getClass() == Return.class) {
				Return ret = (Return) std;
				generate_return(ass, ret);
			}
		}
		return null;
	}

	public List<TC_Abstract> generate_return(List<AS_Abstract> ass, Return ret) {

		Expression exp = ret.get_Expression();

		if (exp.getClass() == LocalVar.class) {
			LocalVar lvar = (LocalVar) exp;
			// TODO wie soll ich hier mit der Language umgehen
			return new ArrayList<TC_Abstract>();
		}

		if (exp.getClass() == FieldVar.class) {
			// Hier kommt der Constraint für die Field Var hinein.

		}

		if (exp.getClass() == MethodCall.class) {
			MethodCall mt_call = (MethodCall) exp;

			// Constraint

		}
		return null;
	}

	public List<TC_Abstract> getRes_constriant_set() {
		return res_constriant_set;
	}

	public String getRes_expression_typed() {
		return res_expression_typed;
	}

	// Neuer Versuch mit neuer herangehensweise um den Typen zu berechnen
	/*
	 * Es werden die Typen der Methoden berechnet und als String ausgegeben
	 */
	public static TV_Abstract get_rt_Type_of(Expression o, List<AS_Abstract> ass) {

		if (o.getClass().equals(LocalVar.class)) {
			LocalVar v = (LocalVar) o;
			return get_rt_Type_of_LocalVar(v, ass);
		} else if (o.getClass().equals(MethodCall.class)) {
			MethodCall m_call = (MethodCall) o;
			return get_rt_Type_of_MethodCall(m_call, ass);
		} else if (o.getClass().equals(FieldVar.class)) {
			FieldVar fvar = (FieldVar) o;
			String type_fvar = get_rt_Type_of_FieldVar(fvar, ass);
			return new TV_Dummy(type_fvar);
		} else {
			return null;
		}
	}

	public static TV_Alpha get_rt_Type_of_LocalVar(LocalVar v, List<AS_Abstract> ass) {
		AS_Argument arg = AS_Abstract.find_AS_Argument_by_Name(v.get_expression(), ass);
		return arg.tv_alpha;
	}

	public static TV_ReturnType2 get_rt_Type_of_MethodCall(MethodCall mt_call, List<AS_Abstract> ass) {
		TV_Abstract tv_receiver = get_rt_Type_of(mt_call.get_Receiver().get_Expression(), ass);
		TV_ReturnType2 res = new TV_ReturnType2(mt_call.get_Name(), tv_receiver.toString());
		return res;
	}

	public static String get_rt_Type_of_FieldVar(FieldVar fvar, List<AS_Abstract> ass) {

		TV_Abstract exp = get_rt_Type_of(fvar.getReceiver(), ass);

		return "DeltaXXX ( " + fvar.getFieldVarName() + "," + exp.toString() + ")";
	}

	// Neuer Versuch eine Methode berechneten Typen auszugeben
	public static String get_Return_typed(Method m, List<AS_Abstract> ass) {
		Return r = (Return) m.get_Block().statements.get(0);
		Expression ex = r.get_Expression();
		return get_rt_Type_of(ex, ass).toString();
	}

	// ------------------------------------------------------------------------
	// Erstellen der Constraints.
	// Auch dieses erstellen muss Rekursiv erfolgen
	// -------------------------------

	public static List<TC_Abstract> generateConstraint(ClassOrInterface cl, List<AS_Abstract> ass) {
		List<TC_Abstract> result = new ArrayList<>();

		// 1. Generate Field Constraints
		result.addAll(generateFieldConstraints(cl, ass));
		// 2. Generate Method Constraints
		result.addAll(generateMethodConstraints(cl, ass));

		return result;
	}

	public static List<TC_Field> generateFieldConstraints(ClassOrInterface cl, List<AS_Abstract> ass) {
		List<TC_Field> result = new ArrayList<>();

		for (Field f : cl.getFieldDecl()) {
			AS_Field as = (AS_Field) AS_Abstract.find_AS_Field(f, ass);
			TC_Field constraint = new TC_Field(cl, f, as.get_TV_Field());
			result.add(constraint);
		}
		return result;
	}

	/*
	 * Leider hat das erstellen der Constraints für die Methoden nicht mehr
	 * funktioniert Aus dies muss Rekursiv erfolgen
	 */
	
	public static List<TC_Abstract> generateMethodConstraints(ClassOrInterface es, List<AS_Abstract> ass) {
		
		List<TC_Abstract> constraints = new ArrayList<TC_Abstract>(); 
		
		for (Method m : es.getMethods() ) {
			Return r = (Return) m.get_Block().statements.get(0); 
			constraints.addAll(generateMethodConstraints(r.get_Expression(), ass)); 			
		}
		return constraints; 		
	}
	
	

	public static List<TC_Abstract> generateMethodConstraints(Expression ex, List<AS_Abstract> ass) {		
		
		

		
			if (ex.getClass().equals(LocalVar.class)) {
				List<TC_Abstract> result = new ArrayList<>();		
				return result; 	
			} else if (ex.getClass().equals(FieldVar.class)) {
				FieldVar v = (FieldVar) ex; 
				List<TC_Abstract> res = generateMethodConstraints(v.getReceiver(), ass); 
				TV_Abstract rt_type = get_rt_Type_of(v.getReceiver(),ass); 
				
				
				// NEW 	TC field TC_Field()
				res.add(new TC_Dummy(String.format("TC_Field: ( %s , %s , %s  )" , rt_type.toString()  , v.getFieldVarName() , "TV_Field(" +  rt_type.toString() + " , " + v.getFieldVarName()  ) )); 
				
				return res; 

			} else if (ex.getClass().equals(MethodCall.class)) {
				MethodCall m_call = (MethodCall) ex; 
				List<TC_Abstract> res = generateMethodConstraints(m_call.get_Receiver().get_Expression(), ass); 
				TV_Abstract rt_type = get_rt_Type_of(m_call, ass); 	
						
				
				res.add(new TC_Dummy("Klasse? " , m_call.get_Name() , "Arguments" , "ReturnType: " + rt_type.toString() , "Typvar: ?"      )); 
				return res; 	
				
			} else {
				// Darf nicht vorkommen
				System.out.println("Fehler - Type Expression");
				return null; 
			}		
	}



}
