/*******************************************************************************
 * Copyright (c) 2007, 2014 Symbian Software Limited 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:
 * Bala Torati (Symbian) - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.core.templateengine;

import java.io.IOException;
import java.net.URL;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.templateengine.process.ProcessFailureException;
import org.eclipse.cdt.core.templateengine.process.TemplateProcessHandler;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;

/**
 * TemplateCore class is responsible providing the non-UI part of template and
 * initiating process part of Template Engine. This is created per TemplateDescriptor basis.
 * Once The Template is created it creates a TemplateDescriptor for the XML file name given.
 * Template class extends this class with additional UI elements that are part of the template.
 *
 * @since 4.0
 */
public class TemplateCore {
	private static final String DESCRIPTION = "description"; //$NON-NLS-1$
	private static final String LABEL = "label"; //$NON-NLS-1$
	private static final String ID = "id"; //$NON-NLS-1$
	private static final String TYPE = "type"; //$NON-NLS-1$

	private static Map<TemplateInfo, TemplateCore> templateCache = new HashMap<>();

	public static final Comparator<TemplateCore> TEMPLATE_ID_CASE_INSENSITIVE_COMPARATOR = new Comparator<TemplateCore>() {
		@Override
		public int compare(TemplateCore t1, TemplateCore t2) {
			return String.CASE_INSENSITIVE_ORDER.compare(t1.getTemplateId(), t2.getTemplateId());
		}
	};

	private TemplateDescriptor templateDescriptor;
	private Map<String, String> valueStore;
	private TemplateInfo templateInfo;
	private Set<String> allMacrosInProcesses;
	private TemplateProcessHandler processHandler;
	private String description;
	private String label;
	private String templateId;
	private String templateType;
	private boolean fireDirtyEvents;

	/**
	 * @param templateInfo may not be null
	 * @throws TemplateInitializationException
	 */
	protected TemplateCore(TemplateInfo templateInfo) throws TemplateInitializationException {
		this.templateInfo = templateInfo;
		URL descriptorURL;
		try {
			descriptorURL = TemplateEngineHelper.getTemplateResourceURL(templateInfo.getPluginId(),
					templateInfo.getTemplatePath());
		} catch (IOException ioe) {
			String msg = NLS.bind(Messages.TemplateCore_init_failed, templateInfo.getTemplatePath());
			throw new TemplateInitializationException(msg);
		}
		if (descriptorURL == null)
			throw new TemplateInitializationException(
					"Unable to load project template. Location URL is null for " + templateInfo.getTemplateId()); //$NON-NLS-1$
		templateDescriptor = new TemplateDescriptor(descriptorURL, templateInfo.getPluginId());
		valueStore = new ValueStore<>(this);
		valueStore.putAll(templateDescriptor.getTemplateDefaults(templateDescriptor.getRootElement()));
		valueStore.putAll(TemplateEngine.getSharedDefaults());
		valueStore.put("projectType", templateInfo.getProjectType()); //$NON-NLS-1$

		processHandler = new TemplateProcessHandler(this);
		allMacrosInProcesses = processHandler.getAllMacros();

		fireDirtyEvents = true;
	}

	/**
	 * Returns All Missing Macros In Processes.
	 * @return Set
	 */
	public Set<String> getAllMissingMacrosInProcesses() {
		Set<String> set = new TreeSet<>(allMacrosInProcesses);
		for (Iterator<String> iter = set.iterator(); iter.hasNext();) {
			if (valueStore.get(iter.next()) != null) {
				iter.remove();
			}
		}
		return set;
	}

	/**
	 * return the ValueStore maintained by this Template.
	 * @return   ValueStore.
	 */
	public Map<String, String> getValueStore() {
		return valueStore;
	}

	/**
	 * This is the List of IDs from TemplateDescriptor with "attribute" Persist
	 * as "true"
	 *
	 * @return Vector of IDs.
	 */
	public List<String> getPersistTrueIDs() {
		return templateDescriptor.getPersistTrueIDs();
	}

	/**
	 * return the TemplateInfo instance corresponding to this Template.
	 * @return   TemplateInfo.
	 */
	public TemplateInfo getTemplateInfo() {
		return templateInfo;
	}

	/**
	 * TemplateDescriptor for which, this Template is created.
	 */
	public TemplateDescriptor getTemplateDescriptor() {
		return templateDescriptor;
	}

	/**
	 * @return   String, which contains the description
	 */
	public String getDescription() {
		if (description == null) {
			description = templateDescriptor.getRootElement().getAttribute(DESCRIPTION).trim();
		}
		return TemplateEngineHelper.externalizeTemplateString(templateInfo, description);
	}

	/**
	 * @return   String, which contains the id of the template
	 */
	public String getTemplateId() {
		if (templateId == null) {
			templateId = templateDescriptor.getRootElement().getAttribute(ID).trim();
		}
		return templateId;
	}

	/**
	 * @return   String, which contains the id of the template
	 */
	public String getTemplateType() {
		if (templateType == null) {
			templateType = templateDescriptor.getRootElement().getAttribute(TYPE).trim();
		}
		return templateType;
	}

	/**
	 * @return   String, which contains the Label
	 */
	public String getLabel() {
		if (label == null) {
			label = templateDescriptor.getRootElement().getAttribute(LABEL).trim();
		}
		return TemplateEngineHelper.externalizeTemplateString(templateInfo, label);
	}

	/**
	 * TemplateDescriptor for which, this Template is created.
	 */
	public TemplateProcessHandler getProcessHandler() {
		return processHandler;
	}

	@Override
	public String toString() {
		return getLabel();
	}

	/**
	 * sets Dirty
	 *
	 */
	public void setDirty() {
		if (fireDirtyEvents) {
			synchronized (templateCache) {
				templateCache.remove(templateInfo);
			}
		}
	}

	/**
	 * initializeProcessBlockList() will create the ProcessBlockList,
	 * processPorcessBlockList() will invoke each process execution by assigning
	 * resources to each process (Ref. ProcessResourceManager).
	 * @param monitor
	 */
	public IStatus[] executeTemplateProcesses(IProgressMonitor monitor, final boolean showError) {
		setDirty();
		TemplateEngine.getDefault().updateSharedDefaults(this);
		final IStatus[][] result = new IStatus[1][];
		try {
			result[0] = getProcessHandler().processAll(monitor);
		} catch (ProcessFailureException e) {
			TemplateEngineUtil.log(e);
			result[0] = new IStatus[] {
					new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, IStatus.ERROR, e.getMessage(), e) };
		}
		return result[0];
	}

	/**
	 * Gets the Template
	 *
	 * @param templateInfo
	 * @throws TemplateInitializationException
	 * @since 4.0
	 */
	public static TemplateCore getTemplate(TemplateInfo templateInfo) throws TemplateInitializationException {
		synchronized (templateCache) {
			TemplateCore template = templateCache.get(templateInfo);
			if (template == null) {
				template = new TemplateCore(templateInfo);
				templateCache.put(templateInfo, template);
			}
			return template;
		}
	}

	private static class ValueStore<K> extends HashMap<K, String> {
		private static final long serialVersionUID = -4523467333437879406L;
		private TemplateCore template;

		ValueStore(TemplateCore template) {
			this.template = template;
		}

		@Override
		public String put(K key, String value) {
			value = TemplateEngineHelper.externalizeTemplateString(template.getTemplateInfo(), value);
			String v = super.put(key, value);
			template.setDirty();
			return v;
		}

		@Override
		public void putAll(Map<? extends K, ? extends String> map) {
			for (K key : map.keySet()) {
				String value = map.get(key);
				value = TemplateEngineHelper.externalizeTemplateString(template.getTemplateInfo(), value);
				super.put(key, value);
			}
			template.setDirty();
		}

		@Override
		public String remove(Object key) {
			String v = super.remove(key);
			template.setDirty();
			return v;
		}
	}
}
