/*******************************************************************************
 *  Copyright (c) 2002, 2016 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
 *******************************************************************************/
package org.eclipse.ui.internal.cheatsheets;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.nio.charset.StandardCharsets;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.help.internal.entityresolver.LocalEntityResolver;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.XMLMemento;
import org.eclipse.ui.internal.cheatsheets.registry.CheatSheetRegistryReader;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;

/**
 * The main plugin class for cheat sheets.
 */

public class CheatSheetPlugin extends AbstractUIPlugin {

	public final static String PLUGIN_ID = "org.eclipse.help.base"; //$NON-NLS-1$

	//The shared instance of this plugin.
	static CheatSheetPlugin plugin;

	//Resource bundle.
	//private boolean resourceBundleInitialized = false;
	//private ResourceBundle resourceBundle;
	private CheatSheetHistory history = null;
	private DocumentBuilder documentBuilder = null;

	private static final String HISTORY_FILENAME = "history.xml"; //$NON-NLS-1$
	private static final String MEMENTO_TAG_CHEATSHEET = "cheatsheet"; //$NON-NLS-1$
	private static final String MEMENTO_TAG_VERSION = "version"; //$NON-NLS-1$
	private static final String VERSION_STRING[] = { "0.0", "3.0.0" }; //$NON-NLS-1$ //$NON-NLS-2$
	private static final String MEMENTO_TAG_CHEATSHEET_HISTORY = "cheatsheetHistory"; //$NON-NLS-1$

	public static final IPath ICONS_PATH = new Path("$nl$/icons/"); //$NON-NLS-1$
	public static final String T_OBJ = "obj16/"; //$NON-NLS-1$
	public static final String T_ELCL = "elcl16/"; //$NON-NLS-1$
	public static final String T_DLCL = "dlcl16/"; //$NON-NLS-1$
	public static final String T_VIEW = "view16/"; //$NON-NLS-1$

	/**
	 * The constructor.
	 */
	public CheatSheetPlugin() {
		super();
	}

	/**
	 * Returns the shared instance.
	 */
	public static CheatSheetPlugin getPlugin() {
		return plugin;
	}

	/**
	 * Returns the image in Cheat Sheet's image registry with the given key,
	 * or <code>null</code> if none.
	 * Convenience method equivalent to
	 * <pre>
	 * CheatSheetPlugin.getImageRegistry().get(key)
	 * </pre>
	 *
	 * @param key the key
	 * @return the image, or <code>null</code> if none
	 */
	public Image getImage(String key) {
		Image image = getImageRegistry().get(key);
		return image;
	}

	/**
	 * Returns the CheatSheetHistory
	 */
	public CheatSheetHistory getCheatSheetHistory() {
		if (history == null) {
			history = new CheatSheetHistory(CheatSheetRegistryReader.getInstance());
			restoreCheatSheetHistory();
		}
		return history;
	}

	/**
	 * Get a file from the state folder.
	 */
	private File getCheatSheetStateFile(String filename) {
		IPath path = CheatSheetPlugin.getPlugin().getStateLocation();
		path = path.append(filename);
		return path.toFile();
	}

	/**
	 * Returns the DocumentBuilder to be used by the cheat sheets.
	 */
	public DocumentBuilder getDocumentBuilder() {
		if(documentBuilder == null) {
			try {
				documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
				documentBuilder.setEntityResolver(new LocalEntityResolver());
			} catch (Exception e) {
				IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, Messages.ERROR_CREATING_DOCUMENT_BUILDER, e);
				CheatSheetPlugin.getPlugin().getLog().log(status);
			}
		}
		return documentBuilder;
	}

	/**
	 * Logs an Error message with an exception.
	 */
	public static synchronized void logError(String message, Throwable ex) {
		if (message == null)
			message = ""; //$NON-NLS-1$
		Status errorStatus = new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK,
				message, ex);
		CheatSheetPlugin.getPlugin().getLog().log(errorStatus);
	}

	@Override
	protected void initializeImageRegistry(ImageRegistry reg) {
		IPath path = ICONS_PATH.append(T_OBJ).append("cheatsheet_obj.gif");//$NON-NLS-1$
		ImageDescriptor imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_OBJ, imageDescriptor);

		path = ICONS_PATH.append(T_OBJ).append("skip_status.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_ITEM_SKIP, imageDescriptor);

		path = ICONS_PATH.append(T_OBJ).append("complete_status.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_ITEM_COMPLETE, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("linkto_help.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_ITEM_HELP, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("start_cheatsheet.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_START, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("restart_cheatsheet.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_RESTART, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("start_task.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_ITEM_BUTTON_START, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("skip_task.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_ITEM_BUTTON_SKIP, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("complete_task.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_ITEM_BUTTON_COMPLETE, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("restart_task.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_ITEM_BUTTON_RESTART, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("return_to_start.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_RETURN, imageDescriptor);

		path = ICONS_PATH.append(T_OBJ).append("error.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.ERROR, imageDescriptor);

		// Images used by composites

		path = ICONS_PATH.append(T_OBJ).append("composite_obj.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.COMPOSITE_OBJ, imageDescriptor);

		path = ICONS_PATH.append(T_OBJ).append("information.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.INFORMATION, imageDescriptor);

		path = ICONS_PATH.append(T_OBJ).append("warning.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.WARNING, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("start_ccs_task.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.COMPOSITE_TASK_START, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("skip_ccs_task.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.COMPOSITE_TASK_SKIP, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("review_ccs_task.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.COMPOSITE_TASK_REVIEW, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("goto_ccs_task.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.COMPOSITE_GOTO_TASK, imageDescriptor);

		path = ICONS_PATH.append(T_ELCL).append("restart_all.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.COMPOSITE_RESTART_ALL, imageDescriptor);

		path = ICONS_PATH.append(T_VIEW).append("cheatsheet_view.gif");//$NON-NLS-1$
		imageDescriptor = createImageDescriptor(getPlugin().getBundle(), path);
		reg.put(ICheatSheetResource.CHEATSHEET_VIEW, imageDescriptor);
	}

	/**
	 * Restores the state of the previously saved cheatsheet history
	 */
	private void restoreCheatSheetHistory() {
		SafeRunner.run(new SafeRunnable() {
			@Override
			public void run() {
				IMemento memento;
				memento = readMemento(HISTORY_FILENAME);
				if (memento != null) {
					IMemento childMem = memento.getChild(MEMENTO_TAG_CHEATSHEET_HISTORY);
					if (childMem != null) {
						history.restoreState(childMem);
					}
				}
			}
			@Override
			public void handleException(Throwable e) {
				String message = Messages.ERROR_READING_STATE_FILE;
				IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, message, e);
				CheatSheetPlugin.getPlugin().getLog().log(status);
			}
		});
	}

	/**
	 * Read a memento from the state directory for the cheatsheets plugin
	 * @param filename A simple filename
	 * @return A memento read from the state directory or null if the memento could not be read
	 */
	public XMLMemento readMemento(String filename) {
		XMLMemento memento;
		InputStreamReader reader = null;

		try {
			// Read the cheatsheet state file.
			final File stateFile = getCheatSheetStateFile(filename);

			FileInputStream input = new FileInputStream(stateFile);
			reader = new InputStreamReader(input, StandardCharsets.UTF_8);
			memento = XMLMemento.createReadRoot(reader);


		} catch (FileNotFoundException e) {
			memento = null;
			// Do nothing, the file will not exist the first time the workbench in used.
		} catch (Exception e) {
			String message = Messages.ERROR_READING_STATE_FILE;
			IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, message, e);
			CheatSheetPlugin.getPlugin().getLog().log(status);
			memento = null;
		} finally {
			try {
				if (reader != null)
					reader.close();
			} catch (IOException e) {
				// Not much to do, just catch the exception and keep going.
				String message = Messages.ERROR_READING_STATE_FILE;
				IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, message, e);
				CheatSheetPlugin.getPlugin().getLog().log(status);
			}
		}
		return memento;
	}

	/**
	 * Saves the current cheatsheet history so it can be restored later on
	 */
	private void saveCheatSheetHistory() {
		SafeRunner.run(new SafeRunnable() {
			@Override
			public void run() {
				XMLMemento memento = XMLMemento.createWriteRoot(MEMENTO_TAG_CHEATSHEET);

				// Save the version number.
				memento.putString(MEMENTO_TAG_VERSION, VERSION_STRING[1]);

				// Save perspective history.
				getCheatSheetHistory().saveState(memento.createChild(MEMENTO_TAG_CHEATSHEET_HISTORY));

				IStatus status = saveMemento(memento, HISTORY_FILENAME);
				if (!status.isOK()) {
					CheatSheetPlugin.getPlugin().getLog().log(status);
				}
			}
			@Override
			public void handleException(Throwable e) {
				String message = Messages.ERROR_WRITING_STATE_FILE;
				IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, message, e);
				CheatSheetPlugin.getPlugin().getLog().log(status);
			}
		});
	}

	/**
	 * Save the memento to a file in this plugins state area
	 * @param memento The memento to save
	 * @param filename A simple filename
	 * @return OK_Status if the memento was saved without error, otherwise an error
	 * status
	 */
	public IStatus saveMemento(XMLMemento memento, String filename) {
		// Save the IMemento to a file.
		File stateFile = getCheatSheetStateFile(filename);
		OutputStreamWriter writer = null;
		try {
			FileOutputStream stream = new FileOutputStream(stateFile);
			writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
			memento.save(writer);
			return Status.OK_STATUS;
		} catch (IOException e) {
			stateFile.delete();
			String message = Messages.ERROR_WRITING_STATE_FILE;
			IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, message, e);
			return status;
		} finally {
			try {
				if (writer != null)
					writer.close();
			} catch (IOException e) {
				String message = Messages.ERROR_WRITING_STATE_FILE;
				IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, message, e);
				CheatSheetPlugin.getPlugin().getLog().log(status);
			}
		}
	}

	@Override
	public void start(BundleContext context) throws Exception {
		super.start(context);

		plugin = this;

		// allow the MRU history to be lazily initialized by getCheatSheetHistory
	}

	@Override
	public void stop(BundleContext context) throws Exception {
		super.stop(context);

		// save the MRU history if necessary
		// if we never restored history, let existing memento stand
		if (history != null) {
			saveCheatSheetHistory();
		}

		CheatSheetRegistryReader.getInstance().stop();
	}

	/*
	 * Since 3.1.1. Load from icon paths with $NL$
	 */
	public static ImageDescriptor createImageDescriptor(Bundle bundle, IPath path) {
		URL url= FileLocator.find(bundle, path, null);
		if (url != null) {
			return ImageDescriptor.createFromURL(url);
		}
		return null;
	}

}
