package org.codemucker.jmutate.generate.bean;

import java.lang.annotation.Annotation;

import org.apache.commons.configuration.Configuration;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.codemucker.jmutate.ClashStrategyResolver;
import org.codemucker.jmutate.JMutateContext;
import org.codemucker.jmutate.SourceTemplate;
import org.codemucker.jmutate.ast.JMethod;
import org.codemucker.jmutate.ast.JSourceFile;
import org.codemucker.jmutate.ast.JType;
import org.codemucker.jmutate.generate.AbstractCodeGenerator;
import org.codemucker.jmutate.generate.CodeGenMetaGenerator;
import org.codemucker.jmutate.generate.pojo.PojoModel;
import org.codemucker.jmutate.generate.pojo.PojoProperty;
import org.codemucker.jmutate.generate.pojo.PropertiesExtractor;
import org.codemucker.jmutate.transform.CleanImportsTransform;
import org.codemucker.jmutate.transform.InsertFieldTransform;
import org.codemucker.jmutate.transform.InsertMethodTransform;
import org.codemucker.jpattern.generate.ClashStrategy;
import org.codemucker.jpattern.generate.GenerateBean;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;

import com.google.inject.Inject;

public abstract class AbstractBeanGenerator<T extends Annotation> extends AbstractCodeGenerator<T>  {

	private static final Logger LOG = LogManager.getLogger(AbstractBeanGenerator.class);

	private final CodeGenMetaGenerator generatorMeta;
	private final JMutateContext ctxt;
	private ClashStrategyResolver methodClashResolver;

	@Inject
	public AbstractBeanGenerator(JMutateContext ctxt) {
		this.ctxt = ctxt;
		this.generatorMeta = new CodeGenMetaGenerator(ctxt, getClass());
	}
	
	
	@Override
	public void beforeRun() {
		//TODO:find all artifacts generated by this generator
	}


	@Override
	public void afterRun() {
		//TODO:remove all artifacts belonging to this generator which were not regenerated
	}

	protected JMutateContext getCtxt(){
		return ctxt;
	}

	@Override
	public void generate(JType optionsDeclaredInNode, Configuration config) {
		if(optionsDeclaredInNode.isInterface()){
			LOG.warn("the " + GenerateBean.class.getName() + " generation annotation on an interface is not supported");
			return;
		}
		BeanModel model = new BeanModel(optionsDeclaredInNode,config);
		if(!model.options.isEnabled()){
			LOG.info("generator annotation marked as disabled, not running generation");
			return;
		}
		ClashStrategy methodClashDefaultStrategy = model.options.getClashStrategy();
		methodClashResolver = new OnlyReplaceMyManagedMethodsResolver(methodClashDefaultStrategy);
		
		addPropertiesFrom(optionsDeclaredInNode,model);
		
		LOG.debug("found " + model.getProperties().size() + " bean properties for "  + model.options.getType().getFullNameRaw());
		
		JSourceFile source = optionsDeclaredInNode.getSource();
		generate(optionsDeclaredInNode, model);	
		writeToDiskIfChanged(source);	
	}
	
    private void addPropertiesFrom(JType node, BeanModel model){
    	PropertiesExtractor extrctor = PropertiesExtractor.with(ctxt.getResourceLoader(), ctxt.getParser())
    		.includeCompiledClasses(true)
    		.propertyNameMatching(model.options.getFieldNames())
    		.includeSuperClass(model.options.isInheritSuperClassProperties())
    		.build();
    		
    	PojoModel pojo = extrctor.extractProperties(node);

    	LOG.debug("adding properties to " + model.options.getType().getFullName());
    		
		boolean fromSuper = false;
		while(pojo != null){	
			for(PojoProperty p:pojo.getDeclaredProperties()){
				boolean readonly = model.options.isMakeReadonly() || p.isFinalField();
				BeanPropertyModel p2 = new BeanPropertyModel(model, p, !readonly, true, model.options.isBindable(), model.options.isVetoable(), fromSuper);
				model.addProperty(p2);
			}
			pojo = pojo.getParent();
			fromSuper = true;
		}
    }
	
	protected abstract void generate(JType bean, BeanModel model);
	
	protected void addField(JType type, FieldDeclaration f, boolean markGenerated) {
		if(markGenerated){
			generatorMeta.addGeneratedMarkers(f);
		}
		ctxt.obtain(InsertFieldTransform.class)
				.clashStrategy(ClashStrategy.REPLACE)
				.target(type)
				.field(f)
				.transform();
		
	}
	
	protected void addMethod(JType type, MethodDeclaration m, boolean markGenerated) {
		if(markGenerated){
			generatorMeta.addGeneratedMarkers(m);
		}
		ctxt.obtain(InsertMethodTransform.class)
				.clashStrategy(methodClashResolver)
				.target(type)
				.method(m)
				.transform();
	}

	private void writeToDiskIfChanged(JSourceFile source) {
		if (source != null) {
			cleanupImports(source.getAstNode());
			source = source.asMutator(ctxt).writeModificationsToDisk();
		}
	}

	protected void cleanupImports(ASTNode node) {
		ctxt.obtain(CleanImportsTransform.class).addMissingImports(true)
				.nodeToClean(node).transform();
	}
	
	protected SourceTemplate newSourceTemplate(){
		return ctxt
			.newSourceTemplate();
	}

	private class OnlyReplaceMyManagedMethodsResolver implements
			ClashStrategyResolver {

		private final ClashStrategy fallbackStrategy;

		public OnlyReplaceMyManagedMethodsResolver(
				ClashStrategy fallbackStrategy) {
			super();
			this.fallbackStrategy = fallbackStrategy;
		}

		@Override
		public ClashStrategy resolveClash(ASTNode existingNode, ASTNode newNode) {
			if (generatorMeta.isManagedByThis(JMethod.from(existingNode).getAnnotations())) {
				return ClashStrategy.REPLACE;
			}
			return fallbackStrategy;
		}

	}

}
@@ -1028,62 +1028,8 @@
 gy;%0A
-import org.codemucker.jpattern.generate.GenerateBean;%0A
 impo
@@ -1685,18 +1685,16 @@
 );%0A%09%7D%0A%09%0A
-%09%0A
 %09@Overri
@@ -2120,26 +2120,34 @@
 %22 + 
-GenerateBean.c
+getAnnotation().getC
 lass
+()
 .get

use genrators actual annotation in error mesages