import japa.parser.ast.body.ModifierSet;

import java.lang.reflect.Type;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.TypeVariable;
import java.util.Map;
import java.util.Hashtable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import codeassess.javassessment.Assessor;
import codeassess.misc.Misc;

public class Template extends Assessor
{
	public Map<String, String> getConfig()
	{
		final Map<String, String> config = new Hashtable<String, String>();

		config.put("Language", "Java");
		config.put("Extensions", ".java");
		config.put("AssessmentMode", "SemiAutomatic");

		return config;
	}
	public List<Object[]> getProperties()
	{
		final List<Object[]> properties = new LinkedList<Object[]>();

		// Interfaz Desplazamiento
		properties.add(new Object[]{ "ExisteInterfazDesplazamiento", "Functionality", "Desplazamiento.java", "Comprueba que la interfaz se llama \"Desplazamiento\"", "Desplazamiento: Debe existir la interfaz Desplazamiento", 0.0 });						
		properties.add(new Object[]{ "DesplazamientoDesplazaXMethod", "Functionality", "Desplazamiento.java", "Comprueba el nombre y los argumentos del método", "Desplazamiento: Crear el método desplazaX de la clase Desplazamiento", 0.0 });
		properties.add(new Object[]{ "DesplazamientoDesplazaYMethod", "Functionality", "Desplazamiento.java", "Comprueba el nombre y los argumentos del método", "Desplazamiento: Crear el método desplazaY de la clase Desplazamiento", 0.0 });
		properties.add(new Object[]{ "DesplazamientoDesplazaXYMethod", "Functionality", "Desplazamiento.java", "Comprueba el nombre y los argumentos del método", "Desplazamiento: Crear el método desplazaXY de la clase Desplazamiento", 0.0 });				
         
        // Clase Figura 
		properties.add(new Object[]{ "ExisteClaseFigura", "Functionality", "Figura.java", "Debes usar \"abstract\"", "Figura: Debe existir la clase abstracta Figura", 0.0 });         
		properties.add(new Object[]{ "FiguraNoTieneSuperclase", "Functionality", "Figura.java", "No debes usar \"extends\"", "Figura: No debe heredar de ninguna clase", 0.0 });         		
		properties.add(new Object[]{ "FiguraImplementsDesplazamiento", "Functionality", "Figura.java", "Debes usar \"implements\"", "Figura: Debe implementar la interfaz Desplazamiento", 1.0 });         
		properties.add(new Object[]{ "FiguraDesplazaXMethod", "Functionality", "Figura.java", "Comprueba el nombre y los argumentos del método", "Figura: Crear el método desplazaX de la clase Figura", 0.3 });
		properties.add(new Object[]{ "FiguraDesplazaYMethod", "Functionality", "Figura.java", "Comprueba el nombre y los argumentos del método", "Figura: Crear el método desplazaY de la clase Figura", 0.3 });
		properties.add(new Object[]{ "FiguraDesplazaXYMethod", "Functionality", "Figura.java", "Comprueba el nombre y los argumentos del método", "Figura: Crear el método desplazaXY de la clase Figura", 0.4 });		

        // Clase Circulo
		properties.add(new Object[]{ "ExisteClaseCirculo", "Functionality", "Circulo.java", "Comprueba que la clase se llama \"Circulo\"", "Circulo: Debe existir la clase Circulo", 0.0 });
		properties.add(new Object[]{ "CirculoExtendsFigura", "Functionality", "Circulo.java", "Debes usar \"extends\"", "Circulo: Debe heredar de la clase Figura", 0.0 });
		properties.add(new Object[]{ "CirculoNoImplementaInterfaces", "Functionality", "Circulo.java", "No debes usar \"implements\"", "Circulo: NO debe implementar ninguna interfaz", 0.0 });
		properties.add(new Object[]{ "CirculoNoImplementaDesplazaMethod", "Functionality", "Circulo.java", "Debes herededar los métodos", "Circulo: NO crear los métodos desplazaX / desplazaY / desplazaXY en la clase Circulo", 0.0 });
						
        // Clase Triangulo				
		properties.add(new Object[]{ "ExisteClaseTriangulo", "Functionality", "Triangulo.java", "Comprueba que has llamado a la clase \"Triangulo\"", "Triangulo: Debe existir la clase Triangulo", 0.0 });
		properties.add(new Object[]{ "TrianguloExtendsFigura", "Functionality", "Triangulo.java", "Debes usar \"extends\"", "Triangulo: Debe heredar de la clase Figura", 0.0 });
		properties.add(new Object[]{ "TrianguloNoImplementaInterfaces", "Functionality", "Triangulo.java", "No debes usar \"implements\"", "Triangulo: NO debe implementar ninguna interfaz", 0.0 });
		properties.add(new Object[]{ "TrianguloNoImplementaDesplazaMethod", "Functionality", "Triangulo.java", "Debes herededar los métodos", "Triangulo: NO crear los métodos desplazaX / desplazaY / desplazaXY en la clase Triangulo", 0.0 });		

        // Clase GrupoFiguras
		properties.add(new Object[]{ "ExisteClaseGrupoFiguras", "Functionality", "GrupoFiguras.java", "Comprueba que has llamado a la clase \"GrupoFiguras\"", "GrupoFiguras: Debe existir la clase GrupoFiguras", 0.0 });
		properties.add(new Object[]{ "GrupoFigurasGenericidad", "Functionality", "GrupoFiguras.java", "Debes incluir una variable de tipo con \"<…>\"", "GrupoFiguras: Es una clase genérica (con una única variable de tipo)", 0.5 });
		properties.add(new Object[]{ "GrupoFigurasGenericidadLimitada", "Functionality", "GrupoFiguras.java", "Debes usar \"extends\"", "GrupoFiguras: La variable de tipo de GrupoFiguras hereda de Figura", 0.5 });
		properties.add(new Object[]{ "GrupoFigurasListaFigurasField", "Functionality", "GrupoFiguras.java", "Comprueba que el atributo es de tipo ArrayList<T>", "GrupoFiguras: Tiene un único atributo de tipo ArrayList<T>", 0.75 });
		properties.add(new Object[]{ "GrupoFigurasAnyadeFiguraMethod", "Functionality", "GrupoFiguras.java", "Comprueba el nombre y los argumentos del método", "GrupoFiguras: Debe tener un método anyadeFigura", 2.0 });
		properties.add(new Object[]{ "GrupoFigurasDesplazaXYMethod", "Functionality", "GrupoFiguras.java", "Comprueba el nombre y los argumentos del método", "GrupoFiguras: Debe tener un método desplazaXY", 2.0 });		
		properties.add(new Object[]{ "GrupoFigurasUnirMethod", "Functionality", "GrupoFiguras.java", "Comprueba el nombre y los argumentos del método", "GrupoFiguras: Debe tener un método unir", 2.25 });		
														        
		return properties;
	}

    // Existen las clases y las interfaces

	// Existe la clase abstracta Figura
	public boolean checkExisteClaseFigura()
	{	
		if (!(super.introspector.checkClassExists("Figura"))) return false;

		final Class<?> figuraClass = super.introspector.getClass("Figura");
		return Modifier.isAbstract( figuraClass.getModifiers() );			
	}
	public boolean solveExisteClaseFigura()
	{
		return false;
	}
	public boolean testExisteClaseFigura()
	{
		return true;
	}

	// Existe la clase Triangulo
	public boolean checkExisteClaseTriangulo()
	{
		return super.introspector.checkClassExists("Triangulo");
	}
	public boolean solveExisteClaseTriangulo()
	{
		return false;
	}
	public boolean testExisteClaseTriangulo()
	{
		return true;
	}

	// Existe la clase Circulo
	public boolean checkExisteClaseCirculo()
	{
		return super.introspector.checkClassExists("Circulo");
	}
	public boolean solveExisteClaseCirculo()
	{
		return false;
	}
	public boolean testExisteClaseCirculo()
	{
		return true;
	}

	// Existe la clase GrupoFiguras
	public boolean checkExisteClaseGrupoFiguras()
	{
		return super.introspector.checkClassExists("GrupoFiguras");
	}
	public boolean solveExisteClaseGrupoFiguras()
	{
		return false;
	}
	public boolean testExisteClaseGrupoFiguras()
	{
		return true;
	}

	// Existe la interfaz Desplazamiento
	public boolean checkExisteInterfazDesplazamiento()
	{
		return super.introspector.checkClassExists("Desplazamiento");
	}
	public boolean solveExisteInterfazDesplazamiento()
	{
		return false;
	}
	public boolean testExisteInterfazDesplazamiento()
	{
		return true;
	}

    // La herencia es correcta	
				
	// Figura no debe heredar de ninguna clase
	public boolean checkFiguraNoTieneSuperclase()
	{
		final Class<?> figuraClass = super.introspector.getClass("Figura");
		if (figuraClass == null)
		return false;

		return super.introspector.getSuperclass(figuraClass) == Object.class;
	}
	public boolean solveFiguraNoTieneSuperclase()
	{
		return false;
	}
	public boolean testFiguraNoTieneSuperclase()
	{
		return true;
	}

	// Circulo extends Figura
	public boolean checkCirculoExtendsFigura()
	{
		final Class<?> circuloClass = super.introspector.getClass("Circulo");
		final Class<?> figuraClass = super.introspector.getClass("Figura");
		if (circuloClass == null || figuraClass == null)
			return false;

		return super.introspector.checkSuperclass(circuloClass, figuraClass);
	}
	public boolean solveCirculoExtendsFigura()
	{
		return false;
	}
	public boolean testCirculoExtendsFigura()
	{
		return true;
	}

	// Triangulo extends Figura
	public boolean checkTrianguloExtendsFigura()
	{
		final Class<?> trianguloClass = super.introspector.getClass("Triangulo");
		final Class<?> figuraClass = super.introspector.getClass("Figura");
		if (trianguloClass == null || figuraClass == null)
			return false;

		return super.introspector.checkSuperclass(trianguloClass, figuraClass);
	}
	public boolean solveTrianguloExtendsFigura()
	{
		return false;
	}
	public boolean testTrianguloExtendsFigura()
	{
		return true;
	}

    // Se implementan las interfaces que deben implementarse
			
	// Figura implements Desplazamiento
	public boolean checkFiguraImplementsDesplazamiento()
	{
		final Class<?> figuraClass = super.introspector.getClass("Figura");
		final Class<?> desplazamientoClass = super.introspector.getClass("Desplazamiento");
		if (figuraClass == null || desplazamientoClass == null)
			return false;

		return super.introspector.getInterfaceCount(figuraClass) == 1 && super.introspector.checkImplements(figuraClass, desplazamientoClass);
	}
	public boolean solveFiguraImplementsDesplazamiento()
	{
		return false;
	}
	public boolean testFiguraImplementsDesplazamiento()
	{
		return true;
	}

	// Circulo does NOT implement any interface
	public boolean checkCirculoNoImplementaInterfaces()
	{
		final Class<?> circuloClass = super.introspector.getClass("Circulo");
		if (circuloClass == null)
			return false;

		return super.introspector.getInterfaceCount(circuloClass) == 0;
	}
	public boolean solveCirculoNoImplementaInterfaces()
	{
		return false;
	}
	public boolean testCirculoNoImplementaInterfaces()
	{
		return true;
	}

	// Triangulo does NOT implement any interface
	public boolean checkTrianguloNoImplementaInterfaces()
	{
		final Class<?> trianguloClass = super.introspector.getClass("Triangulo");
		if (trianguloClass == null)
			return false;

		return super.introspector.getInterfaceCount(trianguloClass) == 0;
	}
	public boolean solveTrianguloNoImplementaInterfaces()
	{
		return false;
	}
	public boolean testTrianguloNoImplementaInterfaces()
	{
		return true;
	}

    // Los atributos y métodos de las clases son los correctos
        
	// Desplazamiento implementa método DesplazaX 		
	public boolean checkDesplazamientoDesplazaXMethod()
	{
		final Class<?> desplazamientoClass = super.introspector.getClass("Desplazamiento");
		if (desplazamientoClass == null)
			return false;

		final Class<?>[] paramTypesX = { double.class };
		
		return super.introspector.checkDeclaredMethod(desplazamientoClass, "desplazaX", paramTypesX, void.class, true);
	}
	public boolean solveDesplazamientoDesplazaXMethod()
	{
		return false;
	}
	public boolean testDesplazamientoDesplazaXMethod()
	{
		return true;
	}

	// Desplazamiento implementa método DesplazaY 		
	public boolean checkDesplazamientoDesplazaYMethod()
	{
		final Class<?> desplazamientoClass = super.introspector.getClass("Desplazamiento");
		if (desplazamientoClass == null)
			return false;

		final Class<?>[] paramTypesX = { double.class };
		
		return super.introspector.checkDeclaredMethod(desplazamientoClass, "desplazaY", paramTypesX, void.class, true);
	}
	public boolean solveDesplazamientoDesplazaYMethod()
	{
		return false;
	}
	public boolean testDesplazamientoDesplazaYMethod()
	{
		return true;
	}

	// Desplazamiento implementa método DesplazaXY 		
	public boolean checkDesplazamientoDesplazaXYMethod()
	{
		final Class<?> desplazamientoClass = super.introspector.getClass("Desplazamiento");
		if (desplazamientoClass == null)
			return false;

		final Class<?>[] paramTypesXY = { double.class, double.class };
		
		return super.introspector.checkDeclaredMethod(desplazamientoClass, "desplazaXY", paramTypesXY, void.class, true);
	}
	public boolean solveDesplazamientoDesplazaXYMethod()
	{
		return false;
	}
	public boolean testDesplazamientoDesplazaXYMethod()
	{
		return true;
	}

	// Circulo NO implementa métodos DesplazaX / DesplazaY / DesplazaXY 			
	public boolean checkCirculoNoImplementaDesplazaMethod()
	{
		final Class<?> circuloClass = super.introspector.getClass("Circulo");
		if (circuloClass == null)
			return false;

		final Class<?>[] paramTypesX = { double.class };
		final Class<?>[] paramTypesXY = { double.class, double.class };
		
		return (!(super.introspector.checkDeclaredMethod(circuloClass, "desplazaX", paramTypesX, void.class, true))) &&
			   (!(super.introspector.checkDeclaredMethod(circuloClass, "desplazaY", paramTypesX, void.class, true))) &&
			   (!(super.introspector.checkDeclaredMethod(circuloClass, "desplazaXY", paramTypesXY, void.class, true)));
	}	
	public boolean solveCirculoNoImplementaDesplazaMethod()
	{
		return false;
	}
	public boolean testCirculoNoImplementaDesplazaMethod()
	{
		return true;
	}

	// Triangulo NO implementa métodos DesplazaX / DesplazaY / DesplazaXY 			
	public boolean checkTrianguloNoImplementaDesplazaMethod()
	{
		final Class<?> trianguloClass = super.introspector.getClass("Triangulo");
		if (trianguloClass == null)
			return false;

		final Class<?>[] paramTypesX = { double.class };
		final Class<?>[] paramTypesXY = { double.class, double.class };
		
		return (!(super.introspector.checkDeclaredMethod(trianguloClass, "desplazaX", paramTypesX, void.class, true))) &&
			   (!(super.introspector.checkDeclaredMethod(trianguloClass, "desplazaY", paramTypesX, void.class, true))) &&
			   (!(super.introspector.checkDeclaredMethod(trianguloClass, "desplazaXY", paramTypesXY, void.class, true)));
	}	
	public boolean solveTrianguloNoImplementaDesplazaMethod()
	{
		return false;
	}
	public boolean testTrianguloNoImplementaDesplazaMethod()
	{
		return true;
	}

	// Figura implementa método DesplazaX 		
	public boolean checkFiguraDesplazaXMethod()
	{
		final Class<?> figuraClass = super.introspector.getClass("Figura");
		if (figuraClass == null)
			return false;

		final Class<?>[] paramTypesX = { double.class };
		
		return super.introspector.checkDeclaredMethod(figuraClass, "desplazaX", paramTypesX, void.class, true);
	}
	public boolean solveFiguraDesplazaXMethod()
	{
		return false;
	}
	public boolean testFiguraDesplazaXMethod()
	{
		final String testCaseName = "TestFiguraDesplazaXMethod";
		final String testMethodName = "test";
		final double posX = 12.3;
		final double posY = 56.6;
		final double despX = 4.2;		
		final Double expectedResult = posX+despX;
		
		final Object[] args = { posX, posY, despX };
		final Object testCaseResult = super.tester.executeTestCase(testCaseName, testMethodName, args);

		return expectedResult.equals(testCaseResult);
	}	

	// Figura implementa método DesplazaY		
	public boolean checkFiguraDesplazaYMethod()
	{
		final Class<?> figuraClass = super.introspector.getClass("Figura");
		if (figuraClass == null)
			return false;

		final Class<?>[] paramTypesX = { double.class };
		
		return super.introspector.checkDeclaredMethod(figuraClass, "desplazaY", paramTypesX, void.class, true);
	}
	public boolean solveFiguraDesplazaYMethod()
	{
		return false;
	}
	public boolean testFiguraDesplazaYMethod()
	{
		final String testCaseName = "TestFiguraDesplazaYMethod";
		final String testMethodName = "test";
		final double posX = 12.3;
		final double posY = 56.6;
		final double despY = 4.2;		
		final Double expectedResult = posY+despY;
		
		final Object[] args = { posX, posY, despY };
		final Object testCaseResult = super.tester.executeTestCase(testCaseName, testMethodName, args);

		return expectedResult.equals(testCaseResult);
	}	
	
	// Figura implementa método DesplazaXY 		
	public boolean checkFiguraDesplazaXYMethod()
	{
		final Class<?> figuraClass = super.introspector.getClass("Figura");
		if (figuraClass == null)
			return false;

		final Class<?>[] paramTypesXY = { double.class, double.class };
		
		return super.introspector.checkDeclaredMethod(figuraClass, "desplazaXY", paramTypesXY, void.class, true);
	}
	public boolean solveFiguraDesplazaXYMethod()
	{
		return false;
	}
	public boolean testFiguraDesplazaXYMethod()
	{
		final String testCaseName = "TestFiguraDesplazaXYMethod";
		final String testMethodName = "test";
		final double posX = 12.3;
		final double posY = 56.6;
		final double despX = 4.2;
		final double despY = 1.3;		
		final Boolean expectedResult = true;
		
		final Object[] args = { posX, posY, despX, despY };
		final Object testCaseResult = super.tester.executeTestCase(testCaseName, testMethodName, args);

		return expectedResult.equals(testCaseResult);
	}	
		
	// GrupoFiguras es una clase genérica (con una única variable de tipo)
	public boolean checkGrupoFigurasGenericidad()
	{
		final Class<?> grupoFigurasClass = super.introspector.getClass("GrupoFiguras");
		if (grupoFigurasClass == null)
			return false;

		return super.introspector.getGenericTypeParameterCount(grupoFigurasClass) == 1;
	}
	public boolean solveGrupoFigurasGenericidad()
	{
		return false;
	}
	public boolean testGrupoFigurasGenericidad()
	{
		return true;
	}

	// La variable de tipo de GrupoFiguras hereda de Figura
	public boolean checkGrupoFigurasGenericidadLimitada()
	{
		// No esta en la libreria
		return /*DAVID*/true ;
	}
	public boolean solveGrupoFigurasGenericidadLimitada()
	{
		return false;
	}
	public boolean testGrupoFigurasGenericidadLimitada()
	{
		return true;
	}

	// GrupoFiguras tiene un único atributo de tipo ArrayList<T>
	public boolean checkGrupoFigurasListaFigurasField()
	{
		final Class<?> grupoFigurasClass = super.introspector.getClass("GrupoFiguras");
		if (grupoFigurasClass == null)
			return false;

		final Field[] grupoFigurasFields = super.introspector.getDeclaredFields(grupoFigurasClass);
		if (grupoFigurasFields.length != 1)
			return false;

		// Esta opción funciona porque al haber una única variable de tipo en la clase (T), pues sabemos que la de arrayList debe ser T.
		//final Field grupoFigurasField = grupoFigurasFields[0];
		//return super.introspector.checkClass(grupoFigurasField, ArrayList.class, true) && this.checkGenericType(grupoFigurasField, 1);		

		// Esta opción funciona aunque hubieran más variables de tipo en la clase.

		final TypeVariable<?> classGenericType = super.introspector.getGenericTypeParameters(grupoFigurasClass)[0];
		final Field grupoFigurasField = grupoFigurasFields[0];
		final Type fieldType = super.introspector.getType(grupoFigurasField);
		final TypeVariable<?> fieldGenericType = super.introspector.getGenericType(fieldType, new int[] { 0 });
		return super.introspector.checkClass(grupoFigurasField, ArrayList.class, true) && super.introspector.checkSameGenericTypes(classGenericType, fieldGenericType);	
	}
	public boolean solveGrupoFigurasListaFigurasField()
	{
		return false;
	}
	public boolean testGrupoFigurasListaFigurasField()
	{
		return true;
	}

	// GrupoFiguras debe tener un método anyadeFigura
	public boolean checkGrupoFigurasAnyadeFiguraMethod()
	{
		final Class<?> grupoFigurasClass = super.introspector.getClass("GrupoFiguras");
		if (grupoFigurasClass == null)
			return false;

		final Class<?> figuraClass = super.introspector.getClass("Figura");
		if (figuraClass == null)
			return false;

		// Esto no comprueba que sea una variable genérica (anyadeFigura(T param)
		final Class<?>[] paramTypes = { figuraClass };  // Tiene un único parámetro de tipo T (una variable de tipo)

//		seria asi:
//		por desgracia no se he implementado la funcion
//		getGenericParameter(Method, int)
//		es decir, no esta en la libreria
//		final Method anyadeFiguraMethod = this.getDeclaredMethod(grupoFigurasClass, "anyadeFigura", paramTypes, void.class, true);
//		if (anyadeFiguraMethod == null) return false;
//		final TypeVariable<?> classGenericType = super.introspector.getGenericTypeParameters(grupoFigurasClass)[1];
//		final TypeVariable<?> parameterType = super.introspector.getGenericParameter(anyadeFiguraMethod)[1];
//		return super.introspector.checkSameGenericTypes(classGenericType, parameterType);
		
		return super.introspector.checkDeclaredMethod(grupoFigurasClass, "anyadeFigura", paramTypes, void.class, true);
	}
	public boolean solveGrupoFigurasAnyadeFiguraMethod()
	{
		return false;
	}
	public boolean testGrupoFigurasAnyadeFiguraMethod()
	{
		final String testCaseName = "TestGrupoFigurasAnyadeFiguraMethod";
		final String testMethodName = "test";
		final Boolean expectedResult = true;

		final Object[] args = { };
		final Object testCaseResult = super.tester.executeTestCase(testCaseName, testMethodName, args);

		return expectedResult.equals(testCaseResult);
	}

	// GrupoFiguras debe tener un método desplazaXY
	public boolean checkGrupoFigurasDesplazaXYMethod()
	{
		final Class<?> grupoFigurasClass = super.introspector.getClass("GrupoFiguras");
		if (grupoFigurasClass == null)
			return false;

		final Class<?>[] paramTypes = { double.class, double.class };

		return super.introspector.checkDeclaredMethod(grupoFigurasClass, "desplazaXY", paramTypes, void.class, true);
	}
	public boolean solveGrupoFigurasDesplazaXYMethod()
	{
		return false;
	}
	public boolean testGrupoFigurasDesplazaXYMethod()
	{
		final String testCaseName = "TestGrupoFigurasDesplazaXYMethod";
		final String testMethodName = "test";
		final Boolean expectedResult = true;

		final Object[] args = { };
		final Object testCaseResult = super.tester.executeTestCase(testCaseName, testMethodName, args);

		return expectedResult.equals(testCaseResult);
	}

	// GrupoFiguras debe tener un método unir
	public boolean checkGrupoFigurasUnirMethod()
	{
		final Class<?> grupoFigurasClass = super.introspector.getClass("GrupoFiguras");
		if (grupoFigurasClass == null)
			return false;

		final Class<?>[] paramTypes = { grupoFigurasClass };  // Tiene un único parámetro de tipo GrupoFiguras<T> 

		return super.introspector.checkDeclaredMethod(grupoFigurasClass, "unir", paramTypes, void.class, true);
	}
	public boolean solveGrupoFigurasUnirMethod()
	{
		return false;
	}
	public boolean testGrupoFigurasUnirMethod()
	{
		final String testCaseName = "TestGrupoFigurasUnirMethod";
		final String testMethodName = "test";
		final Boolean expectedResult = true;

		final Object[] args = { };
		final Object testCaseResult = super.tester.executeTestCase(testCaseName, testMethodName, args);

		return expectedResult.equals(testCaseResult);
	}
	
}