 package es.uniovi.reflection.patternmining.visitor.methods;

import javax.lang.model.element.Modifier;

import com.sun.source.tree.*;
import com.sun.source.util.TreeScanner;

import es.uniovi.reflection.patternmining.category.ExpressionRole;
import es.uniovi.reflection.patternmining.category.Others;
import es.uniovi.reflection.patternmining.category.StatementRole;
import es.uniovi.reflection.patternmining.category.TypeCategory;
import es.uniovi.reflection.patternmining.category.TypeParent;
import es.uniovi.reflection.patternmining.category.TypeRole;
import es.uniovi.reflection.patternmining.features.ExpressionFeatures;
import es.uniovi.reflection.patternmining.features.MethodFeatures;
import es.uniovi.reflection.patternmining.features.StatementFeatures;
import es.uniovi.reflection.patternmining.features.TypeFeatures;
import es.uniovi.reflection.patternmining.model.ReturnFeatures;
import es.uniovi.reflection.patternmining.util.Util;
import es.uniovi.reflection.patternmining.util.Util.NamingSystem;

public class MethodDefinitionsVisitor extends TreeScanner<ReturnFeatures, MethodFeatures> {


    @Override
    public ReturnFeatures visitMethod(MethodTree methodTree, MethodFeatures methodFeatures) {
        methodFeatures.setId(Util.getNextNodeId());
      	
    	//Get modifiers of method
        ModifiersTree methodModifiers = methodTree.getModifiers();
        
        //Get boolean features
        methodFeatures.setHasOverride(methodModifiers.getAnnotations().stream().anyMatch(e -> e.toString().equals("@Override()")));
        methodFeatures.setStatic(methodModifiers.getFlags().contains(Modifier.STATIC));
        methodFeatures.setFinal(methodModifiers.getFlags().contains(Modifier.FINAL));
        methodFeatures.setNative(methodModifiers.getFlags().contains(Modifier.NATIVE));
        methodFeatures.setSynchronized(methodModifiers.getFlags().contains(Modifier.SYNCHRONIZED));
        methodFeatures.setStrictFP(methodModifiers.getFlags().contains(Modifier.STRICTFP));
        methodFeatures.setDefaultImplementation(methodModifiers.getFlags().contains(Modifier.DEFAULT));
        try {
            methodFeatures.setSourceCode(Util.getNodeSourceCode(methodTree.toString()));
            methodFeatures.setSourceCode(methodFeatures.getSourceCode().replaceFirst(methodTree.getName().toString(), 
        		methodFeatures.getPackageName() + methodFeatures.getTypeDefinitionName() + methodTree.getName().toString()));
        } catch (Exception e) {
        	methodFeatures.setSourceCode(methodTree.toString());
        }
        
        
        ReturnFeatures returnFeatures = new ReturnFeatures();
        
        if(methodFeatures.hasOverride())
        	returnFeatures.setNumberOfOverloaded(1);
        
        //Get Void features
        methodFeatures.setNumberAnnotations(methodTree.getModifiers().getAnnotations().size());
        methodFeatures.setNumberParameters(methodTree.getParameters().size());
        methodFeatures.setNumberThrows(methodTree.getThrows().size());
        
        methodFeatures.setMethodNaming(Util.getNamingCovention(methodTree.getName().toString()));
        methodFeatures.setMethodVisibility(Util.getVisibility(methodTree.getModifiers()));
        
        methodFeatures.setNumberGenericTypes(methodTree.getTypeParameters().size());
        
        ReturnFeatures returnFeaturesAux = new ReturnFeatures();
        
        returnFeaturesAux = visitModifiers(methodTree.getModifiers(), methodFeatures);
        
        // Return Type
        if(methodTree.getReturnType() != null) {
            if (methodTree.getReturnType().toString().equals("void")) {
            	methodFeatures.setReturnType(Others.Void);
            } else {
            	TypeFeatures typeFeatures = new TypeFeatures();
            	
                typeFeatures.setParentId(methodFeatures.getId());
                typeFeatures.setParentNode(TypeParent.MethodDefinition);
                typeFeatures.setParentChild(TypeRole.ReturnType);
                typeFeatures.setHeight(methodFeatures.getHeight() + 1);
                
            	returnFeaturesAux = Util.visitors.getVisitorType().scan(methodTree.getReturnType(), typeFeatures);
            	
            	methodFeatures.setReturnType((TypeCategory) returnFeaturesAux.getType());
            	methodFeatures.setReturnTypeParameterId(returnFeaturesAux.getMethodTypeId());
            }
        } else {
        	//constructor
        	methodFeatures.setReturnType(Others.Constructor);
        	methodFeatures.setConstructor(true);
        	methodFeatures.setMethodNaming(NamingSystem.Constructor);
        	returnFeatures.setNumberOfConstructors(1);
        }
       
        // Parameters
        for(VariableTree vt: methodTree.getParameters()) {
        	methodFeatures.setParameterType(true);
        	returnFeaturesAux = vt.accept(this, methodFeatures);
        	Util.updateTypeParameterFeatures(methodFeatures, returnFeaturesAux);
        }
        
        // Body: Check if it's abstract, other way visit body childs
        if(methodTree.getBody() != null) {
        	returnFeaturesAux = visitBlock(methodTree.getBody(), methodFeatures); //to avoid declared method variables
  	      
        	returnFeatures.setNumberOfClasses(returnFeatures.getNumberOfClasses() + returnFeaturesAux.getNumberOfClasses());
        	returnFeatures.setNumberOfInnerTypes(returnFeatures.getNumberOfInnerTypes() + returnFeaturesAux.getNumberOfInnerTypes());
        	returnFeatures.setNumberOfStaticNestedTypes(returnFeatures.getNumberOfStaticNestedTypes() + returnFeaturesAux.getNumberOfStaticNestedTypes());
        }else {
        	methodFeatures.setAbstract(true);
        }
        
        int maximo=0;
        for(int i=0;i<methodFeatures.localVarStat.length;i++) {
        	if(methodFeatures.localVarStat[i] > maximo)
        		methodFeatures.setLocVarNaming(NamingSystem.values()[i]);
        }

        //Insercción
        try {        	
        	Util.databaseService.insertMethodDefinition(methodFeatures);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        
        return returnFeatures;
    }

    //not working
    @Override
    public ReturnFeatures visitVariable(VariableTree variableTree, MethodFeatures methodFeatures) {
        methodFeatures.setMethodLocalVars(methodFeatures.getMethodLocalVars()+1);
        Util.getNamingCovention(variableTree.getName().toString());//naming convection of local variable name
        switch(Util.getNamingCovention(variableTree.getName().toString())) {
			case Camel_Low:
				methodFeatures.localVarStat[ 0] += 1;
				break;
			case Camel_Up:
				methodFeatures.localVarStat[1] += 1;
				break;
			case Lower:
				methodFeatures.localVarStat[2] += 1;
				break;
			case Snake_Case:
				methodFeatures.localVarStat[3] += 1;
				break;
			case Upper:
				methodFeatures.localVarStat[4] += 1;
				break;
			default:
				break;
        
        }
        
        StatementFeatures statementFeatures = new StatementFeatures();
        statementFeatures.setParentId(methodFeatures.getId());
        statementFeatures.setHeight(methodFeatures.getHeight()); // because block it is not considered an statement
        statementFeatures.setParentNode(Others.MethodDefinition);
        statementFeatures.setParentChild(StatementRole.MethodParameterStatement);
        statementFeatures.setParameterType(methodFeatures.isParameterType());
        
        ReturnFeatures returnFeatures = Util.visitors.getVisitorStatements().visitVariable(variableTree, statementFeatures);
        return returnFeatures;
    }

    @Override
    public ReturnFeatures visitBlock(BlockTree blockTree, MethodFeatures methodFeatures) {
        //creation of the statement block for one method
        StatementFeatures statementFeatures = new StatementFeatures();
        
        statementFeatures.setParentId(methodFeatures.getId());
        statementFeatures.setHeight(methodFeatures.getHeight()); // because block it is not considered an statement
        statementFeatures.setParentNode(Others.MethodDefinition);
        statementFeatures.setParentChild(StatementRole.MethodBodyStatement);
        statementFeatures.setPackageName(methodFeatures.getPackageName());
        
        ReturnFeatures returnFeatures = Util.visitors.getVisitorStatements().visitBlock(blockTree, statementFeatures);
	
        //set values for method
        //System.out.println("Number of statements: "+returnFeatures.getNumberOfStatements());
        methodFeatures.setNumberStatements(returnFeatures.getNumberOfStatements());
        methodFeatures.setMethodLocalVars(returnFeatures.getMethodLocalVars());
        methodFeatures.setNumberInnerClasses(methodFeatures.getNumberInnerClasses() + returnFeatures.getNumberOfInnerTypes());
   
        //variable naming
        int localVarsIndex = 0;
        int localVars;
        NamingSystem[] namingSystem = {NamingSystem.Camel_Low, NamingSystem.Camel_Up, NamingSystem.Lower, NamingSystem.Snake_Case, NamingSystem.Upper};
        
        for(int i=0;i<5;i++) {
        	if(returnFeatures.getTotalsArrayVariableNaming()[i] > localVarsIndex) {
        		localVars = returnFeatures.getTotalsArrayVariableNaming()[i];
        		localVarsIndex = i;
        	}
        }
        
        methodFeatures.setLocVarNaming(namingSystem[localVarsIndex]);
        
        return returnFeatures;
    }
    
    public ReturnFeatures visitModifiers(ModifiersTree modifiersTree, MethodFeatures methodFeatures) {
    	ReturnFeatures returnFeatures = new ReturnFeatures();
    	
    	for(AnnotationTree annotation: modifiersTree.getAnnotations()) {
    		returnFeatures = scan(annotation, methodFeatures);
    	}

		return returnFeatures;
	}
    
    @Override
    public ReturnFeatures visitAnnotation(AnnotationTree annotationTree, MethodFeatures methodFeatures)  { //hoja
      
        ExpressionFeatures expressionFeatures = new ExpressionFeatures();
        
        expressionFeatures.setHeight(methodFeatures.getHeight());
        expressionFeatures.setParentId(methodFeatures.getId());
        expressionFeatures.setParentNode(Others.MethodDefinition);
        expressionFeatures.setParentChild(ExpressionRole.MethodAnnotation);
        
        ReturnFeatures returnFeatures = Util.visitors.getVisitorExpressions().visitAnnotation(annotationTree, expressionFeatures);      
        return returnFeatures;
    }

 
}
