package org.objectstyle.wolips.thirdparty.velocity;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

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.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.FileResourceLoader;
import org.eclipse.core.runtime.IPath;
import org.objectstyle.wolips.thirdparty.velocity.resourceloader.ResourceLoader;
import org.osgi.framework.Bundle;

public class WOLipsVelocityUtils {

	/**
	 * Creates a configured VelocityEngine.
	 * 
	 * @param templateFamilyName
	 *            the name of the template family (the common folder name that
	 *            contains the templates)
	 * @param templateBundle
	 *            the bundle of the calling plugin
	 * @param customTemplatePath
	 *            the custom template path to load with
	 * @param projectPath
	 *            the project path to prepend if the customTemplatePath is
	 *            relative
	 * @param insideEclipse
	 *            if true, this engine should load resources from eclipse
	 *            bundles
	 * @param resourceLoaderClass
	 *            the custom resource loader if not running inside eclipse
	 * @return an initialized VelocityEngine
	 * @throws Exception
	 *             if velocity engine creation fails
	 */
	public static VelocityEngine createVelocityEngine(String templateFamilyName, Bundle templateBundle, String customTemplatePath, IPath projectPath, boolean insideEclipse, Class resourceLoaderClass) throws Exception {
		StringBuffer templatePaths = new StringBuffer();
		templatePaths.append(".");
		if (customTemplatePath != null && customTemplatePath.length() > 0) {
			templatePaths.append(",");
			File templateFolder = new File(customTemplatePath);
			if (!templateFolder.isAbsolute() && projectPath != null) {
				templateFolder = new File(projectPath.toFile(), customTemplatePath);
			}
			templatePaths.append(templateFolder.getAbsolutePath());
		}

		templatePaths.append(",");
		templatePaths.append(new File("/Library/Application Support/WOLips/" + templateFamilyName).getAbsolutePath());
		templatePaths.append(",");
		templatePaths.append(new File(System.getProperty("user.home"), "Documents and Settings/Application Data/WOLips/" + templateFamilyName).getAbsolutePath());
		templatePaths.append(",");
		templatePaths.append(new File(System.getProperty("user.home"), "Documents and Settings/AppData/Local/WOLips/" + templateFamilyName).getAbsolutePath());
		templatePaths.append(",");
		templatePaths.append(new File(System.getProperty("user.home"), "Library/Application Support/WOLips/" + templateFamilyName).getAbsolutePath());

		Thread thread = Thread.currentThread();
		ClassLoader loader = thread.getContextClassLoader();
		thread.setContextClassLoader(resourceLoaderClass.getClassLoader());
		try {
			VelocityEngine velocityEngine = new VelocityEngine();
			velocityEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, org.apache.velocity.runtime.log.NullLogSystem.class.getName());
			velocityEngine.setProperty("resource.loader", "file,wolips");
			velocityEngine.setProperty("file.resource.loader.class", FileResourceLoader.class.getName());
			velocityEngine.setProperty("file.resource.loader.path", templatePaths.toString());
			if (insideEclipse) {
				velocityEngine.setProperty("wolips.resource.loader.class", ResourceLoader.class.getName());
				if (templateBundle != null) {
					velocityEngine.setProperty("wolips.resource.loader.bundle", templateBundle);
				}
			} else if (resourceLoaderClass != null) {
				velocityEngine.setProperty("wolips.resource.loader.class", resourceLoaderClass.getName());
			}
			velocityEngine.init();
			return velocityEngine;
		} finally {
			thread.setContextClassLoader(loader);
		}
	}

	public static String writeTemplateToString(VelocityEngine engine, VelocityContext context, String templateName, ByteArrayOutputStream newFileContentsStream) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, Exception {
		Template template;
		try {
			template = engine.getTemplate(templateName, "UTF-8");
		} catch (ResourceNotFoundException e) {
			throw new Exception("Failed to load the template '" + templateName + "'.  Check your model's eogen file to make sure that it specifies the correct template folder and template names.");
		}

		Writer newFileContentsWriter = new BufferedWriter(new OutputStreamWriter(newFileContentsStream, "UTF-8"));
		try {
			template.merge(context, newFileContentsWriter);
		} finally {
			newFileContentsWriter.close();
		}
		String newFileContentsStr = newFileContentsStream.toString("UTF-8");
		if (newFileContentsStr != null) {
			if (newFileContentsStr.contains("<%")) {
				throw new IOException("You are attempting to use an old EOGenerator template with Velocity EOGenerator.");
			} else if (newFileContentsStr.contains("<wo:")) {
				throw new IOException("You are attempting to use a JavaEOGenerator template with Velocity EOGenerator.");
			}
		}
		return newFileContentsStr;
	}

	public static String writeTemplateToString(VelocityEngine engine, VelocityContext context, String templateName) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, Exception {
		return writeTemplateToString(engine, context, templateName, new ByteArrayOutputStream());
	}

	public static void writeTemplate(VelocityEngine engine, VelocityContext context, String templateName, OutputStream outputStream) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, Exception {
		ByteArrayOutputStream newFileContentsStream = new ByteArrayOutputStream();
		String newFileContentsStr = WOLipsVelocityUtils.writeTemplateToString(engine, context, templateName, newFileContentsStream);
		BufferedWriter newFileBufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
		try {
			newFileBufferedWriter.write(newFileContentsStr);
		} finally {
			newFileBufferedWriter.close();
		}
	}
	
	public static void writeTemplate(VelocityEngine engine, VelocityContext context, String templateName, File outputFile) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, Exception {
		ByteArrayOutputStream newFileContentsStream = new ByteArrayOutputStream();
		String newFileContentsStr = WOLipsVelocityUtils.writeTemplateToString(engine, context, templateName, newFileContentsStream);
		writeToFile(outputFile, newFileContentsStream, newFileContentsStr);
	}
	
	public static void writeTemplateToDirectory(VelocityEngine engine, VelocityContext context, String templateName, File outputDirectory) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, Exception {
		ByteArrayOutputStream newFileContentsStream = new ByteArrayOutputStream();
		String newFileContentsStr = WOLipsVelocityUtils.writeTemplateToString(engine, context, templateName, newFileContentsStream);
		int beginIndex = newFileContentsStr.indexOf(" class ") + 7;
		int endIndex = newFileContentsStr.substring(beginIndex).indexOf(" ") + beginIndex;
		String filename = newFileContentsStr.substring(beginIndex, endIndex);
		File outputFile = new File(outputDirectory, filename+".java");
		writeToFile(outputFile, newFileContentsStream, newFileContentsStr);
	}

	private static void writeToFile(File outputFile, ByteArrayOutputStream newFileContentsStream, String newFileContentsStr) throws IOException, UnsupportedEncodingException, FileNotFoundException, NoSuchAlgorithmException {
		if (!outputFile.getParentFile().exists()) {
			if (!outputFile.getParentFile().mkdirs()) {
				throw new IOException("Unable to create the folder " + outputFile.getParentFile() + ".");
			}
		}
		boolean templateChanged = true;
		if (!outputFile.exists()) {
			BufferedWriter newFileBufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "UTF-8"));
			try {
				newFileBufferedWriter.write(newFileContentsStr);
			} finally {
				newFileBufferedWriter.close();
			}
		} else {
			byte[] newFileContents = newFileContentsStream.toByteArray();
			if (outputFile.exists()) {
				FileInputStream fis = new FileInputStream(outputFile);
				int bytesRemaining = (int) outputFile.length();
				if (bytesRemaining == newFileContents.length) {
					byte[] oldFileContents;
					try {
						ByteArrayOutputStream bos = new ByteArrayOutputStream((int) outputFile.length());
						byte[] buf = new byte[4096];
						while (bytesRemaining > 0) {
							int bytesRead = fis.read(buf, 0, Math.min(buf.length, bytesRemaining));
							bos.write(buf, 0, bytesRead);
							bytesRemaining -= bytesRead;
						}
						oldFileContents = bos.toByteArray();
					} finally {
						fis.close();
					}

					MessageDigest md5 = MessageDigest.getInstance("MD5");
					byte[] oldMD5 = md5.digest(oldFileContents);
					md5.reset();
					byte[] newMD5 = md5.digest(newFileContents);
					md5.reset();

					if (oldMD5.length == newMD5.length) {
						templateChanged = false;
						for (int i = 0; !templateChanged && i < oldMD5.length; i++) {
							if (oldMD5[i] != newMD5[i]) {
								templateChanged = true;
							}
						}
					}
				}
			}

			if (templateChanged) {
				FileOutputStream newFileStream = new FileOutputStream(outputFile);
				BufferedOutputStream newFileBufferedStream = new BufferedOutputStream(newFileStream);
				try {
					newFileBufferedStream.write(newFileContents);
				} finally {
					newFileBufferedStream.close();
				}
			} else {
				// System.out.println("EOGenerator.writeTemplate: skipping " +
				// outputFile);
			}
		}
	}
}
