/*******************************************************************************
 * Copyright (c) 2000, 2003 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package net.sourceforge.phpdt.internal.ui.text.spelling;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import net.sourceforge.phpdt.internal.ui.PHPUIMessages;
import net.sourceforge.phpdt.internal.ui.text.spelling.engine.DefaultSpellChecker;
import net.sourceforge.phpdt.internal.ui.text.spelling.engine.ISpellCheckEngine;
import net.sourceforge.phpdt.internal.ui.text.spelling.engine.ISpellCheckPreferenceKeys;
import net.sourceforge.phpdt.internal.ui.text.spelling.engine.ISpellChecker;
import net.sourceforge.phpdt.internal.ui.text.spelling.engine.ISpellDictionary;
import net.sourceforge.phpdt.internal.ui.text.spelling.engine.PersistentSpellDictionary;
import net.sourceforge.phpeclipse.PHPeclipsePlugin;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;

/**
 * Spell check engine for Java source spell checking.
 * 
 * @since 3.0
 */
public class SpellCheckEngine implements ISpellCheckEngine,
		IPropertyChangeListener {

	/** The dictionary location */
	public static final String DICTIONARY_LOCATION = "dictionaries/"; //$NON-NLS-1$

	/** The singleton spell checker instance */
	private static ISpellChecker fChecker = null;

	/** The singleton engine instance */
	private static ISpellCheckEngine fEngine = null;

	/**
	 * Returns the available locales for this spell check engine.
	 * 
	 * @return The available locales for this engine
	 */
	public static Set getAvailableLocales() {

		URL url = null;
		Locale locale = null;
		InputStream stream = null;

		final Set result = new HashSet();
		try {

			final URL location = getDictionaryLocation();
			final Locale[] locales = Locale.getAvailableLocales();

			for (int index = 0; index < locales.length; index++) {

				locale = locales[index];
				url = new URL(
						location,
						locale.toString().toLowerCase()
								+ "." + PHPUIMessages.getString("Spelling.dictionary.file.extension")); //$NON-NLS-1$ //$NON-NLS-2$

				try {
					stream = url.openStream();
					if (stream != null) {
						try {
							result.add(locale);
						} finally {
							stream.close();
						}
					}
				} catch (IOException exception) {
					// Do nothing
				}
			}
		} catch (MalformedURLException exception) {
			// Do nothing
		}
		result.add(getDefaultLocale());

		return result;
	}

	/**
	 * Returns the default locale for this engine.
	 * 
	 * @return The default locale
	 */
	public static Locale getDefaultLocale() {
		return Locale.US;
	}

	/**
	 * Returns the dictionary location.
	 * 
	 * @throws MalformedURLException
	 *             if the URL could not be created
	 * @return The dictionary location, or <code>null</code> iff the location
	 *         is not known
	 */
	public static URL getDictionaryLocation() throws MalformedURLException {

		final PHPeclipsePlugin plugin = PHPeclipsePlugin.getDefault();
		if (plugin != null)
			return plugin.getBundle().getEntry("/" + DICTIONARY_LOCATION); //$NON-NLS-1$

		return null;
	}

	/**
	 * Returns the singleton instance of the spell check engine.
	 * 
	 * @return The singleton instance of the spell check engine
	 */
	public static final synchronized ISpellCheckEngine getInstance() {

		if (fEngine == null)
			fEngine = new SpellCheckEngine();

		return fEngine;
	}

	/** The registered locale insenitive dictionaries */
	private final Set fGlobalDictionaries = new HashSet();

	/** The current locale */
	private Locale fLocale = null;

	/** The registered locale sensitive dictionaries */
	private final Map fLocaleDictionaries = new HashMap();

	/** The preference store where to listen */
	private IPreferenceStore fPreferences = null;

	/** The user dictionary */
	private ISpellDictionary fUserDictionary = null;

	/**
	 * Creates a new spell check manager.
	 */
	private SpellCheckEngine() {

		fGlobalDictionaries.add(new TaskTagDictionary());
		fGlobalDictionaries.add(new HtmlTagDictionary());
		fGlobalDictionaries.add(new JavaDocTagDictionary());

		try {

			Locale locale = null;
			final URL location = getDictionaryLocation();

			for (final Iterator iterator = getAvailableLocales().iterator(); iterator
					.hasNext();) {

				locale = (Locale) iterator.next();
				fLocaleDictionaries.put(locale, new SpellReconcileDictionary(
						locale, location));
			}

		} catch (MalformedURLException exception) {
			// Do nothing
		}
	}

	/*
	 * @see net.sourceforge.phpdt.ui.text.spelling.engine.ISpellCheckEngine#createSpellChecker(java.util.Locale,org.eclipse.jface.preference.IPreferenceStore)
	 */
	public final synchronized ISpellChecker createSpellChecker(
			final Locale locale, final IPreferenceStore store) {

		if (fLocale != null && fLocale.equals(locale))
			return fChecker;

		if (fChecker == null) {

			fChecker = new DefaultSpellChecker(store);
			store.addPropertyChangeListener(this);

			fPreferences = store;

			ISpellDictionary dictionary = null;
			for (Iterator iterator = fGlobalDictionaries.iterator(); iterator
					.hasNext();) {

				dictionary = (ISpellDictionary) iterator.next();
				fChecker.addDictionary(dictionary);
			}
		}

		ISpellDictionary dictionary = null;
		if (fLocale != null) {

			dictionary = (ISpellDictionary) fLocaleDictionaries.get(fLocale);
			if (dictionary != null) {

				fChecker.removeDictionary(dictionary);
				dictionary.unload();
			}
		}
		fLocale = locale;

		dictionary = (ISpellDictionary) fLocaleDictionaries.get(locale);
		if (dictionary == null) {

			if (!getDefaultLocale().equals(locale)) {

				if (fPreferences != null)
					fPreferences.removePropertyChangeListener(this);

				fChecker = null;
				fLocale = null;
			}

		} else
			fChecker.addDictionary(dictionary);

		if (fPreferences != null)
			propertyChange(new PropertyChangeEvent(
					this,
					ISpellCheckPreferenceKeys.SPELLING_USER_DICTIONARY,
					null,
					fPreferences
							.getString(ISpellCheckPreferenceKeys.SPELLING_USER_DICTIONARY)));

		return fChecker;
	}

	/*
	 * @see net.sourceforge.phpdt.ui.text.spelling.engine.ISpellCheckEngine#getLocale()
	 */
	public final Locale getLocale() {
		return fLocale;
	}

	/*
	 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
	 */
	public final void propertyChange(final PropertyChangeEvent event) {

		if (fChecker != null
				&& event.getProperty().equals(
						ISpellCheckPreferenceKeys.SPELLING_USER_DICTIONARY)) {

			if (fUserDictionary != null) {

				fChecker.removeDictionary(fUserDictionary);
				fUserDictionary = null;
			}

			final String file = (String) event.getNewValue();
			if (file.length() > 0) {

				try {

					final URL url = new URL("file", null, file); //$NON-NLS-1$
					InputStream stream = url.openStream();
					if (stream != null) {
						try {
							fUserDictionary = new PersistentSpellDictionary(url);
							fChecker.addDictionary(fUserDictionary);
						} finally {
							stream.close();
						}
					}
				} catch (MalformedURLException exception) {
					// Do nothing
				} catch (IOException exception) {
					// Do nothing
				}
			}
		}
	}

	/*
	 * @see net.sourceforge.phpdt.ui.text.spelling.engine.ISpellCheckEngine#registerDictionary(net.sourceforge.phpdt.ui.text.spelling.engine.ISpellDictionary)
	 */
	public synchronized final void registerDictionary(
			final ISpellDictionary dictionary) {

		fGlobalDictionaries.add(dictionary);

		if (fChecker != null)
			fChecker.addDictionary(dictionary);
	}

	/*
	 * @see net.sourceforge.phpdt.ui.text.spelling.engine.ISpellCheckEngine#registerDictionary(java.util.Locale,net.sourceforge.phpdt.ui.text.spelling.engine.ISpellDictionary)
	 */
	public synchronized final void registerDictionary(final Locale locale,
			final ISpellDictionary dictionary) {

		fLocaleDictionaries.put(locale, dictionary);

		if (fChecker != null && fLocale != null && fLocale.equals(locale))
			fChecker.addDictionary(dictionary);
	}

	/*
	 * @see net.sourceforge.phpdt.ui.text.spelling.engine.ISpellCheckEngine#unload()
	 */
	public synchronized final void unload() {

		ISpellDictionary dictionary = null;
		for (final Iterator iterator = fGlobalDictionaries.iterator(); iterator
				.hasNext();) {

			dictionary = (ISpellDictionary) iterator.next();
			dictionary.unload();
		}

		for (final Iterator iterator = fLocaleDictionaries.values().iterator(); iterator
				.hasNext();) {

			dictionary = (ISpellDictionary) iterator.next();
			dictionary.unload();
		}

		if (fPreferences != null)
			fPreferences.removePropertyChangeListener(this);

		fUserDictionary = null;
		fChecker = null;
	}

	/*
	 * @see net.sourceforge.phpdt.ui.text.spelling.engine.ISpellCheckEngine#unregisterDictionary(net.sourceforge.phpdt.ui.text.spelling.engine.ISpellDictionary)
	 */
	public synchronized final void unregisterDictionary(
			final ISpellDictionary dictionary) {

		fGlobalDictionaries.remove(dictionary);
		fLocaleDictionaries.values().remove(dictionary);

		if (fChecker != null)
			fChecker.removeDictionary(dictionary);

		dictionary.unload();
	}
}