/**
 * License Agreement.
 *
 * Rich Faces - Natural Ajax for Java Server Faces (JSF)
 *
 * Copyright (C) 2007 Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package org.ajax4jsf.builder.mojo;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.project.MavenProject;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.codehaus.plexus.velocity.VelocityComponent;

/**
 * @author shura
 *
 */
public abstract class AbstractCDKMojo extends AbstractMojo {

	/**
	 * Top maven project.
	 * 
	 * @parameter expression="${project}"
	 * @readonly
	 */
	protected MavenProject project;
	/**
	 * 
	 * @parameter
	 */
	protected String key;
	/**
	 * Place for component configuration XML files. All '*.xml' files wil be
	 * parsed as components config. All '*.ent' files will be processed as
	 * include configurations.
	 * 
	 * @parameter expression="src/main/config/component"
	 */
	protected File componentConfigDirectory;
	/**
	 * Place for validator configuration XML files. All '*.xml' files wil be
	 * parsed as component config. All '*.ent' files will be processed as
	 * include configurations.
	 * 
	 * @parameter expression="src/main/config/validator"
	 */
	protected File validatorConfigDirectory;

	/**
	 * Place for converter configuration XML files. All '*.xml' files wil be
	 * parsed as components config. All '*.ent' files will be processed as
	 * include configurations.
	 * 
	 * @parameter expression="src/main/config/converter"
	 */
	protected File converterConfigDirectory;

	/**
	 * Place for faces configuration XML files
	 * 
	 * @parameter expression="src/main/config/faces"
	 */
	protected File facesConfigInclude;
	/**
	 * Place for component configuration XML files
	 * 
	 * @parameter expression="src/main/config/taglib"
	 */
	protected File taglibInclude;
	/**
	 * Place for component configuration XML files
	 * 
	 * @parameter expression="src/main/config/resources"
	 */
	protected File resourcesInclude;
	/**
	 * 
	 * @parameter expression="src/main/templates"
	 */
	protected File templatesDirectory;
	/**
	 * Directory where the output Java Files will be located.
	 * 
	 * @parameter expression="${project.build.directory}/generated-component/java"
	 */
	protected File outputJavaDirectory;

	/**
	 * @parameter expression="${project.build.directory}/generated-component/test"
	 */
	protected File outputTestsDirectory;

	/**
	 * Directory where the output Java Files will be located.
	 * 
	 * @parameter expression="${project.build.directory}/generated-component/resources"
	 */
	protected File outputResourcesDirectory;
	/**
	 * @parameter
	 */
	protected Library library;
	/**
	 * The source directories containing the sources to be compiled.
	 * 
	 * @parameter expression="${project.compileSourceRoots}"
	 * @required
	 * @readonly
	 */
	protected List compileSourceRoots;
	/**
	 * Project classpath.
	 * 
	 * @parameter expression="${project.compileClasspathElements}"
	 * @required
	 * @readonly
	 */
	protected List classpathElements;
	/**
	 * The directory for compiled classes.
	 * 
	 * @parameter expression="${project.build.outputDirectory}"
	 * @required
	 * @readonly
	 */
	protected File outputDirectory;
	/**
	 * @component
	 */
	protected VelocityComponent velocity;
	/**
	 * Check library configuration, and fill all empty values to default.
	 * 
	 * @return
	 */
	protected Taglib checkLibraryConfig() {
		if (null != library) {
			getLog().debug("Library prefix is " + library.getPrefix());
		} else {
			library = new Library();
		}
		if (null == library.getPrefix()) {
			library.setPrefix(project.getGroupId());

		}
		getLog().debug("Default prefix for a generated packages: "+library.getPrefix());
		if (null == library.getDescription()) {
			library.setDescription(project.getDescription());	
		}
		getLog().debug("Library description: "+library.getDescription());
		if( null == library.getJsfVersion()){
			String version = Library.JSF11;
			// Check version-specific methods in UIComponent class
			try {
				Class<?> componentClass = createProjectClassLoader(project, false).loadClass("javax.faces.component.UIComponent");
				Method[] methods = componentClass.getDeclaredMethods();
				for (int i = 0; i < methods.length; i++) {
					if("encodeAll".equals(methods[i].getName())){
						version = Library.JSF12;
						break;
					}
				}
			} catch (ClassNotFoundException e) {
				// Ignore - by defaule, generate codes for JSF 1.1
			}
			library.setJsfVersion(version);
		}
		
		//velocity = new DefaultVelocityComponent();
		
		getLog().debug("Generate files for a JSF "+library.getJsfVersion());
		Renderkit[] renderkits = library.getRenderkits();
		if (null != renderkits) {
			for (int i = 0; i < renderkits.length; i++) {
				Renderkit renderkit = renderkits[i];
				getLog().debug("Renderkit name is " + renderkit.getName());
				if(null == renderkit.getPackage()){
					renderkit.setPackage(library.getPrefix()+".renderkit."+renderkit.getName().toLowerCase());
				}
			}
		} else {
			renderkits = new Renderkit[1];
			Renderkit renderkit = new Renderkit();
			renderkit.setMarkup("html");
			renderkit.setName("HTML_BASIC");
			renderkit.setPackage(library.getPrefix()+".renderkit."+renderkit.getName().toLowerCase());
			renderkits[0] = renderkit;
			library.setRenderkits(renderkits);
		}
		Taglib taglib = library.getTaglib();
		if (null != taglib) {
			getLog().debug("Taglib uri is " + taglib.getUri());
			getLog().debug("Taglib shortname is " + taglib.getShortName());
		} else {
			taglib = new Taglib();
			library.setTaglib(taglib);
		}
		if (null == taglib.getDisplayName()) {
			taglib.setDisplayName(project.getDescription());

		}
		if( null == taglib.getJspVersion()){
			// Jsf 1.2 can use JSP 2.1 only, other - 2.0
			taglib.setJspVersion(library.getJsfVersion().equals(Library.JSF12)?"2.1":"1.2");
		}
		if (null == taglib.getUri()) {
			String url = project.getUrl();
			if (null == url) {
				url = "http://";
				String[] parts = project.getGroupId().split(".");
				for (int i = parts.length - 1; i >= 0; i--) {
					url = url + parts[i];
					if (i > 0) {
						url = url + ".";
					}
				}
				url = url + "/" + project.getArtifactId();
			}
			taglib.setUri(url);

		}
		if (null == taglib.getShortName()) {
			taglib.setShortName(project.getArtifactId());

		}
		if(null == taglib.getTaglib()){
			taglib.setTaglib(taglib.getShortName());
		}
		
		if (null == taglib.getTlibVersion()) {
			taglib.setTlibVersion(createTaglibVersionFromProjectVersion());
		}
		getLog().debug("Taglib uri is " + taglib.getUri());
		getLog().debug("Taglib shortname is " + taglib.getShortName());
		if (null != library.getTaglibs() && library.getTaglibs().length > 0) {
			for (int i = 0; i < library.getTaglibs().length; i++) {
				Taglib t = library.getTaglibs()[i];
				checkTaglib(t);
			}
		}
		return taglib;
	}

	private String createTaglibVersionFromProjectVersion() {
		Artifact artifact = project.getArtifact();
		String version = artifact.getVersion();
		Matcher matcher = Pattern.compile("^(\\d+(?:\\.\\d+)*)").matcher(version);
		if (matcher.find()) {
			return matcher.group(1);
		}

		return "1.2";
	}

	protected void writeParsedTemplate(String templateName, VelocityContext context, File configFile) throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException, Exception {
		File dir = configFile.getParentFile();
		if(!dir.exists()){
			dir.mkdirs();
		}
		VelocityEngine engine = velocity.getEngine();
		FileWriter fileWriter = new FileWriter(configFile);
		Template velocityTemplate = engine.getTemplate(templateName);
		velocityTemplate.merge(context, fileWriter);
		fileWriter.flush();
		fileWriter.close();
	}

	protected ClassLoader createProjectClassLoader(MavenProject project, boolean useCCL) {
		ClassLoader classLoader = Thread.currentThread()
		.getContextClassLoader();
		try {
			List<?> compileClasspathElements = project
			.getCompileClasspathElements();
			String outputDirectory = project.getBuild().getOutputDirectory();
			URL[] urls = new URL[compileClasspathElements.size() + 1];
			int i = 0;
			urls[i++] = new File(outputDirectory).toURI().toURL();
			for (Iterator<?> iter = compileClasspathElements.iterator(); iter
			.hasNext();) {
				String element = (String) iter.next();
				urls[i++] = new File(element).toURI().toURL();
			}

			if (useCCL) {
				classLoader = new URLClassLoader(urls, classLoader);
			} else {
				classLoader = new URLClassLoader(urls);
			}
		} catch (MalformedURLException e) {
			getLog().error("Bad URL in classpath", e);
		} catch (DependencyResolutionRequiredException e) {
			getLog().error("Dependencies not resolved ", e);
		}

		return classLoader;
	}

	protected ClassLoader createProjectClassLoader(MavenProject project) {
		return createProjectClassLoader(project, true);
	}

	protected void checkTaglib(Taglib taglib) {
		if (null == taglib.getDisplayName()) {
			taglib.setDisplayName(library.getTaglib().getDisplayName());
		}
		if (null == taglib.getShortName()) {
			taglib.setShortName(library.getTaglib().getShortName());
		}
		if (null == taglib.getJspVersion()) {
			taglib.setJspVersion(library.getTaglib().getJspVersion());
		}
		if (null == taglib.getUri()) {
			taglib.setUri(library.getTaglib().getUri() + "/"
					+ taglib.getShortName());
		}
		if(null == taglib.getTaglib()){
			taglib.setTaglib(taglib.getShortName());
		}
		if (null == taglib.getTlibVersion()) {
			taglib.setTlibVersion(createTaglibVersionFromProjectVersion());
		}
		getLog().debug("Taglib uri is " + taglib.getUri());
		getLog().debug("Taglib shortname is " + taglib.getShortName());
		getLog().debug("Taglib version is " + taglib.getTlibVersion());
	}

}
