package aQute.jpm.platform;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Formatter;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import aQute.jpm.lib.CommandData;
import aQute.jpm.lib.ServiceData;
import aQute.lib.getopt.Arguments;
import aQute.lib.getopt.Description;
import aQute.lib.getopt.Options;
import aQute.lib.io.IO;
import aQute.lib.strings.Strings;
import aQute.libg.command.Command;
import aQute.libg.glob.Glob;

public abstract class Unix extends Platform {
	private final static Logger	logger		= LoggerFactory.getLogger(Unix.class);

	public static String JPM_GLOBAL = "/var/jpm";

	@Override
	public File getGlobal() {
		return new File("/var/jpm");
	}

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

	@Override
	public File getLocal() {
		File home = new File(System.getenv("HOME"));
		return new File(home, "jpm");
	}

	@Override
	public String createCommand(CommandData data, Map<String,String> map, boolean force, String... extra)
			throws Exception {
		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("unix/command.sh", data, data.bin, map, extra);
		return null;
	}

	@Override
	public void deleteCommand(CommandData data) throws Exception {
		File executable = new File(getExecutable(data));
		IO.deleteWithException(executable);
	}

	@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) {
			if (initd.exists())
				return "Service already exists in " + initd + ", use --force to override";

			if (launch.exists())
				return "Service launch file already exists in " + launch + ", use --force to override";
		}

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

	public File getInitd(ServiceData data) {
		return new File("/etc/init.d/" + data.name);
	}

	protected File getLaunch(ServiceData data) {
		return new File(data.sdir, "launch.sh");
	}

	protected String getExecutable(CommandData data) {
		return new File(jpm.getBinDir(), data.name).getAbsolutePath();
	}

	@Override
	public String deleteService(ServiceData data) {
		if (!getInitd(data).delete())
			return "Cannot delete " + getInitd(data);

		File f = new File(getExecutable(data));
		if (!f.delete())
			return "Cannot delete " + getExecutable(data);

		System.out.println("Removed service data ");

		return null;
	}

	@Override
	public int launchService(ServiceData data) throws Exception {
		File launch = getLaunch(data);
		Process p = Runtime.getRuntime().exec(launch.getAbsolutePath(), null, new File(data.work));
		return p.waitFor();
	}

	String			DAEMON			= "\n### JPM BEGIN ###\n" + "jpm daemon >" + JPM_GLOBAL + "/daemon.log 2>>"
			+ JPM_GLOBAL + "/daemon.log &\n### JPM END ###\n";
	static Pattern	DAEMON_PATTERN	= Pattern.compile("\n### JPM BEGIN ###\n.*\n### JPM END ###\n", Pattern.MULTILINE);

	@Override
	public void installDaemon(boolean user) throws Exception {
		if (user)
			throw new IllegalArgumentException("This Unix platform does not support user based agents");

		File rclocal = new File("/etc/rc.d/rc.local");
		if (!rclocal.isFile())
			rclocal = new File("/etc/rc.local");

		if (!rclocal.isFile())
			throw new IllegalArgumentException("Cannot find rc.local in either /etc or /etc/rc.d. Unknown unix");

		String s = IO.collect(rclocal);
		if (s.contains(DAEMON))
			return;

		// TODO handle exit 0 at end of file

		s += DAEMON;
		IO.store(s, rclocal);

	}

	@Override
	public void uninstallDaemon(boolean user) throws Exception {
		if (user)
			return;

		File rclocal = new File("/etc/rc.d/rc.local");
		if (!rclocal.isFile())
			rclocal = new File("/etc/rc.local");

		if (!rclocal.isFile())
			return;

		String s = IO.collect(rclocal);

		Matcher m = DAEMON_PATTERN.matcher(s);
		s = m.replaceAll("");
		s += DAEMON;
		IO.store(s, rclocal);
	}

	@Override
	public void chown(String user, boolean recursive, File file) throws Exception {
		String cmd = "chown " + (recursive ? " -R " : "") + user + " " + file.getAbsolutePath();
		if ("root".equals(user))
			return;

		if ("0".equals(user))
			return;

		Command chown = new Command(cmd);
		StringBuilder sb = new StringBuilder();

		int n = chown.execute(sb, sb);
		if (n != 0)
			throw new IllegalArgumentException("Changing ownership for " + file + " fails: " + n + " : " + sb);
	}

	@Override
	public String user() throws Exception {
		ProcessBuilder pb = new ProcessBuilder();
		Map<String,String> environment = pb.environment();
		String user = environment.get("USER");
		return user;
		// Command id = new Command("id -nu");
		// StringBuilder sb = new StringBuilder();
		//
		// int n = id.execute(sb,sb);
		// if ( n != 0)
		// throw new IllegalArgumentException("Getting user id fails: " + n +
		// " : " + sb);
		//
		// return sb.toString().trim();
	}

	protected void process(String resource, CommandData data, String file, Map<String,String> map, String... extra)
			throws Exception {
		super.process(resource, data, file, map, extra);
		run("chmod a+x " + file);
	}

	@Override
	public void report(Formatter out) throws IOException, Exception {
		out.format("Name     \t%s\n", getName());
		out.format("Global   \t%s\n", getGlobal());
		out.format("Local    \t%s\n", getLocal());
	}

	/**
	 * Add the bindir to PATH env. variable in .profile
	 */

	@Arguments(arg = {})
	@Description("Add the bin directory for this jpm to your PATH in the user's environment variables in the ~/.profile file.")
	interface PathOptions extends Options {
		@Description("Remove the bindir from the user's environment variables.")
		boolean remove();

		@Description("Delete a path from the PATH environment variable")
		List<String> delete();

		@Description("Add the current binary dir to the PATH environment variable")
		boolean add();

		@Description("Add additional paths to the PATH environment variable")
		List<String> extra();

		@Description("Override the default .profile and use this file. This file is relative to the home directory if not absolute.")
		String profile();

		@Description("If the profile file does not exist, create")
		boolean force();

	}

	static Pattern PATH_P = Pattern.compile("(export|set)\\s+PATH\\s*=(.*):\\$PATH\\s*$");

	@Description("Add the bin directory for this jpm to your PATH in the user's environment variables")
	public void _path(PathOptions options) throws IOException {
		File file = options.profile() == null ? IO.getFile("~/.profile") : IO.getFile(IO.home, options.profile());

		if (!file.isFile()) {
			if (!options.force()) {
				reporter.error("No such file %s", file);
				return;
			}
			IO.store("# created by jpm\n", file);
		}

		String parts[] = System.getenv("PATH").split(":");
		List<String> paths = new ArrayList<String>(Arrays.asList(parts));

		for (int i = 0; i < parts.length; i++) {
			System.out.printf("%2d:%s %s %s%n", i, parts[i].toLowerCase().contains("jpm") ? "*" : " ",
					new File(parts[i]).isDirectory() ? " " : "!", parts[i]);
		}

		String bd = jpm.getBinDir().getAbsolutePath();
		String s = IO.collect(file);

		//
		// Remove the current binary path. If it is add, we add it later
		//
		if (options.remove() || options.add()) {
			if (!bd.equals(getGlobalBinDir().getAbsolutePath())) {
				s = s.replaceAll("(PATH\\s*=)" + bd + ":(.*\\$PATH)", "$1$2");
				logger.debug("removed {}", bd);
			}
		}

		if (options.delete() != null) {
			for (String delete : options.delete()) {
				s = s.replaceAll("(PATH\\s*=)" + Glob.toPattern(delete) + ":(.*\\$PATH)", "$1$2");
			}
		}

		//
		// Remove any orphans
		//
		s = s.replaceAll("\\s*(set|export)\\s+PATH\\s*=\\$PATH\\s*", "");

		List<String> additions = new ArrayList<String>();
		if (options.add()) {
			if (!bd.equals(getGlobalBinDir().getAbsolutePath())) {
				additions.add(bd);
			}
		}
		if (options.extra() != null)
			for (String add : options.extra()) {
				File f = IO.getFile(add);
				if (!f.isDirectory()) {
					reporter.error("%s is not a directory, not added", f.getAbsolutePath());
				} else {
					if (!paths.contains(f.getAbsolutePath()))
						additions.add(f.getAbsolutePath());
				}
			}

		if (!additions.isEmpty()) {
			s += "\nexport PATH=" + Strings.join(":", additions) + ":$PATH\n";
			logger.debug("s {}", s);
		}
		IO.store(s, file);
	}
}
