/*******************************************************************************
 * Copyright (c) 2007, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Pascal Rapicault - Support for bundled macosx http://bugs.eclipse.org/57349
 *     Christian Georgi - Relativize VM path https://bugs.eclipse.org/bugs/437680
 *******************************************************************************/
package org.eclipse.equinox.internal.frameworkadmin.equinox;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.equinox.internal.frameworkadmin.equinox.utils.FileUtils;
import org.eclipse.equinox.internal.frameworkadmin.utils.Utils;
import org.eclipse.equinox.internal.provisional.frameworkadmin.FrameworkAdminRuntimeException;
import org.eclipse.equinox.internal.provisional.frameworkadmin.LauncherData;
import org.eclipse.osgi.service.environment.Constants;
import org.eclipse.osgi.util.NLS;
import org.osgi.service.log.LogService;

public class EclipseLauncherParser {
	private static final String CONFIGURATION_FOLDER = "configuration"; //$NON-NLS-1$

	//this figures out the location of the data area on partial data read from the <eclipse>.ini
	private URI getOSGiInstallArea(List<String> lines, URI base, LauncherData launcherData) {
		//does the eclipse.ini say anything for osgi.install.area?
		File osgiInstallArea = ParserUtils.getOSGiInstallArea(lines, null, launcherData.getLauncher(), base);
		if (osgiInstallArea == null) {
			//otherwise use the launcherData to figure it out
			osgiInstallArea = ParserUtils.getOSGiInstallArea(lines, null, launcherData);
		}
		if (osgiInstallArea != null)
			return URIUtil.makeAbsolute(osgiInstallArea.toURI(), base);
		return null;
	}

	private void setInstall(List<String> lines, LauncherData launcherData, File launcherFolder) {
		if (launcherData.getFwConfigLocation() == null || launcherData.getFwJar() == null) {
			ParserUtils.removeArgument(EquinoxConstants.OPTION_INSTALL, lines);
			return;
		}
		File folder = ParserUtils.fromOSGiJarToOSGiInstallArea(launcherData.getFwJar().getAbsolutePath());
		if (Constants.OS_MACOSX.equals(launcherData.getOS())) {
			folder = new File(folder, "../MacOS");
		}
		if (!folder.equals(launcherFolder)) {
			// Launcher will replace "@launcher.dir" with actual path. See bug 572890.
			ParserUtils.setValueForArgument(EquinoxConstants.OPTION_INSTALL, "@launcher.dir/", lines);
		}
	}

	void read(File launcherConfigFile, LauncherData launcherData) throws IOException {
		if (!launcherConfigFile.exists())
			return;

		List<String> lines = FileUtils.loadFile(launcherConfigFile);

		URI launcherFolder = launcherData.getLauncher().getParentFile().toURI();
		getStartup(lines, launcherFolder);
		getFrameworkJar(lines, launcherFolder, launcherData);
		URI osgiInstallArea = getOSGiInstallArea(lines, launcherFolder, launcherData);
		if (osgiInstallArea == null) {
			osgiInstallArea = launcherData.getFwJar() != null ? ParserUtils.fromOSGiJarToOSGiInstallArea(launcherData.getFwJar().getAbsolutePath()).toURI() : launcherFolder;
		}
		URI configArea = getConfigurationLocation(lines, osgiInstallArea, launcherData);
		if (configArea == null)
			throw new FrameworkAdminRuntimeException(Messages.exception_nullConfigArea, ""); //$NON-NLS-1$
		getPersistentDataLocation(lines, osgiInstallArea, configArea, launcherData);
		getLauncherLibrary(lines, launcherFolder);
		getJVMArgs(lines, launcherData);
		getProgramArgs(lines, launcherData);
		getVM(lines, launcherFolder, launcherData);

		Log.log(LogService.LOG_INFO, NLS.bind(Messages.log_configFile, launcherConfigFile.getAbsolutePath()));
	}

	private void getFrameworkJar(List<String> lines, URI launcherFolder, LauncherData launcherData) {
		File fwJar = launcherData.getFwJar();
		if (fwJar != null)
			return;
		URI location = ParserUtils.getFrameworkJar(lines, launcherFolder);
		if (location != null)
			launcherData.setFwJar(URIUtil.toFile(location));
	}

	private void getPersistentDataLocation(List<String> lines, URI osgiInstallArea, URI configArea, LauncherData launcherData) {
		//TODO The setting of the -clean could only do properly once config.ini has been read
		if (launcherData.getFwPersistentDataLocation() == null) {
			launcherData.setFwPersistentDataLocation(URIUtil.toFile(configArea), ParserUtils.isArgumentSet(EquinoxConstants.OPTION_CLEAN, lines));
		}
	}

	private void getVM(List<String> lines, URI launcherFolder, LauncherData launcherData) {
		String vm = ParserUtils.getValueForArgument(EquinoxConstants.OPTION_VM, lines);
		if (vm == null)
			return;

		URI VMFullPath;
		try {
			VMFullPath = URIUtil.makeAbsolute(FileUtils.fromPath(vm), launcherFolder);
			launcherData.setJvm(URIUtil.toFile(VMFullPath));
			ParserUtils.setValueForArgument(EquinoxConstants.OPTION_VM, VMFullPath.toString(), lines);
		} catch (URISyntaxException e) {
			Log.log(LogService.LOG_ERROR, NLS.bind(Messages.log_failed_make_absolute, vm));
			return;
		}
	}

	private void setVM(List<String> lines, File vm, URI launcherFolder, File installHome) {
		if (vm == null) {
			if (ParserUtils.getValueForArgument(EquinoxConstants.OPTION_VM, lines) != null)
				return;

			ParserUtils.removeArgument(EquinoxConstants.OPTION_VM, lines);
			return;
		}

		URI vmRelativePath = null;
		if (vm.isAbsolute()) {
			// Bug 437680: Correctly relativize on MacOS
			// Example:         (traditional layout)                     (bundled layout)
			//   Install home:  install/                                 Eclipse.app/
			//   Launcher:        Eclipse.app/Contents/MacOS/              Contents/MacOS/
			//   VM:              jre/                                     jre/
			//   Result:        ../../../jre                             ../../jre
			URI vmRelativePathToHome = installHome.toURI().relativize(vm.toURI());
			if (vmRelativePathToHome.isAbsolute()) {
				// VM is not below the install root -> use absolute path
				vmRelativePath = vmRelativePathToHome;
			} else {
				// make VM path relative to launcher folder (which is different to the install root in MacOS installs)
				vmRelativePath = URIUtil.makeRelative(vm.toURI(), launcherFolder);
			}
		} else {
			//For relative files, File#toURI will create an absolute URI by resolving against the current working directory, we don't want that
			String path = vm.getPath().replace('\\', '/');
			try {
				vmRelativePath = URIUtil.fromString(path);
			} catch (URISyntaxException e) {
				vmRelativePath = launcherFolder.relativize(vm.toURI());
			}
		}

		ParserUtils.setValueForArgument(EquinoxConstants.OPTION_VM, FileUtils.toPath(vmRelativePath).replace('\\', '/'), lines);
	}

	private void getJVMArgs(List<String> lines, LauncherData launcherData) {
		ArrayList<String> vmargs = new ArrayList<>(lines.size());
		boolean foundVmArgs = false;
		for (String line : lines) {
			if (!foundVmArgs) {
				if (EquinoxConstants.OPTION_VMARGS.equals(line))
					foundVmArgs = true;
				continue;
			}
			vmargs.add(line);
		}

		launcherData.setJvmArgs(null);
		launcherData.setJvmArgs(vmargs.toArray(new String[vmargs.size()]));
	}

	private void setJVMArgs(List<String> lines, LauncherData launcherData) {
		ParserUtils.removeArgument(EquinoxConstants.OPTION_VMARGS, lines);
		if (launcherData.getJvmArgs() == null || launcherData.getJvmArgs().length == 0)
			return;
		String[] args = launcherData.getJvmArgs();
		lines.add(EquinoxConstants.OPTION_VMARGS);
		for (String arg : args) {
			lines.add(arg);
		}
	}

	private void getProgramArgs(List<String> lines, LauncherData launcherData) {
		ArrayList<String> args = new ArrayList<>(lines.size());
		for (String line : lines) {
			if (EquinoxConstants.OPTION_VMARGS.equals(line))
				break;
			args.add(line);
		}
		launcherData.setProgramArgs(null);
		launcherData.setProgramArgs(args.toArray(new String[args.size()]));
	}

	private URI getLauncherLibrary(List<String> lines, URI launcherFolder) {
		String launcherLibrary = ParserUtils.getValueForArgument(EquinoxConstants.OPTION_LAUNCHER_LIBRARY, lines);
		if (launcherLibrary == null)
			return null;
		URI result = null;
		try {
			result = URIUtil.makeAbsolute(FileUtils.fromPath(launcherLibrary), launcherFolder);
			ParserUtils.setValueForArgument(EquinoxConstants.OPTION_LAUNCHER_LIBRARY, result.toString(), lines);
		} catch (URISyntaxException e) {
			Log.log(LogService.LOG_ERROR, NLS.bind(Messages.log_failed_make_absolute, launcherLibrary));
			return null;
		}
		return result;
	}

	private void setLauncherLibrary(List<String> lines, URI launcherFolder) {
		String launcherLibrary = ParserUtils.getValueForArgument(EquinoxConstants.OPTION_LAUNCHER_LIBRARY, lines);
		if (launcherLibrary == null)
			return;

		try {
			URI result = URIUtil.makeRelative(FileUtils.fromPath(launcherLibrary), launcherFolder);
			ParserUtils.setValueForArgument(EquinoxConstants.OPTION_LAUNCHER_LIBRARY, FileUtils.toPath(result).replace('\\', '/'), lines);
		} catch (URISyntaxException e) {
			Log.log(LogService.LOG_ERROR, NLS.bind(Messages.log_failed_make_absolute, launcherLibrary));
			return;
		}
	}

	private URI getConfigurationLocation(List<String> lines, URI osgiInstallArea, LauncherData data) {
		String configuration = ParserUtils.getValueForArgument(EquinoxConstants.OPTION_CONFIGURATION, lines);
		if (configuration == null)
			try {
				return URIUtil.makeAbsolute(new URI(CONFIGURATION_FOLDER), osgiInstallArea);
			} catch (URISyntaxException e1) {
				//ignore
			}

		URI result = null;
		try {
			result = URIUtil.makeAbsolute(FileUtils.fromPath(configuration), osgiInstallArea);
			ParserUtils.setValueForArgument(EquinoxConstants.OPTION_CONFIGURATION, result.toString(), lines);
			data.setFwConfigLocation(URIUtil.toFile(result));
		} catch (URISyntaxException e) {
			Log.log(LogService.LOG_ERROR, NLS.bind(Messages.log_failed_make_absolute, configuration));
			return null;
		}
		return result;
	}

	private void setConfigurationLocation(List<String> lines, URI osgiInstallArea, LauncherData data) {
		String result = FileUtils.toPath(URIUtil.makeRelative(data.getFwConfigLocation().toURI(), osgiInstallArea));
		//We don't write the default
		if (CONFIGURATION_FOLDER.equals(result)) {
			if (ParserUtils.getValueForArgument(EquinoxConstants.OPTION_CONFIGURATION, lines) != null)
				ParserUtils.removeArgument(EquinoxConstants.OPTION_CONFIGURATION, lines);
			return;
		}

		if (ParserUtils.getValueForArgument(EquinoxConstants.OPTION_CONFIGURATION, lines) == null) {
			ParserUtils.setValueForArgument(EquinoxConstants.OPTION_CONFIGURATION, result.replace('\\', '/'), lines);
		}
		return;
	}

	private URI getStartup(List<String> lines, URI launcherFolder) {
		String startup = ParserUtils.getValueForArgument(EquinoxConstants.OPTION_STARTUP, lines);
		if (startup == null)
			return null;

		URI result = null;
		try {
			result = URIUtil.makeAbsolute(FileUtils.fromPath(startup), launcherFolder);
			ParserUtils.setValueForArgument(EquinoxConstants.OPTION_STARTUP, result.toString(), lines);
		} catch (URISyntaxException e) {
			Log.log(LogService.LOG_ERROR, NLS.bind(Messages.log_failed_make_absolute, startup));
			return null;
		}
		return result;
	}

	private void setStartup(List<String> lines, URI launcherFolder) {
		String startup = ParserUtils.getValueForArgument(EquinoxConstants.OPTION_STARTUP, lines);
		if (startup == null)
			return;

		try {
			URI result = URIUtil.makeRelative(FileUtils.fromPath(startup), launcherFolder);
			ParserUtils.setValueForArgument(EquinoxConstants.OPTION_STARTUP, FileUtils.toPath(result).replace('\\', '/'), lines);
		} catch (URISyntaxException e) {
			Log.log(LogService.LOG_ERROR, NLS.bind(Messages.log_failed_make_relative, startup));
			return;
		}
	}

	void save(EquinoxLauncherData launcherData, boolean backup) throws IOException {
		File launcherConfigFile = EquinoxManipulatorImpl.getLauncherConfigLocation(launcherData);

		if (launcherConfigFile == null)
			throw new IllegalStateException(Messages.exception_launcherLocationNotSet);
		if (!Utils.createParentDir(launcherConfigFile)) {
			throw new IllegalStateException(Messages.exception_failedToCreateDir);
		}
		//Tweak all the values to make them relative
		File launcherFolder = launcherData.getLauncher().getParentFile();
		List<String> newlines = new ArrayList<>();
		newlines.addAll(Arrays.asList(launcherData.getProgramArgs()));

		setStartup(newlines, launcherFolder.toURI());
		setInstall(newlines, launcherData, launcherFolder);
		//Get the osgi install area
		File osgiInstallArea = ParserUtils.getOSGiInstallArea(newlines, null, launcherData);
		//setInstall(lines, osgiInstallArea, launcherFolder);
		setConfigurationLocation(newlines, osgiInstallArea.toURI(), launcherData);
		setLauncherLibrary(newlines, launcherFolder.toURI());
		//		setFrameworkJar(newlines, launcherData.getFwJar());
		setVM(newlines, launcherData.getJvm(), launcherFolder.toURI(), launcherData.getHome());

		//We are done, let's update the program args in the launcher data
		launcherData.setProgramArgs(null);
		launcherData.setProgramArgs(newlines.toArray(new String[newlines.size()]));

		//append jvm args
		setJVMArgs(newlines, launcherData);

		// backup file if exists.
		if (backup)
			if (launcherConfigFile.exists()) {
				File dest = Utils.getSimpleDataFormattedFile(launcherConfigFile);
				if (!launcherConfigFile.renameTo(dest))
					throw new IOException(NLS.bind(Messages.exception_failedToRename, launcherConfigFile, dest));
				Log.log(LogService.LOG_INFO, this, "save()", NLS.bind(Messages.log_renameSuccessful, launcherConfigFile, dest)); //$NON-NLS-1$
			}

		//only write the file if we actually have content
		if (newlines.size() > 0) {
			try (BufferedWriter bw = new BufferedWriter(new FileWriter(launcherConfigFile));) {
				for (String arg : newlines) {
					if (arg == null)
						continue;
					bw.write(arg);
					bw.newLine();
				}
				bw.flush();
				Log.log(LogService.LOG_INFO, NLS.bind(Messages.log_launcherConfigSave, launcherConfigFile));
			}
		}
		File previousLauncherIni = launcherData.getPreviousLauncherIni();
		if (previousLauncherIni != null && !previousLauncherIni.equals(launcherConfigFile))
			previousLauncherIni.delete();
		launcherData.setLauncherConfigLocation(launcherConfigFile);
	}
}
