package com.jreframeworker.engine.identifiers;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;

public class DefineFinalityIdentifier {

	public static Set<String> getFinalityTargets(ClassNode classNode) throws IOException {
		DefineFinalityIdentifier finalityIdentifier = new DefineFinalityIdentifier(classNode);
		Set<String> targets = new HashSet<String>();
		for(DefineTypeFinalityAnnotation annotation : finalityIdentifier.getTargetTypes()){
			targets.add(annotation.getClassName());
		}
		for(DefineMethodFinalityAnnotation annotation : finalityIdentifier.getTargetMethods()){
			targets.add(annotation.getClassName());
		}
		for(DefineFieldFinalityAnnotation annotation : finalityIdentifier.getTargetFields()){
			targets.add(annotation.getClassName());
		}
		return targets;
	}
	
	public static Set<String> getFinalityTargets(ClassNode classNode, int phase) throws IOException {
		DefineFinalityIdentifier finalityIdentifier = new DefineFinalityIdentifier(classNode);
		Set<String> targets = new HashSet<String>();
		for(DefineTypeFinalityAnnotation annotation : finalityIdentifier.getTargetTypes()){
			if(annotation.getPhase() == phase){
				targets.add(annotation.getClassName());
			}
		}
		for(DefineMethodFinalityAnnotation annotation : finalityIdentifier.getTargetMethods()){
			if(annotation.getPhase() == phase){
				targets.add(annotation.getClassName());
			}
		}
		for(DefineFieldFinalityAnnotation annotation : finalityIdentifier.getTargetFields()){
			if(annotation.getPhase() == phase){
				targets.add(annotation.getClassName());
			}
		}
		return targets;
	}
	
	private static final String PHASE = "phase";
	private static final String TYPE = "type";
	private static final String FIELD = "field";
	private static final String METHOD = "method";
	private static final String FINALITY = "finality";
	
	public static class DefineTypeFinalityAnnotation {
		private int phase;
		private String className;
		private boolean finality;
		
		public DefineTypeFinalityAnnotation(int phase, String className, boolean finality) {
			this.phase = phase;
			this.className = className;
			this.finality = finality;
		}
		
		public int getPhase(){
			return phase;
		}
		
		public String getClassName(){
			return className;
		}
		
		public boolean getFinality(){
			return finality;
		}
	}
	
	public static class DefineMethodFinalityAnnotation {
		private int phase;
		private String className;
		private String methodName;
		private boolean finality;
		
		public DefineMethodFinalityAnnotation(int phase, String className, String methodName, boolean finality) {
			this.phase = phase;
			this.className = className;
			this.methodName = methodName;
			this.finality = finality;
		}
		
		public int getPhase(){
			return phase;
		}
		
		public String getClassName(){
			return className;
		}
		
		public String getMethodName(){
			return methodName;
		}
		
		public boolean getFinality(){
			return finality;
		}
	}
	
	public static class DefineFieldFinalityAnnotation {
		private int phase;
		private String className;
		private String fieldName;
		private boolean finality;
		
		public DefineFieldFinalityAnnotation(int phase, String className, String fieldName, boolean finality) {
			this.phase = phase;
			this.className = className;
			this.fieldName = fieldName;
			this.finality = finality;
		}
		
		public int getPhase(){
			return phase;
		}
		
		public String getClassName(){
			return className;
		}
		
		public String getFieldName(){
			return fieldName;
		}
		
		public boolean getFinality(){
			return finality;
		}
	}
	
	private LinkedList<DefineTypeFinalityAnnotation> targetTypes = new LinkedList<DefineTypeFinalityAnnotation>();
	private LinkedList<DefineMethodFinalityAnnotation> targetMethods = new LinkedList<DefineMethodFinalityAnnotation>();
	private LinkedList<DefineFieldFinalityAnnotation> targetFields = new LinkedList<DefineFieldFinalityAnnotation>();

	@SuppressWarnings("rawtypes")
	public DefineFinalityIdentifier(ClassNode classNode) {
		if (classNode.invisibleAnnotations != null) {
			for (Object annotationObject : classNode.invisibleAnnotations) {
				AnnotationNode annotation = (AnnotationNode) annotationObject;
				JREFAnnotationIdentifier checker = new JREFAnnotationIdentifier();
				checker.visitAnnotation(annotation.desc, false);
				
				// type finalities
				if(checker.isDefineTypeFinalitiesAnnotation()){
					for(Object value : annotation.values){
						if(value instanceof List){
							for(Object valueObject : (List) value){
								if(valueObject instanceof AnnotationNode){
									AnnotationNode annotationValue = (AnnotationNode) valueObject;
									extractDefineTypeFinalityAnnotationValues(classNode, annotationValue);
								}
							}
						}
						
					}
				} else if(checker.isDefineTypeFinalityAnnotation()){
					extractDefineTypeFinalityAnnotationValues(classNode, annotation);
				} 
				
				// method finalities
				else if(checker.isDefineMethodFinalitiesAnnotation()){
					for(Object value : annotation.values){
						if(value instanceof List){
							for(Object valueObject : (List) value){
								if(valueObject instanceof AnnotationNode){
									AnnotationNode annotationValue = (AnnotationNode) valueObject;
									extractDefineMethodFinalityValues(classNode, annotationValue);
								}
							}
						}
					}
				} else if(checker.isDefineMethodFinalityAnnotation()){
					extractDefineMethodFinalityValues(classNode, annotation);
				}  
				
				// field finalities
				else if(checker.isDefineFieldFinalitiesAnnotation()){
					for(Object value : annotation.values){
						if(value instanceof List){
							for(Object valueObject : (List) value){
								if(valueObject instanceof AnnotationNode){
									AnnotationNode annotationValue = (AnnotationNode) valueObject;
									extractDefineFieldFinalityValues(classNode, annotationValue);
								}
							}
						}
					}
				}  else if(checker.isDefineFieldFinalityAnnotation()){
					extractDefineFieldFinalityValues(classNode, annotation);
				} 	
			}
		}
    }

	private void extractDefineFieldFinalityValues(ClassNode classNode, AnnotationNode annotation) {
		int phaseValue = 1; // default to 1
		String typeValue = null;
		String fieldValue = null;
		Boolean finalityValue = null;
		if (annotation.values != null) {
		    for (int i = 0; i < annotation.values.size(); i += 2) {
		        String name = (String) annotation.values.get(i);
		        Object value = annotation.values.get(i + 1);
		        if(name.equals(PHASE)){
		        	phaseValue = (int) value;
		        } else if(name.equals(TYPE)){
		        	typeValue = ((String)value).replaceAll("\\.", "/");
		        } else if(name.equals(FIELD)){
		        	fieldValue = (String) value;
		        } else if(name.equals(FINALITY)){
		        	finalityValue = (boolean) value;
		        }
		    }
		    if(typeValue != null && fieldValue != null && finalityValue != null){
		    	String className = typeValue;
		    	if(className.equals("")){
		    		className = classNode.superName;
		    	}
		    	targetFields.add(new DefineFieldFinalityAnnotation(phaseValue, className, fieldValue, finalityValue));
		    }
		}
	}

	private void extractDefineMethodFinalityValues(ClassNode classNode, AnnotationNode annotation) {
		int phaseValue = 1; // default to 1
		String typeValue = null;
		String methodValue = null;
		Boolean finalityValue = null;
		if (annotation.values != null) {
		    for (int i = 0; i < annotation.values.size(); i += 2) {
		        String name = (String) annotation.values.get(i);
		        Object value = annotation.values.get(i + 1);
		        if(name.equals(PHASE)){
		        	phaseValue = (int) value;
		        } else if(name.equals(TYPE)){
		        	typeValue = ((String)value).replaceAll("\\.", "/");
		        } else if(name.equals(METHOD)){
		        	methodValue = (String) value;
		        } else if(name.equals(FINALITY)){
		        	finalityValue = (boolean) value;
		        }
		    }
		    if(typeValue != null && methodValue != null && finalityValue != null){
		    	String className = typeValue;
		    	if(className.equals("")){
		    		className = classNode.superName;
		    	}
		    	targetMethods.add(new DefineMethodFinalityAnnotation(phaseValue, className, methodValue, finalityValue));
		    }
		}
	}

	private void extractDefineTypeFinalityAnnotationValues(ClassNode classNode, AnnotationNode annotation) {
		int phaseValue = 1; // default to 1
		String typeValue = null;
		Boolean finalityValue = null;
		if (annotation.values != null) {
		    for (int i = 0; i < annotation.values.size(); i += 2) {
		        String name = (String) annotation.values.get(i);
		        Object value = annotation.values.get(i + 1);
		        if(name.equals(PHASE)){
		        	phaseValue = (int) value;
		        } else if(name.equals(TYPE)){
		        	typeValue = ((String)value).replaceAll("\\.", "/");
		        } else if(name.equals(FINALITY)){
		        	finalityValue = (boolean) value;
		        }
		    }
		    if(typeValue != null && finalityValue != null){
		    	String className = typeValue;
		    	if(className.equals("")){
		    		className = classNode.superName;
		    	}
		    	targetTypes.add(new DefineTypeFinalityAnnotation(phaseValue, className, finalityValue));
		    }
		}
	}
	
	public LinkedList<DefineTypeFinalityAnnotation> getTargetTypes() {
		return targetTypes;
	}
	
    public LinkedList<DefineMethodFinalityAnnotation> getTargetMethods() {
		return targetMethods;
	}
    
    public LinkedList<DefineFieldFinalityAnnotation> getTargetFields() {
		return targetFields;
	}
}