/*
 *    Fernflower - The Analytical Java Decompiler
 *    http://www.reversed-java.com
 *
 *    (C) 2008 - 2010, Stiver
 *
 *    This software is NEITHER public domain NOR free software 
 *    as per GNU License. See license.txt for more details.
 *
 *    This software is distributed WITHOUT ANY WARRANTY; without 
 *    even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 *    A PARTICULAR PURPOSE. 
 */

package de.fernflower.main.rels;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;

import de.fernflower.code.CodeConstants;
import de.fernflower.main.DecompilerContext;
import de.fernflower.main.ClassesProcessor.ClassNode;
import de.fernflower.main.collectors.CounterContainer;
import de.fernflower.main.collectors.VarNamesCollector;
import de.fernflower.main.extern.IFernflowerLogger;
import de.fernflower.main.extern.IFernflowerPreferences;
import de.fernflower.modules.decompiler.exps.AssignmentExprent;
import de.fernflower.modules.decompiler.exps.ConstExprent;
import de.fernflower.modules.decompiler.exps.Exprent;
import de.fernflower.modules.decompiler.exps.FieldExprent;
import de.fernflower.modules.decompiler.exps.InvocationExprent;
import de.fernflower.modules.decompiler.exps.NewExprent;
import de.fernflower.modules.decompiler.exps.VarExprent;
import de.fernflower.modules.decompiler.sforms.DirectGraph;
import de.fernflower.modules.decompiler.sforms.DirectNode;
import de.fernflower.modules.decompiler.stats.DoStatement;
import de.fernflower.modules.decompiler.stats.RootStatement;
import de.fernflower.modules.decompiler.stats.Statement;
import de.fernflower.modules.decompiler.vars.VarTypeProcessor;
import de.fernflower.modules.decompiler.vars.VarVersionPaar;
import de.fernflower.struct.StructClass;
import de.fernflower.struct.StructField;
import de.fernflower.struct.StructMethod;
import de.fernflower.struct.attr.StructEnclosingMethodAttribute;
import de.fernflower.struct.gen.MethodDescriptor;
import de.fernflower.struct.gen.VarType;
import de.fernflower.util.InterpreterUtil;

public class NestedClassProcessor {
	
	
	public void processClass(ClassNode root, ClassNode node) {

		// hide lambda content methods
		if(node.type == ClassNode.CLASS_LAMBDA) {
			ClassNode node_content = DecompilerContext.getClassprocessor().getMapRootClasses().get(node.classStruct.qualifiedName);
			if(node_content != null && node_content.wrapper != null) {
				node_content.wrapper.getHideMembers().add(node.lambda_information.content_method_key);
			}
		}
		
		if(node.nested.isEmpty()) {
			return;
		}

		if(node.type != ClassNode.CLASS_LAMBDA) {
		
			computeLocalVarsAndDefinitions(node);
			
			// for each local or anonymous class ensure not empty enclosing method 
			checkNotFoundClasses(root, node);
		}

    int nameless = 0, synthetics = 0;
    for(ClassNode child : node.nested) {
      // ensure not-empty class name
      if((child.type == ClassNode.CLASS_LOCAL || child.type == ClassNode.CLASS_MEMBER) && child.simpleName == null) {
        StructClass cl = child.classStruct;
        if(((child.access | cl.access_flags) & CodeConstants.ACC_SYNTHETIC) != 0 || cl.getAttributes().containsKey("Synthetic")) {
          child.simpleName = "SyntheticClass_" + (++synthetics);
        } else {
          DecompilerContext.getLogger().writeMessage("Nameless local or member class " + cl.qualifiedName + "!", IFernflowerLogger.WARNING);
          child.simpleName = "NamelessClass_" + (++nameless);
        }
      }
    }

		for(ClassNode child : node.nested) {
			if(child.type == ClassNode.CLASS_LAMBDA) {
				setLambdaVars(node, child);
			} else {
				if(child.type != ClassNode.CLASS_MEMBER || (child.access & CodeConstants.ACC_STATIC) == 0) {
					insertLocalVars(node, child);
					
					if(child.type == ClassNode.CLASS_LOCAL) {
						setLocalClassDefinition(node.wrapper.getMethods().getWithKey(child.enclosingMethod), child);
					}
				}
			}
		}
		
		for(ClassNode child : node.nested) {
			processClass(root, child);
		}
		
	}
	
	private void setLambdaVars(ClassNode parent, ClassNode child) {

		final MethodWrapper meth = parent.wrapper.getMethods().getWithKey(child.lambda_information.content_method_key);
		final MethodWrapper encmeth = parent.wrapper.getMethods().getWithKey(child.enclosingMethod);

		MethodDescriptor md_lambda = MethodDescriptor.parseDescriptor(child.lambda_information.method_descriptor);
		final MethodDescriptor md_content = MethodDescriptor.parseDescriptor(child.lambda_information.content_method_descriptor);
		
		final int vars_count = md_content.params.length - md_lambda.params.length;
//		if(vars_count < 0) { // should not happen, but just in case...
//			vars_count = 0;
//		}
		
		final boolean is_static_lambda_content = child.lambda_information.is_content_method_static;
		
		final String parent_class_name = parent.wrapper.getClassStruct().qualifiedName;
		final String lambda_class_name = child.simpleName;
		
		final VarType lambda_class_type = new VarType(lambda_class_name, true);

		// this pointer
		if(!is_static_lambda_content && DecompilerContext.getOption(IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS)) {  
			meth.varproc.getThisvars().put(new VarVersionPaar(0, 0), parent_class_name);
			meth.varproc.setVarName(new VarVersionPaar(0, 0), parent.simpleName + ".this");
		}

		// local variables
		DirectGraph graph = encmeth.getOrBuildGraph();
		
		final HashMap<VarVersionPaar, String> mapNewNames = new HashMap<VarVersionPaar, String>();
		
		graph.iterateExprents(new DirectGraph.ExprentIterator() {
			public int processExprent(Exprent exprent) {
				
				List<Exprent> lst = exprent.getAllExprents(true);
				lst.add(exprent);
				
				for(Exprent expr: lst) {
					
					if(expr.type == Exprent.EXPRENT_NEW) {
						NewExprent new_expr = (NewExprent)expr;
						if(new_expr.isLambda() && lambda_class_type.equals(new_expr.getNewtype())) {
							
							InvocationExprent inv_dynamic = new_expr.getConstructor(); 
							
							int param_index = is_static_lambda_content ? 0 : 1;;
							int varindex = is_static_lambda_content ? 0 : 1;
							
							for(int i = 0; i < vars_count; ++i) {
							
								Exprent param = inv_dynamic.getLstParameters().get(param_index + i);
								
								if(param.type == Exprent.EXPRENT_VAR) {
									VarVersionPaar enc_varpaar = new VarVersionPaar((VarExprent)param);
									String enc_varname = encmeth.varproc.getVarName(enc_varpaar);
									
									//meth.varproc.setVarName(new VarVersionPaar(varindex, 0), enc_varname);
									mapNewNames.put(new VarVersionPaar(varindex, 0), enc_varname);
								}
								
								varindex+=md_content.params[i].stack_size;
							}
							
						}
					}
				}
				
				return 0;
			}
		});
	
		// update names of local variables
		HashSet<String> setNewOuterNames = new HashSet<String>(mapNewNames.values());
		setNewOuterNames.removeAll(meth.setOuterVarNames);
		
		meth.varproc.refreshVarNames(new VarNamesCollector(setNewOuterNames));
		meth.setOuterVarNames.addAll(setNewOuterNames);
		
		for(Entry<VarVersionPaar, String> entr : mapNewNames.entrySet()) {
			meth.varproc.setVarName(entr.getKey(), entr.getValue());
		}
		
	}
	
	private void checkNotFoundClasses(ClassNode root, ClassNode node) {
		
		List<ClassNode> lstChildren = new ArrayList<ClassNode>(node.nested);
		
		for(ClassNode child : lstChildren) {
			
			if((child.type == ClassNode.CLASS_LOCAL || child.type == ClassNode.CLASS_ANONYMOUS) && child.enclosingMethod == null) {

				Set<String> setEnclosing = child.enclosingClasses;
				
				if(setEnclosing.size() == 1) {
					StructEnclosingMethodAttribute attr = (StructEnclosingMethodAttribute)child.classStruct.getAttributes().getWithKey("EnclosingMethod");
					if(attr != null && attr.getMethodName() != null) {
						if(node.classStruct.qualifiedName.equals(attr.getClassname()) && 
								node.classStruct.getMethod(attr.getMethodName(), attr.getMethodDescriptor()) != null) {
							child.enclosingMethod = InterpreterUtil.makeUniqueKey(attr.getMethodName(), attr.getMethodDescriptor());
							continue;
						}
					}
				}
				
				node.nested.remove(child);
				child.parent = null;
				setEnclosing.remove(node.classStruct.qualifiedName);
				
				boolean hasEnclosing = !setEnclosing.isEmpty(); 
				if(hasEnclosing) {
					hasEnclosing = insertNestedClass(root, child);
				}
				
				if(!hasEnclosing) {
					if(child.type == ClassNode.CLASS_ANONYMOUS) {
						DecompilerContext.getLogger().writeMessage("Unreferenced anonymous class "+child.classStruct.qualifiedName+"!", IFernflowerLogger.WARNING);
					} else if(child.type == ClassNode.CLASS_LOCAL) {
						DecompilerContext.getLogger().writeMessage("Unreferenced local class "+child.classStruct.qualifiedName+"!", IFernflowerLogger.WARNING);
					}
				}
			}
		}
	}
	
	private boolean insertNestedClass(ClassNode root, ClassNode child) {
		
		Set<String> setEnclosing = child.enclosingClasses;
		
		LinkedList<ClassNode> stack = new LinkedList<ClassNode>();
		stack.add(root);
		
		while(!stack.isEmpty()) {
			
			ClassNode node = stack.removeFirst();

			if(setEnclosing.contains(node.classStruct.qualifiedName)) {
				node.nested.add(child);
				child.parent = node;

				return true;
			}
			
			// note: ordered list
			stack.addAll(node.nested);
		}
		
		return false;
	}
	
	
	private void computeLocalVarsAndDefinitions(final ClassNode node) {
		
		// local var masks
		// class name, constructor descriptor, field mask
		final HashMap<String, HashMap<String, List<VarFieldPair>>> mapVarMasks = new HashMap<String, HashMap<String, List<VarFieldPair>>>(); 
		
		int cltypes = 0;
		
		for(ClassNode nd: node.nested) {
			if(nd.type != ClassNode.CLASS_LAMBDA) {
				if((nd.access & CodeConstants.ACC_STATIC) == 0 && (nd.access & CodeConstants.ACC_INTERFACE) == 0) {
					
					cltypes |= nd.type;
	
					HashMap<String, List<VarFieldPair>> mask = getMaskLocalVars(nd.wrapper);
					if(mask.isEmpty()) {
						DecompilerContext.getLogger().writeMessage("Nested class "+nd.classStruct.qualifiedName+" has no constructor!", IFernflowerLogger.WARNING);
					} else {
						mapVarMasks.put(nd.classStruct.qualifiedName, mask);
					}
				}
			}
		}

		// local var masks
		final HashMap<String, HashMap<String, List<VarFieldPair>>> mapVarFieldPairs = new HashMap<String, HashMap<String, List<VarFieldPair>>>(); 
		
		if(cltypes != ClassNode.CLASS_MEMBER) {
			
			// iterate enclosing class
			for(final MethodWrapper meth: node.wrapper.getMethods()) {
				
				if(meth.root != null) { // neither abstract, nor native
					DirectGraph graph = meth.getOrBuildGraph();
					
					graph.iterateExprents(new DirectGraph.ExprentIterator() {
						public int processExprent(Exprent exprent) {
							List<Exprent> lst = exprent.getAllExprents(true);
							lst.add(exprent);
							
							for(Exprent expr: lst) {
								
								if(expr.type == Exprent.EXPRENT_NEW) {
									InvocationExprent constr = ((NewExprent)expr).getConstructor();
									
									if(constr != null && mapVarMasks.containsKey(constr.getClassname())) { // non-static inner class constructor
										
										String refclname = constr.getClassname();
										
										ClassNode nestedClassNode = node.getClassNode(refclname);
										
										if(nestedClassNode.type != ClassNode.CLASS_MEMBER) {
											
											List<VarFieldPair> mask = mapVarMasks.get(refclname).get(constr.getStringDescriptor());
											
											if(!mapVarFieldPairs.containsKey(refclname)) {
												mapVarFieldPairs.put(refclname, new HashMap<String, List<VarFieldPair>>());
											}
											
											List<VarFieldPair> lstTemp = new ArrayList<VarFieldPair>();
											
											for(int i=0;i<mask.size();i++) {
												Exprent param = constr.getLstParameters().get(i);
												VarFieldPair pair = null;
												
												if(param.type == Exprent.EXPRENT_VAR && mask.get(i) != null) {
													VarVersionPaar varpaar = new VarVersionPaar((VarExprent)param);
													
													// FIXME: final flags of variables are wrong! Correct the entire final functionality.
//													if(meth.varproc.getVarFinal(varpaar) != VarTypeProcessor.VAR_NONFINAL) {
													pair = new VarFieldPair(mask.get(i).keyfield, varpaar);
//													}
												}
												
												lstTemp.add(pair);
											}
											
											List<VarFieldPair> pairmask = mapVarFieldPairs.get(refclname).get(constr.getStringDescriptor());
											
											if(pairmask == null) {
												pairmask = lstTemp;
											} else {
												for(int i=0;i<pairmask.size();i++) {
													if(!InterpreterUtil.equalObjects(pairmask.get(i), lstTemp.get(i))) {
														pairmask.set(i, null);
													}
												}
											}
											
											mapVarFieldPairs.get(refclname).put(constr.getStringDescriptor(), pairmask);
											nestedClassNode.enclosingMethod = InterpreterUtil.makeUniqueKey(meth.methodStruct.getName(), meth.methodStruct.getDescriptor());
										}
									}
								}
								
							}
							return 0;
						}
					});
				}
			}
		}
		
		// merge var masks
		for(Entry<String, HashMap<String, List<VarFieldPair>>> entcl : mapVarMasks.entrySet()) {

			ClassNode nestedNode = node.getClassNode(entcl.getKey());
			
			// intersection
			List<VarFieldPair> intrPairMask = null;
			// merge referenced constructors
			if(mapVarFieldPairs.containsKey(entcl.getKey())) {
				for(List<VarFieldPair> mask : mapVarFieldPairs.get(entcl.getKey()).values()) {
					if(intrPairMask == null) {
						intrPairMask = new ArrayList<VarFieldPair>(mask);
					} else {
						mergeListSignatures(intrPairMask, mask, false);
					}
				}
			}
			
			List<VarFieldPair> intrMask = null;
			// merge all constructors
			for(List<VarFieldPair> mask : entcl.getValue().values()) {
				if(intrMask == null) {
					intrMask = new ArrayList<VarFieldPair>(mask);
				} else {
					mergeListSignatures(intrMask, mask, false);
				}
			}
			
			if(intrPairMask == null) { // member or local and never instantiated 
				intrPairMask = new ArrayList<VarFieldPair>(intrMask);

				boolean found = false;
				
				for(int i=0;i<intrPairMask.size();i++) {
					if(intrPairMask.get(i) != null) {
						if(found) {
							intrPairMask.set(i, null);
						}
						found = true;
					}
				}
			}

			mergeListSignatures(intrPairMask, intrMask, true);
			
			for(int i=0;i<intrPairMask.size();i++) {
				VarFieldPair pair = intrPairMask.get(i);
				if(pair != null && pair.keyfield.length() > 0) {
					nestedNode.mapFieldsToVars.put(pair.keyfield, pair.varpaar);
				}
			}

			// set resulting constructor signatures
			for(Entry<String, List<VarFieldPair>> entmt : entcl.getValue().entrySet()) {
				mergeListSignatures(entmt.getValue(), intrPairMask, false);
				
				MethodWrapper meth = nestedNode.wrapper.getMethodWrapper("<init>", entmt.getKey());
				meth.signatureFields = new ArrayList<VarVersionPaar>();
				
				for(VarFieldPair pair : entmt.getValue()) {
					meth.signatureFields.add(pair==null?null:pair.varpaar);
				}
			}
			
		}
		
	}
	
	private void insertLocalVars(final ClassNode parent, final ClassNode child) {
		
		// enclosing method, is null iff member class
		MethodWrapper encmeth = parent.wrapper.getMethods().getWithKey(child.enclosingMethod);
		
		// iterate all child methods
		for(final MethodWrapper meth : child.wrapper.getMethods()) {
			
			if(meth.root != null) { // neither abstract nor native
				
				// local var names
				HashMap<VarVersionPaar, String> mapNewNames = new HashMap<VarVersionPaar, String>();
				// local var types
				HashMap<VarVersionPaar, VarType> mapNewTypes = new HashMap<VarVersionPaar, VarType>();
				
				final HashMap<Integer, VarVersionPaar> mapParamsToNewVars = new HashMap<Integer, VarVersionPaar>(); 
				if(meth.signatureFields != null) {
					int index = 0;
					int varindex = 1;
					MethodDescriptor md = MethodDescriptor.parseDescriptor(meth.methodStruct.getDescriptor());
					
					for(VarVersionPaar paar : meth.signatureFields) {
						if(paar != null) {
							VarVersionPaar newvar = new VarVersionPaar(meth.counter.getCounterAndIncrement(CounterContainer.VAR_COUNTER), 0);
							
							mapParamsToNewVars.put(varindex, newvar);
							
							String varname = null;
							VarType vartype = null;
							
							if(child.type != ClassNode.CLASS_MEMBER) {
								varname = encmeth.varproc.getVarName(paar);
								vartype = encmeth.varproc.getVarType(paar);
								
								encmeth.varproc.setVarFinal(paar, VarTypeProcessor.VAR_FINALEXPLICIT);
							}
							
							if(paar.var == -1 || "this".equals(varname)) {
								if(parent.simpleName == null) { 
									// anonymous enclosing class, no access to this
									varname = VarExprent.VAR_NAMELESS_ENCLOSURE;
								} else {
									varname = parent.simpleName+".this";
								}
								meth.varproc.getThisvars().put(newvar, parent.classStruct.qualifiedName);
							}
							
							mapNewNames.put(newvar, varname);
							mapNewTypes.put(newvar, vartype);
						}
						varindex+=md.params[index++].stack_size;
					}
				}
				
				// new vars
				final HashMap<String, VarVersionPaar> mapFieldsToNewVars = new HashMap<String, VarVersionPaar>();
				
				for(ClassNode clnode = child; clnode != null; clnode = clnode.parent) {
					
					for(Entry<String, VarVersionPaar> entr : clnode.mapFieldsToVars.entrySet()) {
						VarVersionPaar newvar = new VarVersionPaar(meth.counter.getCounterAndIncrement(CounterContainer.VAR_COUNTER), 0);
						
						mapFieldsToNewVars.put(InterpreterUtil.makeUniqueKey(clnode.classStruct.qualifiedName, entr.getKey()), newvar);
						
						String varname = null;
						VarType vartype = null;
						
						if(clnode.type != ClassNode.CLASS_MEMBER) {
							
							MethodWrapper enclosing_method = clnode.parent.wrapper.getMethods().getWithKey(clnode.enclosingMethod);
							
							varname = enclosing_method.varproc.getVarName(entr.getValue());
							vartype = enclosing_method.varproc.getVarType(entr.getValue());
							
							enclosing_method.varproc.setVarFinal(entr.getValue(), VarTypeProcessor.VAR_FINALEXPLICIT);
						}
						
						if(entr.getValue().var == -1 || "this".equals(varname)) {
							if(clnode.parent.simpleName == null) { 
								// anonymous enclosing class, no access to this
								varname = VarExprent.VAR_NAMELESS_ENCLOSURE;
							} else {
								varname = clnode.parent.simpleName+".this";
							}
							meth.varproc.getThisvars().put(newvar, clnode.parent.classStruct.qualifiedName);
						}
						
						mapNewNames.put(newvar, varname);
						mapNewTypes.put(newvar, vartype);
						
						// hide synthetic field
						if(clnode == child) { // fields higher up the chain were already handled with their classes
							StructField fd = child.classStruct.getFields().getWithKey(entr.getKey());
							child.wrapper.getHideMembers().add(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()));
						}
					}
				}

				HashSet<String> setNewOuterNames = new HashSet<String>(mapNewNames.values());
				setNewOuterNames.removeAll(meth.setOuterVarNames);
				
				meth.varproc.refreshVarNames(new VarNamesCollector(setNewOuterNames));
				meth.setOuterVarNames.addAll(setNewOuterNames);
				
				for(Entry<VarVersionPaar, String> entr : mapNewNames.entrySet()) {
					VarVersionPaar varpaar = entr.getKey();
					VarType vartype = mapNewTypes.get(varpaar);

					meth.varproc.setVarName(varpaar, entr.getValue());
					if(vartype != null) {
						meth.varproc.setVarType(varpaar, vartype);	
					}
				}
				
				DirectGraph graph = meth.getOrBuildGraph();
				
				graph.iterateExprents(new DirectGraph.ExprentIterator() {
					public int processExprent(Exprent exprent) {
						
						if(exprent.type == Exprent.EXPRENT_ASSIGNMENT) {
							AssignmentExprent asexpr = (AssignmentExprent)exprent;
							if(asexpr.getLeft().type == Exprent.EXPRENT_FIELD) {
								FieldExprent fexpr = (FieldExprent)asexpr.getLeft();
								
								if(fexpr.getClassname().equals(child.classStruct.qualifiedName) &&  // process this class only
														mapFieldsToNewVars.containsKey(InterpreterUtil.makeUniqueKey(child.classStruct.qualifiedName,
														InterpreterUtil.makeUniqueKey(fexpr.getName(), fexpr.getDescriptor().descriptorString)))) {
										return 2;
								}
								
								//if(fexpr.getClassname().equals(child.classStruct.qualifiedName) &&
								//		mapFieldsToNewVars.containsKey(InterpreterUtil.makeUniqueKey(fexpr.getName(), fexpr.getDescriptor().descriptorString))) {
								//	return 2;
								//}
							}
						} 
						
						if(child.type == ClassNode.CLASS_ANONYMOUS && "<init>".equals(meth.methodStruct.getName())
								&& exprent.type == Exprent.EXPRENT_INVOCATION) {
							InvocationExprent invexpr = (InvocationExprent)exprent;
							if(invexpr.getFunctype() == InvocationExprent.TYP_INIT) {
								// invocation of the super constructor in an anonymous class
								child.superInvocation = invexpr; // FIXME: save original names of parameters
								return 2;
							}
						}
						
						replaceExprent(exprent);
						
						return 0;
					}
					
					private Exprent replaceExprent(Exprent exprent) {
						
						if(exprent.type == Exprent.EXPRENT_VAR) {
							int varindex = ((VarExprent)exprent).getIndex();
							if(mapParamsToNewVars.containsKey(varindex)) {
								VarVersionPaar newvar = mapParamsToNewVars.get(varindex);
								meth.varproc.getExternvars().add(newvar);
								return new VarExprent(newvar.var, meth.varproc.getVarType(newvar), meth.varproc);
							}
						} else if(exprent.type == Exprent.EXPRENT_FIELD) {
							FieldExprent fexpr = (FieldExprent)exprent;
							
							String keyField = InterpreterUtil.makeUniqueKey(fexpr.getClassname(), InterpreterUtil.makeUniqueKey(fexpr.getName(), fexpr.getDescriptor().descriptorString));
							
							if(mapFieldsToNewVars.containsKey(keyField)) {
							//if(fexpr.getClassname().equals(child.classStruct.qualifiedName) &&
							//		mapFieldsToNewVars.containsKey(keyField)) {
								VarVersionPaar newvar = mapFieldsToNewVars.get(keyField);
								meth.varproc.getExternvars().add(newvar);
								return new VarExprent(newvar.var, meth.varproc.getVarType(newvar), meth.varproc);
							}
						} 
						
						boolean replaced = true;
						while(replaced) {
							replaced = false;
							
							for(Exprent expr: exprent.getAllExprents()) {
								Exprent retexpr = replaceExprent(expr);
								if(retexpr != null) {
									exprent.replaceExprent(expr, retexpr);
									replaced = true;
									break;
								}
							}
						}
						
						return null;
					}
				});
				
			}
		}
		
	}
	
	private HashMap<String, List<VarFieldPair>> getMaskLocalVars(ClassWrapper wrapper) {
		
		HashMap<String, List<VarFieldPair>> mapMasks = new HashMap<String, List<VarFieldPair>>();
		
		StructClass cl = wrapper.getClassStruct();
		
		// iterate over constructors
		for(StructMethod mt: cl.getMethods()) {
			if("<init>".equals(mt.getName())) {
				
				MethodDescriptor md = MethodDescriptor.parseDescriptor(mt.getDescriptor());

				MethodWrapper meth = wrapper.getMethodWrapper("<init>", mt.getDescriptor());
				DirectGraph graph = meth.getOrBuildGraph();

				if(graph != null) { // something gone wrong, should not be null
					List<VarFieldPair> fields = new ArrayList<VarFieldPair>();
	
					int varindex = 1;
					for(int i=0;i<md.params.length;i++) {  // no static methods allowed
						String keyField = getEnclosingVarField(cl, meth, graph, varindex);
						fields.add(keyField==null?null:new VarFieldPair(keyField, new VarVersionPaar(-1, 0))); // TODO: null?
						varindex+=md.params[i].stack_size;
					}
					mapMasks.put(mt.getDescriptor(), fields);
				}
			}
		}
		
		return mapMasks;
	}
	
	private String getEnclosingVarField(StructClass cl, MethodWrapper meth, DirectGraph graph, final int index) {
		
		String field = "";

		// parameter variable final
		if(meth.varproc.getVarFinal(new VarVersionPaar(index, 0)) == VarTypeProcessor.VAR_NONFINAL) {
			return null;
		}
		
		boolean notsynth = DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET);
		
		// no loop at the begin 
		DirectNode firstnode = graph.first;
		if(firstnode.preds.isEmpty()) {
			// assignment to a final synthetic field?
			for(Exprent exprent: firstnode.exprents) {
				if(exprent.type == Exprent.EXPRENT_ASSIGNMENT) {
					AssignmentExprent asexpr = (AssignmentExprent)exprent;
					if(asexpr.getRight().type == Exprent.EXPRENT_VAR && ((VarExprent)asexpr.getRight()).getIndex() == index) {
						if(asexpr.getLeft().type == Exprent.EXPRENT_FIELD) {

							FieldExprent left = (FieldExprent)asexpr.getLeft();
							StructField fd = cl.getField(left.getName(), left.getDescriptor().descriptorString);
							
							if(fd != null) { // local (== not inherited) field
								if(cl.qualifiedName.equals(left.getClassname()) && 
										(fd.access_flags & CodeConstants.ACC_FINAL) != 0 &&
										((fd.access_flags & CodeConstants.ACC_SYNTHETIC) != 0 ||
										 fd.getAttributes().containsKey("Synthetic") || 
										 (notsynth && (fd.access_flags & CodeConstants.ACC_PRIVATE) != 0))) {
									field = InterpreterUtil.makeUniqueKey(left.getName(), left.getDescriptor().descriptorString);
									break;
								}
							}
						}
					}
				}
			}
		}
		
		return field;
	}
	
	private void mergeListSignatures(List<VarFieldPair> first, List<VarFieldPair> second, boolean both) {

		int i=1;
		for(;;) {
			if(first.size() <= i || second.size() <= i) {
				break;
			}
			
			VarFieldPair fobj = first.get(first.size() - i);
			VarFieldPair sobj = second.get(second.size() - i);
			
			boolean eq = false;
			if(fobj == null || sobj == null) {
				eq = (fobj == sobj); 
			} else {
				eq = true;
				if(fobj.keyfield.length()==0) {
					fobj.keyfield = sobj.keyfield;
				} else if(sobj.keyfield.length() == 0) {
					if(both) {
						sobj.keyfield = fobj.keyfield;
					}
				} else {
					eq = fobj.keyfield.equals(sobj.keyfield);
				}
			}
			
			if(!eq) {
				first.set(first.size() - i, null);
				if(both) {
					second.set(second.size() - i, null);
				}
			} else {
				if(fobj != null) {
					if(fobj.varpaar.var == -1) {
						fobj.varpaar = sobj.varpaar;
					} else {
						sobj.varpaar = fobj.varpaar; 
					}
				}
			}
			i++;
		}
		
		for(int j=1;j<=first.size()-i;j++) {
			first.set(j, null);
		}
		
		if(both) {
			for(int j=1;j<=second.size()-i;j++) {
				second.set(j, null);
			}
		}
		
		// first
		if(first.isEmpty()) {
			if(!second.isEmpty() && both) {
				second.set(0, null);
			}
		} else if(second.isEmpty()) {
			first.set(0, null);
		} else {
			VarFieldPair fobj = first.get(0);
			VarFieldPair sobj = second.get(0);
			
			boolean eq = false;
			if(fobj == null || sobj == null) {
				eq = (fobj == sobj); 
			} else {
				eq = true;
				if(fobj.keyfield.length()==0) {
					fobj.keyfield = sobj.keyfield;
				} else if(sobj.keyfield.length() == 0) {
					if(both) {
						sobj.keyfield = fobj.keyfield;
					}
				} else {
					eq = fobj.keyfield.equals(sobj.keyfield);
				}
			}
			
			if(!eq) {
				first.set(0, null);
				if(both) {
					second.set(0, null);
				}
			} else if(fobj != null) {
				if(fobj.varpaar.var == -1) {
					fobj.varpaar = sobj.varpaar;
				} else {
					sobj.varpaar = fobj.varpaar; 
				}
			}
		}
		
	}
	
	
	private void setLocalClassDefinition(MethodWrapper meth, ClassNode node) {
		
		RootStatement root = meth.root;
		
		HashSet<Statement> setStats = new HashSet<Statement>();
		VarType classtype = new VarType(node.classStruct.qualifiedName, true);
		
		Statement stdef = getDefStatement(root, classtype, setStats); 
		if(stdef == null) {
			// unreferenced local class
			stdef = root.getFirst();
		}
		
		Statement first = findFirstBlock(stdef, setStats);
		
		List<Exprent> lst;
		if(first == null) {
			lst = stdef.getVarDefinitions();
		} else if(first.getExprents() == null) {
			lst = first.getVarDefinitions();
		} else {
			lst = first.getExprents();
		}
		
		
		int addindex = 0;
		for(Exprent expr: lst) {
			if(searchForClass(expr, classtype)) {
				break;
			}
			addindex++;
		}
		
		VarExprent var = new VarExprent(meth.counter.getCounterAndIncrement(CounterContainer.VAR_COUNTER), 
				classtype, meth.varproc);
		var.setDefinition(true);
		var.setClassdef(true);
		
		lst.add(addindex, var);
		
	}
	
	
	
	private Statement findFirstBlock(Statement stat, HashSet<Statement> setStats) {

		LinkedList<Statement> stack = new LinkedList<Statement>();
		stack.add(stat);
		
		while(!stack.isEmpty()) {
			Statement st = stack.remove(0);
			
			if(stack.isEmpty() || setStats.contains(st)) {
				
				if(st.isLabeled() && !stack.isEmpty()) {
					return st;
				}
					
				if(st.getExprents() != null) {
					return st;
				} else {
					stack.clear();
					
					switch(st.type) {
					case Statement.TYPE_SEQUENCE:
						stack.addAll(0, st.getStats());
						break;
					case Statement.TYPE_IF:
					case Statement.TYPE_ROOT:
					case Statement.TYPE_SWITCH:
					case Statement.TYPE_SYNCRONIZED:
						stack.add(st.getFirst());
						break;
					default:
						return st;
					}
				}
			}
		}
		
		return null;
	}
	
	
	private Statement getDefStatement(Statement stat, VarType classtype, HashSet<Statement> setStats) {
		
		List<Exprent> condlst = new ArrayList<Exprent>();
		Statement retstat = null;
		
		if(stat.getExprents() == null) {
			int counter = 0;

			for(Object obj: stat.getSequentialObjects()) {
				if(obj instanceof Statement) {
					Statement st = (Statement)obj;
					
					Statement stTemp = getDefStatement(st, classtype, setStats);
					
					if(stTemp != null) {
						if(counter == 1) {
							retstat = stat;
							break;
						}
						retstat = stTemp;
						counter++;
					}
					
					if(st.type == DoStatement.TYPE_DO) {
						DoStatement dost = (DoStatement)st;
						
						condlst.addAll(dost.getInitExprentList());
						condlst.addAll(dost.getConditionExprentList());
					}
					
				} else if(obj instanceof Exprent) {
					condlst.add((Exprent)obj);
				}
			}	
		} else {
			condlst = stat.getExprents(); 
		}
		
		if(retstat != stat) {
			for(Exprent exprent : condlst) {
				if(exprent!=null && searchForClass(exprent, classtype)) {
					retstat = stat;
					break;
				}
			}
		}
		
		if(retstat != null) {
			setStats.add(stat);
		}
		
		return retstat;
	}
	
	private boolean searchForClass(Exprent exprent, VarType classtype) {
		
		List<Exprent> lst = exprent.getAllExprents(true);
		lst.add(exprent);

		String classname = classtype.value;
		
		for(Exprent expr : lst) {
			
			boolean res = false;
			
			switch(expr.type) {
			case Exprent.EXPRENT_CONST:
				ConstExprent cexpr = (ConstExprent)expr;
				res = (VarType.VARTYPE_CLASS.equals(cexpr.getConsttype()) && classname.equals(cexpr.getValue()) ||
							classtype.equals(cexpr.getConsttype()));
				break;
			case Exprent.EXPRENT_FIELD:
				res = classname.equals(((FieldExprent)expr).getClassname());
				break;
			case Exprent.EXPRENT_INVOCATION:
				res = classname.equals(((InvocationExprent)expr).getClassname());
				break;
			case Exprent.EXPRENT_NEW:
				VarType newType = expr.getExprType();
				res = newType.type == CodeConstants.TYPE_OBJECT && classname.equals(newType.value);
				break;
			case Exprent.EXPRENT_VAR:
				VarExprent vexpr = (VarExprent)expr;
				if(vexpr.isDefinition()) {
					VarType vtype = vexpr.getVartype();
					if(classtype.equals(vtype) || (vtype.arraydim > 0 && classtype.value.equals(vtype.value))) {
						res = true;
					}
				}
			}
			
			if(res) {
				return true;
			}
		}
		
		return false;
	}
	
	
	private class VarFieldPair {
		
		public String keyfield = "";
		public VarVersionPaar varpaar;
		
		public VarFieldPair(String field, VarVersionPaar varpaar) {
			this.keyfield = field;
			this.varpaar = varpaar;
		}

		@Override
		public boolean equals(Object o) {
			if(o == this) return true;
			if(o == null || !(o instanceof VarFieldPair)) return false;

			VarFieldPair pair = (VarFieldPair)o;
			return keyfield.equals(pair.keyfield) && varpaar.equals(pair.varpaar);
		}

		@Override
		public int hashCode() {
			return keyfield.hashCode()+varpaar.hashCode();
		}
		
	}
	
}
