package jp.ac.kyutech.ai.ylab.shiva.nativecapable.plugin.wizards;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

import javax.xml.parsers.DocumentBuilderFactory;

import jp.ac.kyutech.ai.ylab.shiva.nativecapable.plugin.NCClasspathContainerInitializer;
import jp.ac.kyutech.ai.ylab.shiva.nativecapable.plugin.NativeCapableJavaPlugin;
import jp.ac.kyutech.ai.ylab.shiva.nativecapable.plugin.properties.NativeCapableJavaProjectPropertyPage;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchWizard;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * This is a sample new wizard. Its role is to create a new file resource in the
 * provided container. If the container resource (a folder or a project) is
 * selected in the workspace when the wizard is opened, it will accept it as the
 * target container. The wizard creates one file with the extension "mpe". If a
 * sample multi-page editor (also available as a template) is registered for the
 * same extension, it will be able to open it.
 */

public class NativeCapableProjectNewWizard extends Wizard implements INewWizard {
	private NativeCapableJavaProjectNewWizardPage page;
	private ISelection selection;

	/**
	 * Constructor for NyARToolkitProjectNewWizard.
	 */
	public NativeCapableProjectNewWizard() {
		super();
		setNeedsProgressMonitor(true);
	}

	/**
	 * Adding the page to the wizard.
	 */

	public void addPages() {
		page = new NativeCapableJavaProjectNewWizardPage(selection);
		addPage(page);
	}

	/**
	 * This method is called when 'Finish' button is pressed in the wizard. We
	 * will create an operation and run it using wizard as execution context.
	 */
	public boolean performFinish() {

		final String projectName = page.getProjectName();
		final String srcFolderName = page.getSrcFolderName();
		final String binFolderName = page.getBinFolderName();
		final String libFolderName = page.getLibFolderName();
		IRunnableWithProgress op = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor)
					throws InvocationTargetException {
				try {

					doFinish(projectName, srcFolderName, binFolderName,
							libFolderName, monitor);
				} catch (CoreException e) {
					throw new InvocationTargetException(e);
				} finally {
					monitor.done();
				}
			}
		};
		try {
			getContainer().run(true, false, op);
		} catch (InterruptedException e) {
			return false;
		} catch (InvocationTargetException e) {
			Throwable realException = e.getTargetException();
			MessageDialog.openError(getShell(), "Error", realException
					.getMessage());
			return false;
		}
		return true;
	}

	/**
	 * The worker method. It will find the container, create the file if missing
	 * or just replace its contents, and open the editor on the newly created
	 * file.
	 */

	private void doFinish(final String projectName, String srcFolderName,
			String binFolderName, String libDirName,
			final IProgressMonitor monitor) throws CoreException {

		// create a java project
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		IProject project = root.getProject(projectName);
		project.create(monitor);
		project.open(monitor);
		project.setDefaultCharset("UTF-8", monitor);

		// Adding Java nature to Project
		IProjectDescription description = project.getDescription();
		String[] natures = description.getNatureIds();
		String[] newNatures = new String[natures.length + 1];
		System.arraycopy(natures, 0, newNatures, 0, natures.length);
		newNatures[natures.length] = JavaCore.NATURE_ID;
		description.setNatureIds(newNatures);
		project.setDescription(description, monitor);

		// Adding NativeCapable Nature to Project
		String newNatureId = NativeCapableJavaPlugin.NATURE_ID;
		description = project.getDescription();
		if (!description.hasNature(newNatureId)) {
			String[] ids = description.getNatureIds();
			String[] newIds = new String[ids.length + 1];
			System.arraycopy(ids, 0, newIds, 0, ids.length);
			newIds[ids.length] = newNatureId;
			description.setNatureIds(newIds);
			project.setDescription(description, monitor);
		}
		System.out.println("NativeCapableProjectNewWizard#configure");
		// Creating Java Project
		final IJavaProject javaProject = JavaCore.create(project);

		// store values
		project.setPersistentProperty(new QualifiedName("",
				NativeCapableJavaProjectPropertyPage.SRC_DIR_PROPERTY),
				srcFolderName);
		project.setPersistentProperty(new QualifiedName("",
				NativeCapableJavaProjectPropertyPage.LIB_DIR_PROPERTY),
				libDirName);
		project.setPersistentProperty(new QualifiedName("",
				NativeCapableJavaProjectPropertyPage.BIN_DIR_PROPERTY),
				binFolderName);

		Set<IClasspathEntry> entries = new HashSet<IClasspathEntry>();
		// Adding source path
		IPath sourcePath = javaProject.getPath().append(srcFolderName);
		IFolder sourceDir = project.getFolder(new Path(srcFolderName));
		mkDirs(sourceDir, monitor);

		// Output path
		IPath outputPath = javaProject.getPath().append(binFolderName);
		IFolder outputDir = project.getFolder(new Path(binFolderName));
		mkDirs(outputDir, monitor);

		// source folder location, excluded nested folder, output location
		IClasspathEntry srcEntry = JavaCore.newSourceEntry(sourcePath,
				new IPath[] {}, outputPath);

		final IFolder libDir = project.getFolder(new Path(libDirName));
		mkDirs(libDir, monitor);

		// read os.xml
		String nativePath = "native";
		String osName = System.getProperty("os.name");
		String osArch = System.getProperty("os.arch");

		Document doc = null;
		try {
			doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
					.parse(
							new BufferedInputStream(getClass()
									.getResourceAsStream("/resources/os.xml")));
		} catch (Exception e) {
			e.printStackTrace();
		}
		NodeList nl = doc.getElementsByTagName("library");
		for (int i = 0; i < nl.getLength(); i++) {
			Node n = nl.item(i);
			if (n instanceof Element) {
				Element e = (Element) n;
				if (osName.trim().startsWith(e.getAttribute("name").trim())
						&& osArch.trim().startsWith(
								e.getAttribute("arch").trim())) {
					nativePath = e.getAttribute("path").trim();
				}
			}
		}

		final IPath conNativePath = javaProject.getPath().append(
				srcFolderName + "/" + nativePath);
		IFolder conNativeDir = project.getFolder(new Path(srcFolderName + "/"
				+ nativePath));
		mkDirs(conNativeDir, monitor);

		// copying os.xml
		{
			InputStream is = new BufferedInputStream(getClass()
					.getResourceAsStream("/resources/os.xml"));
			IFile iFile = sourceDir.getFile("os.xml");
			iFile.create(is, true, monitor);
		}

		// copying jars
		try {
			URL url = getClass().getResource("/resources/lib");
			url = FileLocator.toFileURL(url);

			File file = null;
			file = new File(url.getFile());

			File[] fl = file.listFiles();
			for (File f : fl) {
				String name = f.getName();
				String t = name.toLowerCase();
				if (t.equals("nativecapable.jar") || t.equals("ant.jar")
						|| t.equals("javassist.jar")) {
					InputStream is = new BufferedInputStream(
							new FileInputStream(f));
					IFile iFile = libDir.getFile(name);
					iFile.create(is, true, monitor);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		// coping natives
		// try {
		// URL url = getClass().getResource("/resources/native");
		// url = FileLocator.toFileURL(url);
		//
		// File file = null;
		// try {
		// file = new File(url.toURI());
		// } catch (URISyntaxException e2) {
		// String path = url.toString().substring(6);
		// file = new File(path);
		// }
		// copy(file, sourceDir, monitor);
		//
		// } catch (IOException e) {
		// e.printStackTrace();
		// }

		// container 'NCLIB' + hint 'default', not exported
		IClasspathEntry libEntry = JavaCore.newContainerEntry(new Path(
				"NCLIB/default"), null, new IClasspathAttribute[] { JavaCore
				.newClasspathAttribute(
						JavaRuntime.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY,
						conNativePath.toString().substring(1)) }, false);

		NCClasspathContainerInitializer initializer = new NCClasspathContainerInitializer();
		initializer.initialize(new Path("NCLIB/default"), javaProject);

		// Adding Default JRE
		entries.add(JavaRuntime.getDefaultJREContainerEntry());
		entries.add(libEntry);
		entries.add(srcEntry);
		javaProject.setRawClasspath(entries.toArray(new IClasspathEntry[entries
				.size()]), monitor);

		// copying sample
		try {
			URL url = getClass().getResource("/resources/samples/sample");
			url = FileLocator.toFileURL(url);

			File file = null;
			try {
				file = new File(url.toURI());
			} catch (URISyntaxException e2) {
				String path = url.toString().substring(6);
				file = new File(path);
			}
			copy(file, sourceDir, monitor);
		} catch (IOException e) {
			e.printStackTrace();
		}

		monitor.worked(1);
	}

	private void mkDirs(IFolder folder, IProgressMonitor monitor)
			throws CoreException {
		if (!folder.exists()) {
			IContainer c = folder.getParent();
			if (c instanceof IFolder) {
				mkDirs((IFolder) folder.getParent(), monitor);
			}
			folder.create(true, false, monitor);
		}
	}

	private void copy(File file, IFolder destDir, IProgressMonitor monitor)
			throws FileNotFoundException, CoreException {
		if (!destDir.exists()) {
			destDir.create(true, true, monitor);
		}
		if (file.isDirectory()) {
			File[] fl = file.listFiles();
			IFolder folder = destDir.getFolder(file.getName());
			for (File f : fl) {
				copy(f, folder, monitor);
			}
		} else {
			InputStream is = new BufferedInputStream(new FileInputStream(file));
			IFile iFile = destDir.getFile(file.getName());
			iFile.create(is, true, monitor);
		}

	}

	// /**
	// * We will initialize file contents with a sample text.
	// */
	//
	// private InputStream openContentStream() {
	// String contents = "This is the initial file contents for *.mpe file that
	// should be word-sorted in the Preview page of the multi-page editor";
	// return new ByteArrayInputStream(contents.getBytes());
	// }
	//
	// private void throwCoreException(String message) throws CoreException {
	// IStatus status = new Status(IStatus.ERROR, "NyARToolkitPlugin",
	// IStatus.OK, message, null);
	// throw new CoreException(status);
	// }

	/**
	 * We will accept the selection in the workbench to see if we can initialize
	 * from it.
	 * 
	 * @see IWorkbenchWizard#init(IWorkbench, IStructuredSelection)
	 */
	public void init(IWorkbench workbench, IStructuredSelection selection) {
		System.out.println("NativeCapableProjectNewWizard#init");
		this.selection = selection;

	}
}