/*
 * (c) Copyright Sysdeo SA 2001, 2002.
 * All Rights Reserved.
 */

package com.sysdeo.eclipse.tomcat;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.StringTokenizer;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectNature;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.IVMInstall;
import org.eclipse.jdt.launching.IVMInstallType;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;

import com.sysdeo.eclipse.tomcat.editors.ProjectListElement;


/**
 * The main plugin class to be used in the desktop.
 */
public class TomcatLauncherPlugin extends AbstractUIPlugin {

	/** TOMCAT_PREF_PROJECTSINCP_KEY */
	private static final String TOMCAT_PREF_PROJECTSINCP_KEY = "projectsInCp";
	/** TOMCAT_PREF_PROJECTSINSOURCEPATH_KEY */
	private static final String TOMCAT_PREF_PROJECTSINSOURCEPATH_KEY = "projectsInSourcePath";
	/** TOMCAT_PREF_TARGETPERSPECTIVE */
	private static final String TOMCAT_PREF_TARGETPERSPECTIVE = "targetPerspective";
	/** TOMCAT_HOME_CLASSPATH_VARIABLE */
	private static final String TOMCAT_HOME_CLASSPATH_VARIABLE = "TOMCAT_HOME";
	/** The shared instance. */
	private static final TomcatLauncherPlugin PLUGIN = new TomcatLauncherPlugin();

	/** Resource bundle. */
	private ResourceBundle resourceBundle;

	/**
	 * The constructor.
	 */
	public TomcatLauncherPlugin() {
		super();
		try {
			this.resourceBundle = ResourceBundle.getBundle("resources");
		} catch (final MissingResourceException ex) {
			this.resourceBundle = null;
			log(ex);
		}

		getWorkspace().addResourceChangeListener(new TomcatProjectChangeListener(), IResourceChangeEvent.PRE_DELETE);
	}

	/**
	 * Remove TOMCAT_HOME variable from Tomcat projects build path
	 * (Eclipse 3 will not compile Tomcat projects without this fix)
	 */
	private void fixTomcatHomeBug() {
		if (getPreferenceStore().getString("fixTomcatHomeBug").isEmpty()) {
			getPreferenceStore().setValue("fixTomcatHomeBug", "fixed");
			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();

			try {
				for (final IProject project : root.getProjects()) {
					if (project.hasNature(TomcatPluginResources.NATURE_ID)) {
						IJavaProject javaProject = JavaCore.create(project);

						List<IClasspathEntry> cp = new ArrayList<>();
						for (final IClasspathEntry entry : javaProject.getRawClasspath()) {
							if (entry.getEntryKind() != IClasspathEntry.CPE_VARIABLE
											|| !entry.getPath().equals(getTomcatIPath())) {
								cp.add(entry);
							}
						}

						javaProject.setRawClasspath(cp.toArray(new IClasspathEntry[cp.size()]), null);
					}
				}
			} catch (final CoreException e) {
				log(e);
			}
		}
	}

	/**
	 * Returns the shared instance.
	 * @return TomcatLauncherPlugin
	 */
	public static TomcatLauncherPlugin getDefault() {
		return PLUGIN;
	}

	/**
	 * Returns the workspace instance.
	 * @return IWorkspace
	 */
	public static IWorkspace getWorkspace() {
		return ResourcesPlugin.getWorkspace();
	}

	/**
	 * Returns the active shell for this plugin.
	 * @return Shell
	 */
	public static Shell getShell() {
		return getDefault().getWorkbench().getActiveWorkbenchWindow().getShell();
	}

	/**
	 * Returns the string from the plugin's resource bundle,
	 * or 'key' if not found.
	 * @param key String
	 * @return ResourceString
	 */
	public static String getResourceString(final String key) {
		ResourceBundle bundle = PLUGIN.getResourceBundle();
		try {
			return bundle.getString(key);
		} catch (final MissingResourceException e) {
			log(e);
			return key;
		}
	}

	/**
	 * Returns the plugin's resource bundle,
	 * @return ResourceBundle
	 */
	public ResourceBundle getResourceBundle() {
		try {
			this.resourceBundle = ResourceBundle.getBundle("resources");
		} catch (final MissingResourceException ex) {
			this.resourceBundle = null;
			log(ex);
		}
		return this.resourceBundle;
	}

	/**
	 *
	 * @return TomcatDir
	 */
	public static String getTomcatDir() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TomcatPluginResources.TOMCAT_PREF_HOME_KEY);
	}

	/**
	 *
	 * @return TomcatBase
	 */
	public static String getTomcatBase() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TomcatPluginResources.TOMCAT_PREF_BASE_KEY);
	}

	/**
	 *
	 * @return ConfigFile
	 */
	public static String getConfigFile() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TomcatPluginResources.TOMCAT_PREF_CONFIGFILE_KEY);
	}

	/**
	 *
	 * @return ConfigMode
	 */
	public static String getConfigMode() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TomcatPluginResources.TOMCAT_PREF_CONFMODE_KEY);
	}

	/**
	 *
	 * @return ContextsDir
	 */
	public static String getContextsDir() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TomcatPluginResources.TOMCAT_PREF_CONTEXTSDIR_KEY);
	}

	/**
	 *
	 * @return TomcatVersion
	 */
	public static String getTomcatVersion() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		String result = pref.getString(TomcatPluginResources.TOMCAT_PREF_VERSION_KEY);
		return result;
	}

	/**
	 *
	 * @return TomcatJRE
	 */
	public static String getTomcatJRE() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		String result = pref.getString(TomcatPluginResources.TOMCAT_PREF_JRE_KEY);
		if (result.isEmpty()) {
			result = JavaRuntime.getDefaultVMInstall().getId();
		}
		return result;
	}

	/**
	 *
	 * @return isDebugMode
	 */
	public static boolean isDebugMode() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return !pref.getBoolean(TomcatPluginResources.TOMCAT_PREF_DEBUGMODE_KEY);
	}

	/**
	 *
	 * @return TargetPerspective
	 */
	public static String getTargetPerspective() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TOMCAT_PREF_TARGETPERSPECTIVE);
	}

	/**
	 *
	 * @return isSecurityManagerEnabled
	 */
	public static boolean isSecurityManagerEnabled() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getBoolean(TomcatPluginResources.TOMCAT_PREF_SECURITYMANAGER);
	}

	/**
	 *
	 * @return JvmParamaters
	 */
	public static String getJvmParamaters() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TomcatPluginResources.TOMCAT_PREF_JVM_PARAMETERS_KEY);
	}

	/**
	 *
	 * @return JvmClasspath
	 */
	public static String getJvmClasspath() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TomcatPluginResources.TOMCAT_PREF_JVM_CLASSPATH_KEY);
	}

	/**
	 *
	 * @return JvmBootClasspath
	 */
	public static String getJvmBootClasspath() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TomcatPluginResources.TOMCAT_PREF_JVM_BOOTCLASSPATH_KEY);
	}

	/**
	 *
	 * @return TomcatBootstrap
	 */
	public static TomcatBootstrap getTomcatBootstrap() {
		TomcatBootstrap tomcatBootstrap = null;

		//		if (getTomcatVersion().equals(TOMCAT_VERSION3)) {
		//			tomcatBootsrap = new Tomcat3Bootstrap();
		//		}
		//		if (getTomcatVersion().equals(TOMCAT_VERSION4)) {
		//			tomcatBootsrap = new Tomcat4Bootstrap();
		//		}
		//		if (getTomcatVersion().equals(TOMCAT_VERSION41)) {
		//			tomcatBootsrap = new Tomcat41Bootstrap();
		//		}
		//		if (getTomcatVersion().equals(TOMCAT_VERSION5)) {
		//			tomcatBootsrap = new Tomcat5Bootstrap();
		//		}
		//		if (getTomcatVersion().equals(TOMCAT_VERSION6)) {
		//			tomcatBootsrap = new Tomcat6Bootstrap();
		//		}

		if (getTomcatVersion().equals(TomcatPluginResources.TOMCAT_VERSION7)) {

			tomcatBootstrap = new Tomcat7Bootstrap(
					getConfigMode().equals(TomcatPluginResources.SERVERXML_MODE), getConfigFile(),
					isSecurityManagerEnabled(), getTomcatIPath());
			setProperties(tomcatBootstrap);

		} else if (getTomcatVersion().equals(TomcatPluginResources.TOMCAT_VERSION8)) {

			tomcatBootstrap = new Tomcat8Bootstrap(
					getConfigMode().equals(TomcatPluginResources.SERVERXML_MODE), getConfigFile(),
					isSecurityManagerEnabled(), getTomcatIPath());
			setProperties(tomcatBootstrap);

		} else if (getTomcatVersion().equals(TomcatPluginResources.TOMCAT_VERSION9)) {

			tomcatBootstrap = new Tomcat9Bootstrap(
					getConfigMode().equals(TomcatPluginResources.SERVERXML_MODE), getConfigFile(),
					isSecurityManagerEnabled(), getTomcatIPath());
			setProperties(tomcatBootstrap);

		}

		return tomcatBootstrap;
	}

	/**
	 * パラメータ設定
	 * @param tomcatBootstrap TomcatBootstrap
	 */
	private static void setProperties(final TomcatBootstrap tomcatBootstrap) {
		String[] classpath = addPreferenceJvmToClasspath(new String[0]);
		classpath = addPreferenceProjectListToClasspath(classpath);
		String[] vArgs = addPreferenceParameters(new String[0]);
		String[] bootClasspath = addPreferenceJvmToBootClasspath(new String[0]);

		tomcatBootstrap.setDebugMode(isDebugMode());
		tomcatBootstrap.setTomcatDir(getTomcatDir());
		tomcatBootstrap.setTomcatBase(getTomcatBase());
		tomcatBootstrap.setProjects(getProjectMap());
		tomcatBootstrap.setClasspath(classpath);
		tomcatBootstrap.setVmArgs(vArgs);
		tomcatBootstrap.setBootClasspath(bootClasspath);
		tomcatBootstrap.setVmInstalled(getVMInstall());
		tomcatBootstrap.setElements(PLUGIN.getProjectsInSourcePath());
	}

	/**
	 *
	 * @return ManagerAppUrl
	 */
	public static String getManagerAppUrl() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TomcatPluginResources.TOMCAT_PREF_MANAGER_URL);
	}

	/**
	 *
	 * @return ManagerAppUser
	 */
	public static String getManagerAppUser() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TomcatPluginResources.TOMCAT_PREF_MANAGER_USER);
	}

	/**
	 *
	 * @return ManagerAppPassword
	 */
	public static String getManagerAppPassword() {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		return pref.getString(TomcatPluginResources.TOMCAT_PREF_MANAGER_PASSWORD);
	}

	/**
	 *
	 * @param msg message
	 */
	public static void log(final String msg) {
		ILog log = PLUGIN.getLog();
		Status status = new Status(IStatus.ERROR, PLUGIN.getBundle().getSymbolicName(),
						IStatus.ERROR, msg + "\n", null);
		log.log(status);
	}

	/**
	 *
	 * @param ex Exception
	 */
	public static void log(final Exception ex) {
		ILog log = PLUGIN.getLog();
		StringWriter stringWriter = new StringWriter();
	    ex.printStackTrace(new PrintWriter(stringWriter));
		String msg = stringWriter.getBuffer().toString();

		Status status = new Status(IStatus.ERROR, PLUGIN.getBundle().getSymbolicName(),
						IStatus.ERROR, msg, null);
		log.log(status);
	}

	/**
	 *
	 * @return TomcatIPath
	 */
	public static IPath getTomcatIPath() {
		IPath tomcatPath = getTomcatClasspathVariable();
		if (tomcatPath == null) {
			return new Path(getTomcatDir());
		}
		return new Path(TOMCAT_HOME_CLASSPATH_VARIABLE);
	}

	/**
	 *
	 * @return TomcatClasspathVariable
	 */
	private static IPath getTomcatClasspathVariable() {
		IPath tomcatPath = JavaCore.getClasspathVariable(TOMCAT_HOME_CLASSPATH_VARIABLE);
		if (tomcatPath == null) {
			initTomcatClasspathVariable();
			tomcatPath = JavaCore.getClasspathVariable(TOMCAT_HOME_CLASSPATH_VARIABLE);
		}
		return tomcatPath;
	}

	/**
	 * initTomcatClasspathVariable
	 */
	public static void initTomcatClasspathVariable() {
		try {
			JavaCore.setClasspathVariable(TOMCAT_HOME_CLASSPATH_VARIABLE,
							new Path(getTomcatDir()), null);
		} catch (final JavaModelException e) {
			log(e);
		}
	}

	/**
	 *
	 * @param projectsInCP List<ProjectListElement>
	 */
	public static void setProjectsInCP(final List<ProjectListElement> projectsInCP) {
		saveProjectsToPreferenceStore(projectsInCP, TOMCAT_PREF_PROJECTSINCP_KEY);
	}

	/**
	 *
	 * @return ProjectsInCP
	 */
	public static List<ProjectListElement> getProjectsInCP() {
		return readProjectsFromPreferenceStore(TOMCAT_PREF_PROJECTSINCP_KEY);
	}

	/**
	 *
	 * @param projectsInCP List<ProjectListElement>
	 */
	public static void setProjectsInSourcePath(final List<ProjectListElement> projectsInCP) {
		saveProjectsToPreferenceStore(projectsInCP, TOMCAT_PREF_PROJECTSINSOURCEPATH_KEY);
	}

	/**
	 *
	 * @return ProjectsInSourcePath
	 */
	public List<ProjectListElement> getProjectsInSourcePath() {
		IPreferenceStore pref = getPreferenceStore();
		boolean automaticallyComputed = pref.getBoolean(TomcatPluginResources.TOMCAT_PREF_COMPUTESOURCEPATH_KEY);
		if (automaticallyComputed) {
			return computeProjectsInSourcePath();
		}
		return readProjectsInSourcePathFromPref();
	}

	/**
	 *
	 * @return List<ProjectListElement>
	 */
	public List<ProjectListElement> readProjectsInSourcePathFromPref() {
		IPreferenceStore pref = getPreferenceStore();
		if (!(pref.contains(TOMCAT_PREF_PROJECTSINSOURCEPATH_KEY))) {
			// Compute source path for a new workspace
			pref.setValue(TomcatPluginResources.TOMCAT_PREF_COMPUTESOURCEPATH_KEY, true);
			return computeProjectsInSourcePath();
		}
		return readProjectsFromPreferenceStore(TOMCAT_PREF_PROJECTSINSOURCEPATH_KEY);
	}

	/**
	 *
	 * @return List<ProjectListElement>
	 */
	private List<ProjectListElement> computeProjectsInSourcePath() {
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		IProject[] allProjects = root.getProjects();

		// Since version 3.2 final, default source path contains all opened Java projects
		// Previously we add Tomcat projects and their required Java projects to source path
		// For beginner this should make thing easier.

		ArrayList<ProjectListElement> tempList = new ArrayList<>(allProjects.length);

		Set<IProject> alreadyAdded = new HashSet<>();
		for (final IProject project : allProjects) {
			try {
				if ((project.isOpen()) && project.hasNature(JavaCore.NATURE_ID)) {
					IProjectNature javaProject = project.getNature(JavaCore.NATURE_ID);
					if (!alreadyAdded.contains(project)) {
						tempList.add(new ProjectListElement(javaProject.getProject()));
						alreadyAdded.add(project);
					}
				}
			} catch (final CoreException e) {
				log(e);
			}
		}
		return tempList;
	}

	/**
	 *
	 * @param projectList List<ProjectListElement>
	 * @param keyInPreferenceStore String
	 */
	static void saveProjectsToPreferenceStore(
					final List<ProjectListElement> projectList, final String keyInPreferenceStore) {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		StringBuilder buf = new StringBuilder();
		Iterator<ProjectListElement> it = projectList.iterator();
		while (it.hasNext()) {
			ProjectListElement each = it.next();
			buf.append(each.getID());
			buf.append(';');
		}
		pref.setValue(keyInPreferenceStore, buf.toString());
	}

	/**
	 *
	 * @param keyInPreferenceStore String
	 * @return List<ProjectListElement>
	 */
	static List<ProjectListElement> readProjectsFromPreferenceStore(final String keyInPreferenceStore) {
		IPreferenceStore pref = PLUGIN.getPreferenceStore();
		String stringList = pref.getString(keyInPreferenceStore);

		List<String> projectsIdList = new ArrayList<>();
		StringTokenizer tokenizer = new StringTokenizer(stringList, ";");
		while (tokenizer.hasMoreElements()) {
			projectsIdList.add(tokenizer.nextToken());
		}

		return ProjectListElement.stringsToProjectsList(projectsIdList);
	}

	/**
	 *
	 * @return checkTomcatSettingsAndWarn
	 */
	public static boolean checkTomcatSettingsAndWarn() {
		if (!isTomcatConfigured()) {
			String msg = getResourceString("msg.noconfiguration");
			MessageDialog.openWarning(getShell(), "Tomcat", msg);
			return false;
		}
		return true;
	}

	/**
	 *
	 * @return isTomcatConfigured
	 */
	public static boolean isTomcatConfigured() {
		return !(getTomcatDir().isEmpty());
	}

    /**
     * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
     */
    @Override
	public void start(final BundleContext context) throws Exception {
    	super.start(context);
		fixTomcatHomeBug();
    }

	/**
	 *
	 * @param previouscp String[]
	 * @return String[]
	 */
	private static String[] addPreferenceProjectListToClasspath(final String[] previouscp) {
		List<ProjectListElement> projectsList = getProjectsInCP();
		String[] result = previouscp;
		Iterator<ProjectListElement> it = projectsList.iterator();
		while (it.hasNext()) {
			try {
				ProjectListElement ple = it.next();
				IJavaProject jproject = JavaCore.create(ple.getProject());
				result = addProjectToClasspath(result, jproject);
			} catch (final CoreException e) {
				// nothing will be added to classpath
				log(e);
			}
		}
		return result;
	}

	/**
	 *
	 * @param previouscp String[]
	 * @param project IJavaProject
	 * @return String[]
	 * @throws CoreException CoreException
	 */
	private static String[] addProjectToClasspath(final String[] previouscp,
					final IJavaProject project) throws CoreException {
		if ((project != null) && (project.exists() && project.isOpen())) {
			String[] projectcp = JavaRuntime.computeDefaultRuntimeClassPath(project);
			return StringUtil.concatUniq(projectcp, previouscp);
		}
		return previouscp;
	}

	/**
	 *
	 * @param previous String[]
	 * @return String[]
	 */
	private static String[] addPreferenceParameters(final String[] previous) {
		String[] prefParams = StringUtil.cutString(getJvmParamaters(),
				TomcatPluginResources.PREF_PAGE_LIST_SEPARATOR);
		return StringUtil.concat(previous, prefParams);
	}

	/**
	 *
	 * @param previous String[]
	 * @return String[]
	 */
	private static String[] addPreferenceJvmToClasspath(final String[] previous) {
		String[] prefClasspath = StringUtil.cutString(getJvmClasspath(),
				TomcatPluginResources.PREF_PAGE_LIST_SEPARATOR);
		return StringUtil.concatUniq(previous, prefClasspath);
	}

	/**
	 *
	 * @param previous String[]
	 * @return String[]
	 */
	private static String[] addPreferenceJvmToBootClasspath(final String[] previous) {
		String[] prefBootClasspath = StringUtil.cutString(getJvmBootClasspath(),
				TomcatPluginResources.PREF_PAGE_LIST_SEPARATOR);
		return StringUtil.concatUniq(previous, prefBootClasspath);
	}

	/**
	 *
	 * @return projectMap
	 */
	private static Map<IProject, IProjectNature> getProjectMap() {
		Map<IProject, IProjectNature> ret = new HashMap<>();

		IProject[] projects = getWorkspace().getRoot().getProjects();
		for (final IProject project: projects) {
			if (project.isOpen()) {
				try {
					IProjectNature nature = project.getNature(TomcatPluginResources.NATURE_ID);
					if (nature != null && IJavaProject.class.isInstance(project.getNature(JavaCore.NATURE_ID))) {
						ret.put(project, nature);
					}
				} catch (final CoreException e) {
					log(e);
				}
			}
		}
		return ret;
	}

	/**
	 *
	 * @return VMInstall
	 */
	private static IVMInstall getVMInstall() {
		for (IVMInstallType type : JavaRuntime.getVMInstallTypes()) {
			for (final IVMInstall vms : type.getVMInstalls()) {
				if (vms.getId().equals(getTomcatJRE())) {
					return vms;
				}
			}
		}
		return JavaRuntime.getDefaultVMInstall();
	}
}
