package aQute.jpm.platform;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

import aQute.jpm.lib.CommandData;
import aQute.jpm.lib.JVM;
import aQute.jpm.lib.ServiceData;
import aQute.lib.io.IO;

class MacOS extends Unix {
	private final static Logger		logger	= LoggerFactory.getLogger(MacOS.class);
	static DocumentBuilderFactory	dbf	= DocumentBuilderFactory.newInstance();
	static XPathFactory				xpf	= XPathFactory.newInstance();

	@Override
	public File getGlobal() {
		return new File("/Library/Java/PackageManager").getAbsoluteFile();
	}

	@Override
	public File getGlobalBinDir() {
		return new File("/usr/local/bin").getAbsoluteFile();
	}

	@Override
	public File getLocal() {
		return IO.getFile("~/Library/PackageManager").getAbsoluteFile();
	}

	@Override
	public void shell(String initial) throws Exception {
		run("open -n /Applications/Utilities/Terminal.app");
	}

	@Override
	public String getName() {
		return "MacOS";
	}

	@Override
	public String createCommand(CommandData data, Map<String,String> map, boolean force, String... extra)
			throws Exception {
		if (data.bin == null)
			data.bin = getExecutable(data);

		File f = new File(data.bin);
		if (f.isDirectory()) {
			f = new File(data.bin, data.name);
			data.bin = f.getAbsolutePath();
		}

		if (!force && f.exists())
			return "Command already exists " + data.bin;

		process("macos/command.sh", data, data.bin, map, extra);
		return null;
	}

	@Override
	public String createService(ServiceData data, Map<String,String> map, boolean force, String... extra)
			throws Exception {
		// File initd = getInitd(data);
		File launch = getLaunch(data);
		if (!force && launch.exists())
			return "Cannot create service " + data.name + " because it exists";

		process("macos/launch.sh", data, launch.getAbsolutePath(), map, add(extra, data.serviceLib));
		return null;
	}

	@Override
	public String deleteService(ServiceData data) {
		// File initd = getInitd(data);
		File launch = getLaunch(data);

		if (launch.exists() && !launch.delete())
			return "Cannot delete service " + data.name + " because it exists and cannot be deleted: " + launch;

		return null;
	}

	@Override
	public void installDaemon(boolean user) throws IOException {
		String dest = "~/Library/LaunchAgents/org.jpm4j.run.plist";
		if (!user) {
			dest = "/Library/LaunchAgents/org.jpm4j.run.plist";
		}
		IO.copy(getClass().getResource("macos/daemon.plist"), IO.getFile(dest));
	}

	@Override
	public void uninstallDaemon(boolean user) throws IOException {
		if (user)
			IO.delete(new File("~/Library/LaunchAgents/org.jpm4j.run.plist"));
		else
			IO.delete(new File("/Library/LaunchAgents/org.jpm4j.run.plist"));
	}

	@Override
	public void uninstall() throws IOException {}

	public String defaultCacertsPassword() {
		return "changeit";
	}

	public String toString() {
		return "MacOS/Darwin";
	}

	/**
	 * Return the VMs on the platform.
	 * 
	 * @throws Exception
	 */
	@Override
	public void getVMs(Collection<JVM> vms) throws Exception {
		String paths[] = {
				"/System/Library/Java/JavaVirtualMachines", "/Library/Java/JavaVirtualMachines"
		};
		for (String path : paths) {
			for (File vmdir : new File(path).listFiles()) {
				JVM jvm = getJVM(vmdir);
				if (jvm != null)
					vms.add(jvm);
			}
		}
	}

	@Override
	public JVM getJVM(File vmdir) throws Exception {
		if (!vmdir.isDirectory()) {
			return null;
		}

		File contents = new File(vmdir, "Contents");
		if (!contents.isDirectory()) {
			logger.debug("Found a directory {}, but it does not have the expected Contents directory", vmdir);
			return null;
		}

		File plist = new File(contents, "Info.plist");
		if (!plist.isFile()) {
			logger.debug("The VM in {} has no Info.plist with the necessary details", vmdir);
			return null;
		}

		File home = new File(contents, "Home");
		String error = verifyVM(home);
		if (error != null) {
			reporter.error("Invalid vm directory for MacOS %s: %s", vmdir, error);
			return null;
		}

		DocumentBuilder db = dbf.newDocumentBuilder();
		try {
			Document doc = db.parse(plist);
			XPath xp = xpf.newXPath();
			Node versionNode = (Node) xp.evaluate("//dict/key[text()='JVMVersion']", doc, XPathConstants.NODE);
			Node platformVersionNode = (Node) xp.evaluate("//dict/key[text()='JVMPlatformVersion']", doc,
					XPathConstants.NODE);
			Node vendorNode = (Node) xp.evaluate("//dict/key[text()='JVMVendor']", doc, XPathConstants.NODE);
			Node capabilitiesNode = (Node) xp.evaluate("//dict/key[text()='JVMCapabilities']", doc,
					XPathConstants.NODE);

			JVM jvm = new JVM();
			jvm.name = vmdir.getName();
			jvm.path = home.getCanonicalPath();
			jvm.platformRoot = vmdir.getCanonicalPath();
			jvm.version = getSiblingValue(versionNode);
			jvm.platformVersion = getSiblingValue(platformVersionNode);
			jvm.vendor = getSiblingValue(vendorNode);

			return jvm;
		} catch (Exception e) {
			logger.debug("Could not parse the Info.plist in {}", vmdir, e);
			throw e;
		}
	}

	private String getSiblingValue(Node node) {
		if (node == null)
			return null;
		node = node.getNextSibling();
		while (node.getNodeType() == Node.TEXT_NODE)
			node = node.getNextSibling();

		return node.getTextContent();
	}

}
