/*******************************************************************************
 * Copyright (c) 2015, 2018 Red Hat Inc. and others.
 * 
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Red Hat - Initial Contribution
 *******************************************************************************/
package org.eclipse.linuxtools.internal.vagrant.core;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.security.storage.ISecurePreferences;
import org.eclipse.equinox.security.storage.SecurePreferencesFactory;
import org.eclipse.equinox.security.storage.StorageException;

/**
 * A class for managing the environments of various vagrant boxes.
 * 
 * The implementation will use secure storage to save the environments for all
 * environments. It will keep all environments stored in one property key,
 * serialized, and load / store via a byte array.
 * 
 * While it's definitely possible that some environments do not need to be
 * secured, it seems prudent in this case that the possibility that credentials
 * will be passed through the environment make it more than reasonable to secure
 * all environments.
 */
public class EnvironmentsManager {
	private static EnvironmentsManager singleton = new EnvironmentsManager();

	public static EnvironmentsManager getSingleton() {
		return singleton;
	}

	private EnvironmentsManager() {
		getEnvironment(); // pre-load from preferences
	}

	private Environment env;

	private synchronized Environment getEnvironment() {
		if (env == null)
			env = new Environment();
		return env;
	}

	private class Environment {
		private static final String ENVIRONMENT_ATTRIBUTE = "org.eclipse.linuxtools.internal.vagrant.core.EnvironmentsManager.EnvironmentKey"; //$NON-NLS-1$
		private static final String SECURE_PREFERNCES_BASEKEY = Activator.PLUGIN_ID;
		private static final String ENVIRONMENT_KEY = "environment"; //$NON-NLS-1$

		private static final boolean READ = true;
		private static final boolean WRITE = false;

		private Map<String, Map<String, String>> backing;

		public Environment() {
			load();
		}

		private ISecurePreferences getPreferenceNode() {
			ISecurePreferences root = SecurePreferencesFactory.getDefault();
			ISecurePreferences node = root.node(SECURE_PREFERNCES_BASEKEY);
			return node.node(ENVIRONMENT_KEY);
		}

		@SuppressWarnings("unchecked")
		private void load() {
			ISecurePreferences prefs2 = getPreferenceNode();
			try {
				byte[] read = prefs2.getByteArray(ENVIRONMENT_ATTRIBUTE,
						(byte[]) null);
				backing = new HashMap<>();
				if (read == null) {
					return;
				}
				ByteArrayInputStream bis = new ByteArrayInputStream(read);
				ObjectInputStream ois = new ObjectInputStream(bis);
				backing = (Map<String, Map<String, String>>) ois.readObject();
				ois.close();
				bis.close();
			} catch (IOException ioe) {
				Activator.getDefault().getLog().log(createStatus(ioe, READ));
			} catch (ClassNotFoundException cnfe) {
				Activator.getDefault().getLog().log(createStatus(cnfe, READ));
			} catch (StorageException se) {
				Activator.getDefault().getLog().log(createStatus(se, READ));
			}
		}

		private void save() {
			if (backing == null) {
				backing = new HashMap<>();
			}
			ISecurePreferences prefs = getPreferenceNode();
			try {
				byte[] toSave = null;
				ByteArrayOutputStream bos = new ByteArrayOutputStream();
				ObjectOutputStream oos = new ObjectOutputStream(bos);
				oos.writeObject(backing);
				oos.close();
				bos.close();
				toSave = bos.toByteArray();
				prefs.putByteArray(ENVIRONMENT_ATTRIBUTE, toSave, true);
				prefs.flush();
			} catch (IOException ioe) {
				Activator.getDefault().getLog().log(createStatus(ioe, WRITE));
			} catch (StorageException se) {
				Activator.getDefault().getLog().log(createStatus(se, WRITE));
			}
		}

		private IStatus createStatus(Throwable t, boolean action) {
			// TODO externalize?
			if (action == READ)
				return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
						Messages.EnvironmentsManager_error_read, t);
			else
				return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
						Messages.EnvironmentsManager_error_write, t);
		}

		public void setEnvironmentForFolder(File folder,
				Map<String, String> env) {
			if (backing != null) {
				backing.put(folder.getAbsolutePath(), env);
			}
		}

		public Map<String, String> getEnvironmentForFolder(File folder) {
			if (backing != null) {
				return backing.get(folder.getAbsolutePath());
			}
			return null;
		}
	}

	public Map<String, String> getEnvironment(File folder) {
		return getEnvironment().getEnvironmentForFolder(folder);
	}

	public void setEnvironment(File folder, Map<String, String> environment) {
		Environment env = getEnvironment();
		env.setEnvironmentForFolder(folder, environment);
		env.save();
	}


	/*
	 * Convert a string/string hashmap into an array of string environment
	 * variables as required by java.lang.Runtime This will super-impose the
	 * provided environment variables ON TOP OF the existing environment in
	 * eclipse, as users may not know *all* environment variables that need to
	 * be set, or to do so may be tedious.
	 */
	public static String[] convertEnvironment(final Map<String, String> env) {
		// Create a new map based on pre-existing environment of Eclipse
		final Map<String, String> environment = System.getenv();
		Map<String, String> result = new HashMap<>(environment);
		// Add all new environments on top of existing
		if (env != null) {
			result.putAll(env);
		}
		// also update the PATH to include the path to the Vagrant executable
		final StringBuilder path = new StringBuilder();
		final String newEnvPath = path.append(environment.get("PATH")) //$NON-NLS-1$
				.append(File.pathSeparator)
				.append(VagrantConnection.getUserDefinedVagrantPath())
				.toString();
		result.put("PATH", newEnvPath); //$NON-NLS-1$
		// Convert the combined map into a form that can be used to launch
		// process
		ArrayList<String> ret = new ArrayList<>();
		Iterator<String> it = result.keySet().iterator();
		String working = null;
		while (it.hasNext()) {
			working = it.next();
			ret.add(working + "=" + result.get(working)); //$NON-NLS-1$
		}
		return ret.toArray(new String[ret.size()]);
	}

}
