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

package com.sysdeo.eclipse.tomcat;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectNature;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.QualifiedName;
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 com.sysdeo.eclipse.tomcat.editors.ProjectListElement;

/**
 * TomcatProject
 */
public class TomcatProject extends PlatformObject implements IProjectNature, ITomcatProject {

	// Persistence properties of projects
	/** PROPERTIES_FILENAME */
	private static final String PROPERTIES_FILENAME = ".tomcatplugin";
	/** KEY_WEBPATH */
	private static final String KEY_WEBPATH = "webPath";
	/** KEY_UPDATEXML */
	private static final String KEY_UPDATEXML = "updateXml";
	/** KEY_EXPORTSOURCE */
	private static final String KEY_EXPORTSOURCE = "exportSource";
	/** KEY_RELOADABLE */
	private static final String KEY_RELOADABLE = "reloadable";
	/** KEY_REDIRECTLOGGER */
	private static final String KEY_REDIRECTLOGGER = "redirectLogger";
	/** KEY_WARLOCATION */
	private static final String KEY_WARLOCATION = "warLocation";
	/** KEY_ROOTDIR */
	private static final String KEY_ROOTDIR = "rootDir";
	/** KEY_WORKDIR */
	private static final String KEY_WORKDIR = "workDir";
	/** KEY_EXTRAINFO */
	private static final String KEY_EXTRAINFO = "extraInfo";
	/** EXTRA_BEGIN_TAG */
	private static final String EXTRA_BEGIN_TAG = "<!-- Extra info begin -->";
	/** EXTRA_END_TAG */
	private static final String EXTRA_END_TAG = "<!-- Extra info end -->";

	// The platform project this <code>TomcatProject</code> is based on
	/** IProject */
	private IProject project;
	/** IJavaProject */
	private IJavaProject javaProject;
	/** webPath */
	private String webPath = "";
	/** warLocation */
	private String warLocation = "";
	/** rootDir */
	private String rootDir = "";
	/** workDir */
	private String workDir = "";
	/** extraInfo */
	private String extraInfo = "";
	/** updateXml */
	private boolean updateXml;
	/** exportSource */
	private boolean exportSource;
	/** reloadable */
	private boolean reloadable = true;
	/** redirectLogger */
	private boolean redirectLogger = false;
	/** WebClassPathEntries */
	private WebClassPathEntries webClassPathEntries;
	/** IFolder */
	private IFolder rootDirFolder;

	/**
	 * Gets the project.
	 * @return Returns a IProject
	 */
	@Override
	public IProject getProject() {
		return this.project;
	}

	/**
	 * Sets the project.
	 * @param prj The project to set
	 */
	@Override
	public void setProject(final IProject prj) {
		this.project = prj;
	}

	/***
	 * @see IProjectNature#configure()
	 */
	@Override
	public void configure() throws CoreException {
		return;
	}

	/**
	 * @see IProjectNature#deconfigure()
	 */
	@Override
	public void deconfigure() throws CoreException {
		return;
	}

	/**
	 * @see IProjectNature#getProject()
	 * @return IJavaProject
	 */
	public IJavaProject getJavaProject() {
		return this.javaProject;
	}

	/**
	 * @see IProjectNature#setProject(IProject)
	 * @param prj IJavaProject
	 */
	@Override
	public void setJavaProject(final IJavaProject prj) {
		this.javaProject = prj;
		setProject(prj.getProject());
	}

	/**
	 *
	 * @param project IJavaProject
	 */
	public static void addTomcatNature(final IJavaProject project) {
		try {
			JDTUtil.addNatureToProject(project.getProject(), TomcatPluginResources.NATURE_ID);
		} catch (final CoreException ex) {
			TomcatLauncherPlugin.log(ex.getMessage());
		}
	}

	/**
	 *
	 * @param project IJavaProject
	 */
	public static void removeTomcatNature(final IJavaProject project) {
		try {
			JDTUtil.removeNatureToProject(project.getProject(), TomcatPluginResources.NATURE_ID);
		} catch (final CoreException ex) {
			TomcatLauncherPlugin.log(ex.getMessage());
		}
	}

	/**
	 * Return a TomcatProject if this javaProject has the tomcat nature
	 * Return null if Project has not tomcat nature
	 *
	 * @param javaProject IJavaProject
	 * @return TomcatProject
	 */
	public static TomcatProject create(final IJavaProject javaProject) {
		TomcatProject result = null;
		try {
			result = (TomcatProject)javaProject.getProject().getNature(TomcatPluginResources.NATURE_ID);
			if (result != null) {
				result.setJavaProject(javaProject);
			}
		} catch (final CoreException ex) {
			TomcatLauncherPlugin.log(ex.getMessage());
		}
		return result;
	}

	/**
	 * Return a TomcatProject if this Project has the tomcat nature
	 * Return null if Project has not tomcat nature
	 * @param project IProject
	 * @return TomcatProject
	 */
	public static TomcatProject create(final IProject project) {

		IJavaProject javaProject = JavaCore.create(project);
		if (javaProject != null) {
			return create(javaProject);
		}
		return null;
	}

	/**
	 *
	 * @return File
	 */
	private File getPropertiesFile() {
		return getProject().getLocation().append(PROPERTIES_FILENAME).toFile();
	}

	/**
	 *
	 * @param key String
	 * @return String
	 */
	private String readProperty(final String key) {
		String result = null;
		try {
			result = FileUtil.readPropertyInXMLFile(getPropertiesFile(), key);
		} catch (final IOException e) {
			TomcatLauncherPlugin.log(e);

			try {
				result = getJavaProject().getCorrespondingResource().getPersistentProperty(
						new QualifiedName("TomcatProject", key));
			} catch (final CoreException ex) {
				TomcatLauncherPlugin.log(ex);
			}
		}

		if (result == null) {
			result = "";
		}

		return result;
	}

	/**
	 * Gets the rootDir.
	 * @return Returns a String
	 */
	public String getRootDir() {
		return readProperty(KEY_ROOTDIR);
	}

	/**
	 * Sets the rootDir.
	 * @param rd The rootDir to set
	 */
	public void setRootDir(final String rd) {
		this.rootDir = rd;
		this.rootDirFolder = null;
	}

	/**
	 * Gets the workDir.
	 * @return Returns a String
	 */
	public String getWorkDir() {
		return readProperty(KEY_WORKDIR);
	}

	/**
	 * Sets the workDir.
	 * @param wd The workDir to set
	 */
	public void setWorkDir(final String wd) {
		this.workDir = wd;
	}

	/**
	 * Gets the webpath.
	 * @return Returns a String
	 */
	public String getWebPath() {
		return readProperty(KEY_WEBPATH);
	}

	/**
	 * Sets the webpath.
	 * @param wp The webpath to set
	 */
	public void setWebPath(final String wp) {
		this.webPath = wp;
	}

	/**
	 *
	 * @param newWebPath String
	 * @throws CoreException CoreException
	 */
	public void updateWebPath(final String newWebPath) throws CoreException {
		setWebPath(newWebPath);
		if (!newWebPath.equals(getWebPath())) {
			if (getUpdateXml()) {
				removeContext();
			}
		}
	}

	/**
	 * Gets the warfile.
	 * @return Returns a String
	 */
	public String getWarLocation() {
		return this.readProperty(KEY_WARLOCATION);
	}

	/**
	 * Sets the warfile
	 * @param wl The warfile to set
	 */
	public void setWarLocation(final String wl) {
		this.warLocation = wl;
	}

	/**
	 * Gets the updateXml.
	 * @return Returns a boolean
	 */
	public boolean getUpdateXml() {
		return Boolean.parseBoolean(readProperty(KEY_UPDATEXML));
	}

	/**
	 * Sets the updateXml.
	 * @param xml The updateXml to set
	 */
	public void setUpdateXml(final boolean xml) {
		this.updateXml = xml;
	}

	/**
	 * Gets the updateXml.
	 * @return Returns a boolean
	 */
	public boolean getExportSource() {
		return Boolean.parseBoolean(readProperty(KEY_EXPORTSOURCE));
	}

	/**
	 * Sets the exportSource.
	 * @param exp The exportSource to set
	 */
	public void setExportSource(final boolean exp) {
		this.exportSource = exp;
	}

	/**
	 * Gets the reloadable
	 * @return Returns a boolean
	 */
	public boolean getReloadable() {
		String reloadableProperty = readProperty(KEY_RELOADABLE);
		// Set default value to true
		if (reloadableProperty.isEmpty()) {
			reloadableProperty = "true";
		}
		return Boolean.parseBoolean(reloadableProperty);
	}

	/**
	 * Sets the reloadable
	 * @param enable The reloadable to set
	 */
	public void setReloadable(final boolean enable) {
		this.reloadable = enable;
	}

	/**
	 * Gets the reloadable
	 * @return Returns a boolean
	 */
	public boolean getRedirectLogger() {
		String redirectLoggerProperty = readProperty(KEY_REDIRECTLOGGER);
		// Set default value to false
		if (redirectLoggerProperty.isEmpty()) {
			redirectLoggerProperty = "false";
		}
		return Boolean.parseBoolean(redirectLoggerProperty);
	}

	/**
	 * Sets the reloadable
	 * @param logger The reloadable to set
	 */
	public void setRedirectLogger(final boolean logger) {
		this.redirectLogger = logger;
	}

	/**
	 * Gets the warfile.
	 * @return Returns a String
	 */
	public String getExtraInfo() {
		try {
			return URLDecoder.decode(readProperty(KEY_EXTRAINFO), "UTF-8");
		} catch (final UnsupportedEncodingException e) {
			TomcatLauncherPlugin.log(e);
			return null;
		}
	}

	/**
	 * Sets the warfile
	 * @param extra The warfile to set
	 */
	public void setExtraInfo(final String extra) {
		this.extraInfo = extra;
	}

	/**
	 * set the classpath entries which shall be loaded by the webclassloader
	 * @param entries List of WebClasspathEntry objects
	 */
	public void setWebClassPathEntries(final WebClassPathEntries entries) {
		this.webClassPathEntries = entries;
	}

	/**
	 * return the webclasspath entries
	 * @return WebClassPathEntries
	 */
	@Override
	public WebClassPathEntries getWebClassPathEntries() {
		try {
			return WebClassPathEntries.xmlUnmarshal(FileUtil.readTextFile(getPropertiesFile()));
		} catch (final IOException ex) {
			TomcatLauncherPlugin.log(ex);
			return null;
		}
	}

	/**
	 * Store exportSource in project persistent properties
	 */
	public void saveProperties() {
		try {
			StringBuilder fileContent = new StringBuilder();
			fileContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
			fileContent.append("<tomcatProjectProperties>\n");
			fileContent.append("    <rootDir>" + this.rootDir + "</rootDir>\n");
			fileContent.append("    <workDir>" + this.workDir + "</workDir>\n");
			fileContent.append("    <exportSource>" + this.exportSource + "</exportSource>\n");
			fileContent.append("    <reloadable>" + this.reloadable + "</reloadable>\n");
			fileContent.append("    <redirectLogger>" + this.redirectLogger + "</redirectLogger>\n");
			fileContent.append("    <updateXml>" + this.updateXml + "</updateXml>\n");
			fileContent.append("    <warLocation>" + this.warLocation + "</warLocation>\n");
			fileContent.append("    <extraInfo>" + URLEncoder.encode(
							this.extraInfo, StandardCharsets.UTF_8.name()) + "</extraInfo>\n");
			fileContent.append("    <webPath>" + this.webPath + "</webPath>\n");
			if (this.webClassPathEntries != null) {
				fileContent.append(this.webClassPathEntries.xmlMarshal(4));
			}
			fileContent.append("</tomcatProjectProperties>\n");
			FileUtil.toTextFile(getPropertiesFile(), fileContent.toString());
			// refresh the project files.
			this.project.refreshLocal(IResource.DEPTH_ONE, null);

			//Notification for VCM (Team plug-in)
			IFile propertiesIFile = getProject().getFile(PROPERTIES_FILENAME);
			ResourcesPlugin.getWorkspace().validateEdit(new IFile[]{propertiesIFile}, null);


		} catch (final CoreException | IOException ex) {
			TomcatLauncherPlugin.log(ex.getMessage());
		}
	}

	/**
	 * Run all the steps to configure a JavaProject as a TomcatProject
	 * @throws CoreException CoreException
	 */
	public void fullConfiguration() throws CoreException {
		if (!this.rootDir.isEmpty()) {
			initRootDirFolder(true);
		}

		addProjectToSourcePathPref();
		createWebInfFolder();
		createWebInfSrcFolder();
		createWorkFolder();

		addTomcatJarToProjectClasspath();
		addWebInfLibJarFilesToProjectClasspath();

		clearDefaultSourceEntries();
		setClassesAsOutputFolder();
		if (classesContainsJavaFiles()) {
			setClassesAsSourceFolder();
		}

		setWebInfSrcAsSourceFolder();
		setWorkAsSourceFolder();

		updateContext();
	}

	/**
	 *
	 * @throws CoreException CoreException
	 */
	public void clearDefaultSourceEntries() throws CoreException {
		List<IClasspathEntry> cp = new ArrayList<>();
		for (final IClasspathEntry entry : this.javaProject.getRawClasspath()) {
			if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) {
				cp.add(entry);
			}
		}
		this.javaProject.setRawClasspath(cp.toArray(new IClasspathEntry[cp.size()]), null);
	}

	/**
	 * Add servlet.jar and jasper.jar to project classpath
	 * @throws CoreException CoreException
	 */
	public void addTomcatJarToProjectClasspath() throws CoreException {
		TomcatBootstrap tb = TomcatLauncherPlugin.getTomcatBootstrap();
		List<IClasspathEntry> cp = new ArrayList<>();
		for (final IClasspathEntry entry : this.javaProject.getRawClasspath()) {
			if (!((entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE)
							&& (entry.getPath().toOSString().startsWith(
									TomcatLauncherPlugin.getTomcatIPath().toOSString())))) {
				cp.add(entry);
			}
		}
		cp.addAll(tb.getTomcatJars(TomcatLauncherPlugin.getTomcatIPath()));

		this.javaProject.setRawClasspath(cp.toArray(new IClasspathEntry[cp.size()]), null);
	}

	/**
	 * Add all jar in WEB-INF/lib to project classpath
	 * @throws CoreException CoreException
	 */
	public void addWebInfLibJarFilesToProjectClasspath() throws CoreException {
		IFolder libFolder = getWebInfFolder().getFolder("lib");
		IResource[] libFiles = libFolder.members();

		IClasspathEntry[] entries = this.javaProject.getRawClasspath();
		List<IClasspathEntry> cp = new ArrayList<>(entries.length + 1);
		cp.addAll(Arrays.asList(entries));

		for (final IResource resource : libFiles) {
			if ((resource.getType() == IResource.FILE) && ("jar".equalsIgnoreCase(resource.getFileExtension()))) {
				cp.add(JavaCore.newLibraryEntry(resource.getFullPath(), null, null));
			}
		}

		this.javaProject.setRawClasspath(cp.toArray(new IClasspathEntry[cp.size()]), null);
	}

	/**
	 *
	 * @return IFolder
	 */
	public IFolder getWebInfFolder() {
		if (getRootDirFolder() == null) {
			return this.project.getFolder("WEB-INF");
		}
		return getRootDirFolder().getFolder("WEB-INF");
	}

	/**
	 *
	 * @return IFolder
	 */
	public IPath getWorkFolder() {
		String workFolder = getWorkDir();
		if (workFolder.isEmpty()) {
			workFolder = TomcatLauncherPlugin.getTomcatDir() + "/work";
		}
		return new Path(workFolder);
	}

	/**
	 *
	 * @return IFolder
	 */
	@Override
	public IFolder getRootDirFolder() {
		if (this.rootDirFolder == null) {
			initRootDirFolder(false);
		}
		return this.rootDirFolder;
	}

	/**
	 *
	 * @param create boolean
	 */
	private void initRootDirFolder(final boolean create) {
		StringTokenizer tokenizer = new StringTokenizer(getRootDir(), "/\\:");
		IFolder folder = null;
		try {
			while (tokenizer.hasMoreTokens()) {
				String each = tokenizer.nextToken();
				if (folder == null) {
					folder = this.project.getFolder(each);
				} else {
					folder = folder.getFolder(each);
				}
				if (create) {
					createFolder(folder);
				}
			}
		} catch (final CoreException ex) {
			TomcatLauncherPlugin.log(ex);
			folder = null;
			setRootDir("/");
		}
		this.rootDirFolder = folder;
	}

	/**
	 *
	 * @throws CoreException CoreException
	 */
	public void createWebInfFolder() throws CoreException {
		IFolder webinfFolder = getWebInfFolder();
		createFolder(webinfFolder);
		createFolder(webinfFolder.getFolder("classes"));
		createFolder(webinfFolder.getFolder("lib"));

		// Create .cvsignore for classes
		createFile(webinfFolder.getFile(".cvsignore"), "classes");
	}

	/**
	 *
	 * @throws CoreException CoreException
	 */
	public void createWebInfSrcFolder() throws CoreException {
		createFolder(getWebInfFolder().getFolder("src"));
	}

	/**
	 *
	 * @throws CoreException CoreException
	 */
	public void createWorkFolder() throws CoreException {
		IPath folderHandle = getWorkFolder();
		if (!folderHandle.toFile().exists()) {
			if (!folderHandle.toFile().mkdirs()) {
				throw new CoreException(new Status(IStatus.ERROR,
						TomcatLauncherPlugin.getDefault().getBundle().getSymbolicName(),
						" create directory failed.:" + folderHandle.toFile().toString()));
			}
		}

		// Add a .cvsignore file in work directory
		//createFile(this.project.getFile(".cvsignore"), "work");
	}

	/**
	 *
	 * @throws CoreException CoreException
	 */
	public void setWebInfSrcAsSourceFolder() throws CoreException {
		setFolderAsSourceEntry(getWebInfFolder().getFolder("src"), null);
	}

	/**
	 *
	 * @throws CoreException CoreException
	 */
	public void setClassesAsOutputFolder() throws CoreException {
		IFolder classesFolder = getWebInfFolder().getFolder("classes");
		this.javaProject.setOutputLocation(classesFolder.getFullPath(), null);
	}

	/**
	 *
	 * @throws CoreException CoreException
	 */
	public void setClassesAsSourceFolder() throws CoreException {
		IFolder classesFolder = getWebInfFolder().getFolder("classes");
		setFolderAsSourceEntry(classesFolder, null);
	}

	/**
	 *
	 * @throws CoreException CoreException
	 */
	public void setWorkAsSourceFolder() throws CoreException {
		setFolderAsSourceEntry(getWorkFolder(), getWorkFolder());
	}

	/**
	 *
	 * @param folderHandle IFolder
	 * @throws CoreException CoreException
	 */
	private void createFolder(final IFolder folderHandle) throws CoreException {
		try {
			// Create the folder resource in the workspace
			folderHandle.create(false, true, null);
		} catch (final CoreException e) {
			// If the folder already existed locally, just refresh to get contents
			if (e.getStatus().getCode() == IResourceStatus.PATH_OCCUPIED) {
				folderHandle.refreshLocal(IResource.DEPTH_INFINITE, null);
			}
			throw e;
		}
	}

	/**
	 *
	 * @param fileHandle IFile
	 * @param content String
	 * @throws CoreException CoreException
	 */
	private void createFile(final IFile fileHandle, final String content) throws CoreException {
		try {
			fileHandle.create(new ByteArrayInputStream(content.getBytes(StandardCharsets.ISO_8859_1)), 0, null);
		} catch (final CoreException e) {
			// If the file already existed locally, just refresh to get contents
			if (e.getStatus().getCode() == IResourceStatus.PATH_OCCUPIED) {
				fileHandle.refreshLocal(IResource.DEPTH_INFINITE, null);
			}
			throw e;
		}
	}


	/**
	 * ouput could be null (project default output will be used)
	 * @param folderHandle IFolder
	 * @param output IFolder
	 * @throws CoreException CoreException
	 */
	private void setFolderAsSourceEntry(final IFolder folderHandle, final IFolder output) throws CoreException {
		IPath outputPath = null;
		if (output != null) {
			outputPath = output.getFullPath();
		}
		setFolderAsSourceEntry(folderHandle.getFullPath(), outputPath);
	}


	/**
	 * ouput could be null (project default output will be used)
	 * @param folderPath IPath
	 * @param outputPath IPath
	 * @throws CoreException CoreException
	 */
	private void setFolderAsSourceEntry(final IPath folderPath, final IPath outputPath) throws CoreException {
		IClasspathEntry[] entries = this.javaProject.getRawClasspath();
		IClasspathEntry[] newEntries = new IClasspathEntry[entries.length + 1];
		System.arraycopy(entries, 0, newEntries, 0, entries.length);
		IPath[] emptyPath = {};
		newEntries[entries.length] = JavaCore.newSourceEntry(folderPath, emptyPath, outputPath);

		this.javaProject.setRawClasspath(newEntries, null);
	}


	/**
	 * Add or update a Context definition
	 * @throws CoreException CoreException
	 */
	public void updateContext() throws CoreException {
		if (TomcatLauncherPlugin.getConfigMode().equals(TomcatPluginResources.SERVERXML_MODE)) {
			updateServerXML();
		} else {
			try {
				updateContextFile();
			} catch (final IOException e) {
				throw new CoreException(new Status(IStatus.ERROR,
								TomcatLauncherPlugin.getDefault().getBundle().getSymbolicName(),
								e.getMessage(), e));
			}
		}
	}

	/**
	 * Add or update a Context entry on Tomcat server.xml file
	 * @throws CoreException CoreException
	 */
	private void updateServerXML() throws CoreException {
		if (getUpdateXml()) {
			backupServerXML();

			try {
				String xml = FileUtil.readTextFile(getServerXML());
				if (!contextExistsInXML(xml)) {
					addContextToServerXML();
				} else {
					updateContextDefinitionInFile(getServerXML());
				}
			} catch (final IOException e) {
				throw new CoreException(new Status(IStatus.ERROR,
								TomcatLauncherPlugin.getDefault().getBundle().getSymbolicName(),
								e.getMessage(), e));
			}
		}
	}

	/**
	 * create or update a Context file
	 * @throws IOException IOException
	 */
	private void updateContextFile() throws IOException {
		if (getUpdateXml()) {
			File contextFile = getContextFile();
			if (!contextFile.exists()) {
				FileUtil.toTextFile(contextFile, createContextDefinition());
			} else {
				updateContextDefinitionInFile(contextFile);
			}
		}
	}

	/**
	 *
	 * @return File
	 */
	private File getContextFile() {
		File contextFile = new File(TomcatLauncherPlugin.getContextsDir()
						+ File.separator + getContextFileName());
		return contextFile;
	}

	/**
	 *
	 * @return String
	 */
	private String getContextFileName() {
		String contextFileName = getWebPath();
		if (contextFileName.startsWith("/")) {
			contextFileName = contextFileName.substring(1);
		}
		if (contextFileName.isEmpty()) {
			contextFileName = "ROOT";
		}

		// Tomcat 5 converts / to # in context file name
		contextFileName = contextFileName.replace('/', '#');

		return contextFileName + ".xml";
	}

	/**
	 *
	 * @throws CoreException CoreException
	 */
	@Override
	public void removeContext() throws CoreException {
		// Always call removeContext file because Tomcat create it automatically when using server.xml
		removeContextFile();

		if (TomcatLauncherPlugin.getConfigMode().equals(TomcatPluginResources.SERVERXML_MODE)) {
			removeContextInServerXML();
		}
	}

	/**
	 * removeContextFile
	 * @return boolean
	 */
	private boolean removeContextFile() {
		return getContextFile().delete();
	}

	/**
	 *
	 * @throws CoreException CoreException
	 */
	private void removeContextInServerXML() throws CoreException {
		backupServerXML();

		try {
			String xml = FileUtil.readTextFile(getServerXML());
			if (contextExistsInXML(xml)) {
				int contextTagIdx = getContextTagIndex(xml);
				int endTagIndex = xml.indexOf("</Context>", contextTagIdx);
				if (endTagIndex < 0) {
					endTagIndex = xml.indexOf('>', contextTagIdx);
				} else {
					endTagIndex += "</Context>".length();
				}

				StringBuilder out = new StringBuilder(xml.substring(0, contextTagIdx));
				out.append(xml.substring(endTagIndex + 1, xml.length()));
				FileUtil.toTextFile(getServerXML(), out.toString());
			}
		} catch (final IOException e) {
			throw new CoreException(new Status(IStatus.ERROR,
							TomcatLauncherPlugin.getDefault().getBundle().getSymbolicName(),
							e.getMessage(), e));
		}
	}

	/**
	 * Backup Tomcat server.xml file using the following algorithm :
	 * - Initial server.xml is backuped to server.xml.backup
	 * - Before updating server.xml create a copy named server.xml.old
	 * @throws CoreException CoreException
	 */
	public void backupServerXML() throws CoreException {
		String backup = getServerXMLLocation() + ".backup";
		String old = getServerXMLLocation() + ".old";

		if (!getServerXML().exists()) {
			String msg = "Tomcat server.xml file is not found in " + getServerXML().getAbsolutePath();
			Status status = new Status(IStatus.ERROR,
							TomcatLauncherPlugin.getDefault().getBundle().getSymbolicName(),
							IStatus.ERROR, msg, null);
			throw new CoreException(status);
		}

		File backupFile = new File(backup);
		try {
			if (!backupFile.exists()) {
				FileUtil.copy(getServerXML(), backupFile);
			}

			FileUtil.copy(getServerXML(), new File(old));
		} catch (final IOException e) {
			throw new CoreException(new Status(IStatus.ERROR,
							TomcatLauncherPlugin.getDefault().getBundle().getSymbolicName(),
							e.getMessage(), e));
		}
	}

	/**
	 *
	 * @return File
	 */
	private File getServerXML() {
		return new File(getServerXMLLocation());
	}

	/**
	 *
	 * @return String
	 */
	private String getServerXMLLocation() {
		return TomcatLauncherPlugin.getConfigFile();
	}

	/**
	 * Quick and dirty implementations : using an XML Parser would be better
	 * @param xml String
	 * @return boolean
	 */
	private boolean contextExistsInXML(final String xml) {
		return getContextTagIndex(xml) != -1;
	}

	/**
	 *
	 * @param xml String
	 * @return int
	 */
	private int getContextTagIndex(final String xml) {
		int pathIndex = xml.indexOf(getContextPath());

		if (pathIndex == -1) {
			return -1;
		}

		int tagIndex = (xml.substring(0, pathIndex)).lastIndexOf('<');
		String tag = xml.substring(tagIndex, tagIndex + 8);
		if (!tag.equalsIgnoreCase(getContextStartTag())) {
			return -1;
		}

		return tagIndex;
	}

	/**
	 *
	 * @throws IOException IOException
	 */
	private void addContextToServerXML() throws IOException {
		String xml = FileUtil.readTextFile(getServerXML());
		String tag = TomcatLauncherPlugin.getTomcatBootstrap().getXMLTagAfterContextDefinition();

		int tagIndex = xml.indexOf(tag);
		int insertIndex = (xml.substring(0, tagIndex)).lastIndexOf('\n');

		StringBuilder out = new StringBuilder(xml.substring(0, insertIndex));
		out.append(createContextDefinition());
		out.append(xml.substring(insertIndex, xml.length()));

		FileUtil.toTextFile(getServerXML(), out.toString());
	}

	/**
	 *
	 * @return String
	 */
	private String createContextDefinition() {
		StringBuilder contextBuffer = new StringBuilder();
		contextBuffer.append(getContextStartTag());
		contextBuffer.append(' ');
		contextBuffer.append(getContextPath());
		contextBuffer.append(' ');
		contextBuffer.append(getContextReloadable());
		contextBuffer.append(' ');
		contextBuffer.append(getContextDocBase());
		contextBuffer.append(' ');
		contextBuffer.append(getContextWorkDir());
		contextBuffer.append(" />\n");

		String context = contextBuffer.toString();
		if (getWebClassPathEntries() != null) {
			context = addLoaderToContext(context);
		}
		if (getRedirectLogger()) {
			context = addLoggerToContext(context);
		}
		if (!(getExtraInfo().isEmpty())) {
			context = addExtraInfoToContext(context);
		}

		return context;
	}

	/**
	 *
	 * @param ctx String
	 * @return String
	 */
	private String updateContextDefinition(final String ctx) {

		// if reloadable param not set
		String context = ctx;
		int reloadableIndex = context.indexOf("reloadable");
		if (reloadableIndex == -1) {
			context = addReloadableToContext(context);
		} else {
			context = updateReloadableInContext(context);
		}

		// update docBase if set
		int docBaseIndex = context.indexOf("docBase");
		if (docBaseIndex == -1) {
			context = addDocBaseToContext(context);
		} else {
			context = updateDocBaseInContext(context);
		}

		// if work param not set
		int workIndex = context.indexOf("workDir");
		if (workIndex == -1) {
			context = addWorkToContext(context);
		} else {
			context = updateWorkInContext(context);
		}

		// if loader not set
		int loaderIndex = context.indexOf("<Loader");
		if ((loaderIndex == -1) && (getWebClassPathEntries() != null)) {
			context = addLoaderToContext(context);
		}
		if ((loaderIndex != -1) && (getWebClassPathEntries() == null)) {
			context = removeLoaderInContext(context);
		}
		if ((loaderIndex != -1) && (getWebClassPathEntries() != null)) {
			context = updateLoaderInContext(context);
		}


		// if logger not set
		int loggerIndex = context.indexOf("<Logger");
		if ((loggerIndex == -1) && getRedirectLogger()) {
			context = addLoggerToContext(context);
		}
		if ((loggerIndex != -1) && !getRedirectLogger()) {
			context = removeLoggerInContext(context);
		}
		if ((loggerIndex != -1) && getRedirectLogger()) {
			context = updateLoggerInContext(context);
		}

		// Extra info
		int extraInfoIndex = context.indexOf(EXTRA_BEGIN_TAG);
		if ((extraInfoIndex == -1) && !(getExtraInfo().isEmpty())) {
			context = addExtraInfoToContext(context);
		}
		if ((extraInfoIndex != -1) && getExtraInfo().isEmpty()) {
			context = removeExtraInfoInContext(context);
		}
		if ((extraInfoIndex != -1) && !(getExtraInfo().isEmpty())) {
			context = updateExtraInfoInContext(context);
		}
		return context;
	}

	/**
	 *
	 * @param xmlFile File
	 * @throws IOException IOException
	 */
	private void updateContextDefinitionInFile(final File xmlFile) throws IOException {
		String xml = FileUtil.readTextFile(xmlFile);
		int contextTagIndex = getContextTagIndex(xml);

		// If context doesn't exist do nothing
		if (contextTagIndex == -1) {
			return;
		}

		// Get context
		int endContextTagIndex = xml.indexOf('>', contextTagIndex);
		if (xml.charAt(endContextTagIndex - 1) != '/') {
			endContextTagIndex = xml.indexOf(
					getContextEndTag(), contextTagIndex) + getContextEndTag().length() - 1;
		}
		String context = xml.substring(contextTagIndex, endContextTagIndex + 1);

		StringBuilder out = new StringBuilder(xml.substring(0, contextTagIndex));
		out.append(updateContextDefinition(context));
		out.append(xml.substring(endContextTagIndex + 1));
		FileUtil.toTextFile(xmlFile, out.toString());
	}

	/**
	 *
	 * @param context String
	 * @return String
	 */
	private String addDocBaseToContext(final String context) {
		int reloadableIndex = context.indexOf(getContextReloadable());
		int firstDoubleQuoteIndex = context.indexOf('"', reloadableIndex) + 1;
		int docBaseIndex = context.indexOf('"', firstDoubleQuoteIndex) + 1;
		StringBuilder out = new StringBuilder(context.substring(0, docBaseIndex));
		out.append(' ');
		out.append(getContextDocBase());
		out.append(' ');
		out.append(context.substring(docBaseIndex, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param context String
	 * @return String
	 */
	private String updateDocBaseInContext(final String context) {
		int docBaseIndex = context.indexOf("docBase");
		int startIndex = context.indexOf('"', docBaseIndex);
		int endIndex = context.indexOf('"', startIndex + 1);
		StringBuilder out = new StringBuilder(context.substring(0, docBaseIndex));
		out.append(getContextDocBase());
		out.append(context.substring(endIndex + 1, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @return String
	 */
	private String getContextReloadable() {
		return "reloadable=" + '"' + getReloadable() + '"';
	}

	/**
	 *
	 * @param context String
	 * @return String
	 */
	private String addReloadableToContext(final String context) {
		int pathIndex = context.indexOf(getContextPath());
		int firstDoubleQuoteIndex = context.indexOf('"', pathIndex) + 1;
		int reloadableIndex = context.indexOf('"', firstDoubleQuoteIndex) + 1;

		StringBuilder out = new StringBuilder(context.substring(0, reloadableIndex));
		out.append(' ');
		out.append(getContextReloadable());
		out.append(' ');
		out.append(context.substring(reloadableIndex, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param context String
	 * @return String
	 */
	private String updateReloadableInContext(final String context) {
		int reloadableIndex = context.indexOf("reloadable");
		int startIndex = context.indexOf('"', reloadableIndex);
		int endIndex = context.indexOf('"', startIndex + 1);
		StringBuilder out = new StringBuilder(context.substring(0, reloadableIndex));
		out.append(getContextReloadable());
		out.append(context.substring(endIndex + 1, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param context String
	 * @return String
	 */
	private String addWorkToContext(final String context) {
		int docBaseIndex = context.indexOf("docBase");
		int firstDoubleQuoteIndex = context.indexOf('"', docBaseIndex) + 1;
		int workIndex = context.indexOf('"', firstDoubleQuoteIndex) + 1;
		StringBuilder out = new StringBuilder(context.substring(0, workIndex));
		out.append(' ');
		out.append(getContextWorkDir());
		out.append(' ');
		out.append(context.substring(workIndex, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param context String
	 * @return String
	 */
	private String updateWorkInContext(final String context) {
		int workDirIndex = context.indexOf("workDir");
		int startIndex = context.indexOf('"', workDirIndex);
		int endIndex = context.indexOf('"', startIndex + 1);
		StringBuilder out = new StringBuilder(context.substring(0, workDirIndex));
		out.append(getContextWorkDir());
		out.append(context.substring(endIndex + 1, context.length()));
		return out.toString();
	}

	/**
	 * Add </Context> instead of  />, and \n if needed
	 * @param context String
	 * @return String
	 */
	private String formatContextEndTag(final String context) {
		int endContextStartTagIndex = context.indexOf('>');
		StringBuilder newContext = new StringBuilder();
		if (context.charAt(endContextStartTagIndex - 1) == '/') {
			newContext.append(context.substring(0, endContextStartTagIndex - 1));
			newContext.append(">");
			newContext.append("\n");
			newContext.append(getContextEndTag());
			newContext.append("\n");
		} else {
			int endContextTagIndex = context.indexOf(getContextEndTag());
			if (context.charAt(endContextTagIndex - 1) != '\n') {
				newContext.append(context.substring(0, endContextTagIndex));
				newContext.append("\n");
				newContext.append(context.substring(endContextTagIndex));
			} else {
				return context;
			}
		}
		return newContext.toString();
	}

	/**
	 *
	 * @param ctx String
	 * @return String
	 */
	private String addLoaderToContext(final String ctx) {
		String context = formatContextEndTag(ctx);
		int endContextStartTagIndex = context.indexOf('>');
		int loaderIndex = endContextStartTagIndex + 1;
		StringBuilder out = new StringBuilder(context.substring(0, loaderIndex));
		out.append(getContextWebAppClassLoader());
		out.append(context.substring(loaderIndex, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param ctx String
	 * @return String
	 */
	private String updateLoaderInContext(final String ctx) {
		String context = formatContextEndTag(ctx);
		int endContextStartTagIndex = context.indexOf('>');
		int startIndex = context.indexOf("<Loader", endContextStartTagIndex);
		if (context.charAt(startIndex - 1) == '\t') {
			startIndex--;
		}
		if (context.charAt(startIndex - 1) == '\n') {
			startIndex--;
		}
		int endIndex = context.indexOf("/>", startIndex + 1) + 1;
		StringBuilder out = new StringBuilder(context.substring(0, startIndex));
		out.append(getContextWebAppClassLoader());
		out.append(context.substring(endIndex + 1, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param context String
	 * @return String
	 */
	private String removeLoaderInContext(final String context) {
		int endContextStartTagIndex = context.indexOf('>');
		int startIndex = context.indexOf("<Loader", endContextStartTagIndex);
		if (context.charAt(startIndex - 1) == '\t') {
			startIndex--;
		}
		if (context.charAt(startIndex - 1) == '\n') {
			startIndex--;
		}
		int endIndex = context.indexOf("/>", startIndex + 1) + 1;
		StringBuilder out = new StringBuilder(context.substring(0, startIndex));
		out.append(context.substring(endIndex + 1, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param ctx String
	 * @return String
	 */
	private String addLoggerToContext(final String ctx) {
		String context = formatContextEndTag(ctx);
		int endContextStartTagIndex = context.indexOf('>');
		int loggerIndex = endContextStartTagIndex + 1;
		StringBuilder out = new StringBuilder(context.substring(0, loggerIndex));
		out.append(getContextLogger());
		out.append(context.substring(loggerIndex, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param context String
	 * @return String
	 */
	private String updateLoggerInContext(final String context) {
		int endContextStartTagIndex = context.indexOf('>');
		int startIndex = context.indexOf("<Logger", endContextStartTagIndex);
		if (context.charAt(startIndex - 1) == '\t') {
			startIndex--;
		}
		if (context.charAt(startIndex - 1) == '\n') {
			startIndex--;
		}
		int endIndex = context.indexOf("/>", startIndex) + 1;
		StringBuilder out = new StringBuilder(context.substring(0, startIndex));
		out.append(getContextLogger());
		out.append(context.substring(endIndex + 1, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param context String
	 * @return String
	 */
	private String removeLoggerInContext(final String context) {
		int endContextStartTagIndex = context.indexOf('>');
		int startIndex = context.indexOf("<Logger", endContextStartTagIndex);
		if (context.charAt(startIndex - 1) == '\t') {
			startIndex--;
		}
		if (context.charAt(startIndex - 1) == '\n') {
			startIndex--;
		}
		int endIndex = context.indexOf("/>", startIndex) + 1;
		StringBuilder out = new StringBuilder(context.substring(0, startIndex));
		out.append(context.substring(endIndex + 1, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param ctx String
	 * @return String
	 */
	private String addExtraInfoToContext(final String ctx) {
		String context = formatContextEndTag(ctx);
		int endContextStartTagIndex = context.indexOf('>');
		int extraInfoIndex = endContextStartTagIndex + 1;
		StringBuilder out = new StringBuilder(context.substring(0, extraInfoIndex));
		out.append('\n');
		out.append(EXTRA_BEGIN_TAG);
		out.append('\n');
		out.append(getExtraInfo());
		out.append('\n');
		out.append(EXTRA_END_TAG);
		out.append(context.substring(extraInfoIndex, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param context String
	 * @return String
	 */
	private String updateExtraInfoInContext(final String context) {
		int endContextStartTagIndex = context.indexOf('>');
		int startIndex = context.indexOf(EXTRA_BEGIN_TAG, endContextStartTagIndex);
		if (context.charAt(startIndex - 1) == '\t') {
			startIndex--;
		}
		if (context.charAt(startIndex - 1) == '\n') {
			startIndex--;
		}
		int extraEndTagStartIndex = context.indexOf(EXTRA_END_TAG, startIndex);
		StringBuilder out = new StringBuilder(context.substring(0, startIndex));
		out.append('\n');
		out.append(EXTRA_BEGIN_TAG);
		out.append('\n');
		out.append(getExtraInfo());
		out.append('\n');
		out.append(context.substring(extraEndTagStartIndex, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @param context String
	 * @return String
	 */
	private String removeExtraInfoInContext(final String context) {
		int endContextStartTagIndex = context.indexOf('>');
		int startIndex = context.indexOf(EXTRA_BEGIN_TAG, endContextStartTagIndex);
		if (context.charAt(startIndex - 1) == '\t') {
			startIndex--;
		}
		if (context.charAt(startIndex - 1) == '\n') {
			startIndex--;
		}
		int extraEndTagStartIndex = context.indexOf(EXTRA_END_TAG, startIndex);
		StringBuilder out = new StringBuilder(context.substring(0, startIndex));
		int endIndex = extraEndTagStartIndex + EXTRA_END_TAG.length();
		out.append(context.substring(endIndex, context.length()));
		return out.toString();
	}

	/**
	 *
	 * @return String
	 */
	private String getContextStartTag() {
		return "<Context";
	}

	/**
	 *
	 * @return String
	 */
	private String getContextPath() {
		return "path=" + '"' + getWebPath() + '"';
	}

	/**
	 *
	 * @return String
	 */
	private String getContextDocBase() {
		String docBaseLocation = "";
		if (getRootDirFolder() == null) {
			docBaseLocation = this.project.getLocation().toOSString();
		} else {
			docBaseLocation = getRootDirFolder().getLocation().toOSString();
		}
		return "docBase=" + '"' + docBaseLocation + '"';
	}

	/**
	 *
	 * @return String
	 */
	private String getContextWorkDir() {
		String workFolderLocation = getWorkFolder().toOSString();
		return TomcatLauncherPlugin.getTomcatBootstrap().getContextWorkDir(workFolderLocation);
	}

	/**
	 *
	 * @return String
	 */
	private String getContextWebAppClassLoader() {
		return "\n\t<Loader className=\"org.apache.catalina.loader.DevLoader\" reloadable=\"true\" />";
	}

	/**
	 *
	 * @return String
	 */
	private String getContextLogger() {
		return "\n\t<Logger className=\"org.apache.catalina.logger.SystemOutLogger\" "
				+ "verbosity=\"4\" timestamp=\"true\"/>";
	}

	/**
	 *
	 * @return String
	 */
	private String getContextEndTag() {
		return "</Context>";
	}

	/**
	 *
	 * @throws IOException IOException
	 */
	public void exportToWar() throws IOException {

		File warFile = new File(getWarLocation());

		File directory = null;
		if (getRootDirFolder() == null) {
			directory = getProject().getLocation().toFile();
		} else {
			directory = getRootDirFolder().getLocation().toFile();
		}

		Zipper zipper = new TomcatProjectZipper(warFile, directory, getExportSource());
		zipper.zip();
	}

	/**
	 * if WEB-INF classes contains Java files add it to source folders
	 * Otherwise Eclipse will delete all those files
	 * @return boolean
	 */
	private boolean classesContainsJavaFiles() {
		IFolder webinfFolder = getWebInfFolder();
		IFolder classesFolder = webinfFolder.getFolder("classes");
		File f = classesFolder.getLocation().toFile();
		if (!f.exists() || !f.isDirectory()) {
			return false;
		}
		return FileUtil.dirContainsFiles(f, "java", true);
	}

	/**
	 * A new Tomcat project should be checked by default in source path preference page
	 */
	private void addProjectToSourcePathPref() {
		List<ProjectListElement> projects = TomcatLauncherPlugin.getDefault().getProjectsInSourcePath();
		ProjectListElement ple = new ProjectListElement(getProject());
		if (!projects.contains(ple)) {
			projects.add(ple);
			TomcatLauncherPlugin.setProjectsInSourcePath(projects);
		}
	}
}
