package org.objectstyle.wolips.eomodeler.factories;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.objectstyle.woenvironment.env.WOEnvironment;
import org.objectstyle.woenvironment.plist.PropertyListParserException;
import org.objectstyle.woenvironment.plist.SimpleParserDataStructureFactory;
import org.objectstyle.woenvironment.plist.WOLPropertyListSerialization;
import org.objectstyle.wolips.baseforplugins.util.StringUtils;
import org.objectstyle.wolips.baseforplugins.util.URLUtils;
import org.objectstyle.wolips.eomodeler.core.model.AbstractManifestEOModelGroupFactory;
import org.objectstyle.wolips.eomodeler.core.model.ManifestSearchFolder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class EclipseProjectEOModelGroupFactory extends AbstractManifestEOModelGroupFactory {
	@Override
	public List<ManifestSearchFolder> getSearchFolders(File selectedModelFolder) throws IOException {
		// System.out.println(
		// "EclipseProjectEOModelGroupFactory.getSearchFolders: Looking for Eclipse projects ..."
		// );
		List<ManifestSearchFolder> searchFolders = null;
		List<File> eclipseProjectFolders = new LinkedList<File>();
		try {
			findEclipseProjectFolders(selectedModelFolder, eclipseProjectFolders);
		} catch (PropertyListParserException e) {
			e.printStackTrace();
			throw new IOException("Failed to parse '.EntityModeler.plist'. " + StringUtils.getErrorMessage(e));
		}
		if (!eclipseProjectFolders.isEmpty()) {
			searchFolders = new LinkedList<ManifestSearchFolder>();
			for (File eclipseProjectFolder : eclipseProjectFolders) {
				// System.out.println(
				// "EclipseProjectEOModelGroupFactory.getSearchFolders: Project = "
				// + eclipseProjectFolder);
				Set<File> visitedProjectFolders = new HashSet<File>();
				try {
					processEclipseProject(eclipseProjectFolder, searchFolders, visitedProjectFolders, new WOEnvironment());
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		// System.out.println(
		// "EclipseProjectEOModelGroupFactory.getSearchFolders: " +
		// searchFolders);
		return searchFolders;
	}

	protected File getWorkspaceFolder(File projectFolder) {
		File workspaceFolder;
		String workspaceFolderPath = System.getProperty("workspaceFolder");
		if (workspaceFolderPath != null) {
			workspaceFolder = new File(workspaceFolderPath);
		} else {
			workspaceFolder = null;
			File searchingFolder = projectFolder;
			while (searchingFolder != null && workspaceFolder == null) {
				File tempFolder = new File(searchingFolder, ".metadata");
				if (tempFolder.exists() && tempFolder.isDirectory()) {
					workspaceFolder = searchingFolder;
				} else {
					searchingFolder = searchingFolder.getParentFile();
				}
			}
		}

		if (workspaceFolder == null) {
			workspaceFolder = projectFolder.getParentFile();
		}

		return workspaceFolder;
	}

	protected File getProjectFolder(File workspaceFolder, String projectName) {
		File projectFolder = null;
		File projectLocationFile = new File(workspaceFolder, ".metadata/.plugins/org.eclipse.core.resources/.projects/" + projectName + "/.location");
		if (projectLocationFile.exists()) {
			// MS: This is pretty crazy, but the .location file is a binary file (of "unknown" layout) that contains
			// a URL that is the location of the project folder.  We're going to load that binary file and look for
			// something that looks like a file: URL ... Crazy but works :)
			try {
				FileInputStream fis = new FileInputStream(projectLocationFile);
				try {
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					byte[] buffer = new byte[2048];
					while (fis.available() > 0) {
						int bytesRead = fis.read(buffer);
						baos.write(buffer, 0, bytesRead);
					}
					String locationContents = baos.toString();
					int fileIndex = locationContents.indexOf("file:");
					if (fileIndex != -1) {
						int zeroIndex = locationContents.indexOf(0, fileIndex);
						String projectPath = locationContents.substring(fileIndex, zeroIndex);
						projectFolder = URLUtils.cheatAndTurnIntoFile(new URL(projectPath));
					}
				} finally {
					fis.close();
				}
			} catch (Throwable t) {
				t.printStackTrace();
			}
		}

		if (projectFolder == null) {
			projectFolder = new File(workspaceFolder, projectName);
		}
		return projectFolder;
	}

	protected void processEclipseProject(File eclipseProjectFolder, List<ManifestSearchFolder> searchFolders, Set<File> visitedProjects, WOEnvironment env) throws IOException, XPathExpressionException, SAXException, ParserConfigurationException {
		if (eclipseProjectFolder == null || !eclipseProjectFolder.exists() || visitedProjects.contains(eclipseProjectFolder)) {
			return;
		}
		// System.out.println(
		// "EclipseProjectEOModelGroupFactory.processEclipseProject: Project folder '"
		// + eclipseProjectFolder + "' ...");

		String[] buildFolderNames = new String[] { "build", "dist" };
		for (String buildFolderName : buildFolderNames) {
			File buildFolder = new File(eclipseProjectFolder, buildFolderName);
			if (buildFolder.exists()) {
				File buildResourcesFolder;
				File frameworkResourcesFolder = new File(buildFolder, eclipseProjectFolder.getName() + ".framework" + File.separator + "Resources");
				if (frameworkResourcesFolder.exists()) {
					buildResourcesFolder = frameworkResourcesFolder;
				} else {
					File woaResourcesFolder = new File(buildFolder, eclipseProjectFolder.getName() + ".woa" + File.separator + "Contents" + File.separator + "Resources");
					if (woaResourcesFolder.exists()) {
						buildResourcesFolder = woaResourcesFolder;
					} else {
						buildResourcesFolder = buildFolder;
					}
				}
				// We're cheating some here and forcing project build resources
				// folders to the front of the line ...
				searchFolders.add(0, new ManifestSearchFolder(buildResourcesFolder));
				break;
			}
		}
		visitedProjects.add(eclipseProjectFolder);

		File workspaceFolder = null;
		File eclipseClasspathFile = new File(eclipseProjectFolder, ".classpath");
		if (eclipseClasspathFile.exists()) {
			XPathExpression eclipseClasspathEntryExpression = XPathFactory.newInstance().newXPath().compile("//classpath/classpathentry");
			Document eclipseClasspathDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(eclipseClasspathFile);
			NodeList eclipseClasspathEntryNodes = (NodeList) eclipseClasspathEntryExpression.evaluate(eclipseClasspathDocument, XPathConstants.NODESET);
			for (int eclipseClasspathEntryNum = 0; eclipseClasspathEntryNum < eclipseClasspathEntryNodes.getLength(); eclipseClasspathEntryNum++) {
				Element eclipseClasspathEntryElement = (Element) eclipseClasspathEntryNodes.item(eclipseClasspathEntryNum);
				String kind = eclipseClasspathEntryElement.getAttribute("kind");
				String path = eclipseClasspathEntryElement.getAttribute("path");
				if ("src".equals(kind) && path != null && path.startsWith("/")) {
					if (workspaceFolder == null) {
						workspaceFolder = getWorkspaceFolder(eclipseProjectFolder);
					}
					File referencedProjectFolder = getProjectFolder(workspaceFolder, path).getCanonicalFile();
						//new File(eclipseProjectFolder.getParentFile(), path).getCanonicalFile();
					processEclipseProject(referencedProjectFolder, searchFolders, visitedProjects, env);
				} else if ("con".equals(kind) && path != null && path.startsWith("org.objectstyle.wolips.ContainerInitializer/")) {
					String[] strs = path.split("/");
					List<String> frameworkNames = new LinkedList<String>();
					for (int i = 3; i < strs.length; i += 11) {
						frameworkNames.add(strs[i]);
					}
					loadFrameworks(frameworkNames.toArray(new String[frameworkNames.size()]), searchFolders, visitedProjects, env);
				} else if ("con".equals(kind) && path != null && path.startsWith("org.objectstyle.wolips.WO_CLASSPATH/")) {
					String[] frameworkNames = path.split("/");
					loadFrameworks(frameworkNames, searchFolders, visitedProjects, env);
				} else if ("con".equals(kind) && path != null && path.startsWith("WOFramework/")) {
					String frameworkName = path.substring(path.indexOf("/") + 1);
					if (workspaceFolder == null) {
						workspaceFolder = getWorkspaceFolder(eclipseProjectFolder);
					}
					File referencedProjectFolder = getProjectFolder(workspaceFolder, frameworkName).getCanonicalFile();
					if (referencedProjectFolder.exists()) {
						processEclipseProject(referencedProjectFolder, searchFolders, visitedProjects, env);
					}
					else {
						loadFrameworks(new String[] { frameworkName }, searchFolders, visitedProjects, env);
					}
				}
			}
		}
	}

	protected void loadFrameworks(String[] frameworkNames, List<ManifestSearchFolder> searchFolders, Set<File> visitedProjects, WOEnvironment env) throws IOException {
		File userFrameworksFolder = new File(env.getWOVariables().userFrameworkPath());
		File localFrameworksFolder = new File(env.getWOVariables().localFrameworkPath());
		File systemFrameworksFolder = new File(env.getWOVariables().systemFrameworkPath());
		for (int frameworkNum = 1; frameworkNum < frameworkNames.length; frameworkNum++) {
			String frameworkFolderName = frameworkNames[frameworkNum] + ".framework";
			File matchingFrameworkFolder = null;
			File userFrameworkFolder = new File(userFrameworksFolder, frameworkFolderName);
			if (userFrameworkFolder.exists()) {
				matchingFrameworkFolder = userFrameworkFolder.getCanonicalFile();
			} else {
				File localFrameworkFolder = new File(localFrameworksFolder, frameworkFolderName);
				if (localFrameworkFolder.exists()) {
					matchingFrameworkFolder = localFrameworkFolder.getCanonicalFile();
				} else {
					File systemFrameworkFolder = new File(systemFrameworksFolder, frameworkFolderName);
					if (systemFrameworkFolder.exists()) {
						matchingFrameworkFolder = systemFrameworkFolder.getCanonicalFile();
					}
				}
			}
			if (matchingFrameworkFolder != null && !visitedProjects.contains(matchingFrameworkFolder)) {
				// System.out.println(
				// "EclipseProjectEOModelGroupFactory.processEclipseProject: framework = "
				// + matchingFrameworkFolder);
				ManifestSearchFolder searchFolder = new ManifestSearchFolder(new File(matchingFrameworkFolder, "Resources"));
				if (!searchFolders.contains(searchFolder)) {
					searchFolders.add(searchFolder);
				}	
				visitedProjects.add(matchingFrameworkFolder);
			}
		}

	}

	@SuppressWarnings("unchecked")
	protected void findEclipseProjectFolders(File folder, List<File> eclipseProjectFolders) throws IOException, PropertyListParserException {
		if (folder != null) {
			boolean foundProjectFolders = false;
			if (folder.isDirectory()) {
				// System.out.println(
				// "EclipseProjectEOModelGroupFactory.findEclipseProjectFolders: Looking for '"
				// + folder + "' ...");
				File projectLocator = new File(folder, ".EntityModeler.plist");
				if (projectLocator.exists()) {
					// System.out.println(
					// "EclipseProjectEOModelGroupFactory.findEclipseProjectFolders:   Found project locator '"
					// + projectLocator + "' ...");
					Map<String, Object> projectProperties = (Map<String, Object>) WOLPropertyListSerialization.propertyListWithContentsOfFile(projectLocator.getCanonicalPath(), new SimpleParserDataStructureFactory());
					Map<String, List<String>> dependencies = (Map<String, List<String>>) projectProperties.get("Dependencies");
					if (dependencies != null) {
						List<String> projectFilePaths = dependencies.get("Eclipse");
						if (projectFilePaths != null) {
							for (String projectFilePath : projectFilePaths) {
								File projectFolder = new File(projectFilePath);
								if (!projectFolder.exists()) {
									projectFolder = new File(folder, projectFilePath);
								}
								if (projectFolder.exists()) {
									projectFolder = projectFolder.getCanonicalFile();
									if (isEclipseProjectFolder(projectFolder)) {
										// System.out.println(
										// "EclipseProjectEOModelGroupFactory.findEclipseProjectFolders:     Found project '"
										// + projectFolder + "'");
										eclipseProjectFolders.add(projectFolder);
										foundProjectFolders = true;
									}
								}
							}
						}
					}
				} else if (isEclipseProjectFolder(folder)) {
					// System.out.println(
					// "EclipseProjectEOModelGroupFactory.findEclipseProjectFolders:   Found project '"
					// + folder + "'");
					File eclipseProjectFolder = folder.getCanonicalFile();
					eclipseProjectFolders.add(eclipseProjectFolder);
					foundProjectFolders = true;
				}
			}
			if (!foundProjectFolders) {
				File parentFolder = folder.getParentFile();
				findEclipseProjectFolders(parentFolder, eclipseProjectFolders);
			}
		}
	}

	public boolean isEclipseProjectFolder(File folder) {
		return new File(folder, ".project").exists() && new File(folder, ".classpath").exists();
	}

}
