/*
 * Copyright 2006 Maskat Project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.maskat.framework.screendef.customize;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.PropertyDescriptor;
import org.maskat.framework.NoRemoveIterator;
import org.maskat.ide.MaskatIDEPlugin;
import org.maskat.ide.property.descriptor.MaskatTextPropertyDescriptor;

public class PluginDefFactory {

	private static final String DEFAULT_PREFIX = "extend";

	/**
	 * Map of IPropertyDescriptor, which are the properties of the customized
	 * components defined in the plugin.xml. key is "prop"+"."+"component node
	 * name"+"."+"attribute name", value is the IPropertyDescriptor.
	 */
	private static Map propDescriptors = new HashMap();

	/**
	 * Map of the configuration of customized component. key is component node
	 * name. value is one IComponentPluginConfig.
	 */
	private static Map pluginConfigs = null;

	private static Map getPluginConfigs() {
		if (pluginConfigs == null) {
			init();
		}
		return pluginConfigs;
	}

	private static void init() {
		IExtensionPoint ep = Platform.getExtensionRegistry().getExtensionPoint(
				MaskatIDEPlugin.PLUGIN_ID, "customizecomponents");
		IExtension[] extensions = ep.getExtensions();
		pluginConfigs = new HashMap();
		for (int i = 0; i < extensions.length; i++) {
			IExtension ex = extensions[i];
			IConfigurationElement[] configEles = ex.getConfigurationElements();
			for (int j = 0; j < configEles.length; j++) {
				IConfigurationElement element = configEles[j];
				if (element.getName().equals("component")) {
					// 拡張部品を登録する
					registerPluginDefs(element, ex.getNamespaceIdentifier());
				}
			}
		}
	}

	/**
	 * Registers the customized component defined in the plugin registry.
	 * 
	 * @param element
	 */
	private static void registerPluginDefs(IConfigurationElement element,
			String extensionNamespace) {
		IComponentPluginConfig config = new ComponentPluginConfig();
		String componentNodeName = element.getAttribute("name");
		config.defineNodeName(componentNodeName);
		config.defineNamespace(element.getAttribute("namespace"));
		config.defineIsContainer(element.getAttribute("isContainer"));
		config.definePrefix(element.getAttribute("prefix"));
		try {
			IConfigurationElement[] children = element.getChildren("contentFactory");
			if (children.length == 1) {
				config.defineContentFactory((IContentFactory) children[0]
						.createExecutableExtension("class"));
			}
		} catch (CoreException e) {
			MaskatIDEPlugin.getDefault().getLog().log(e.getStatus());
		}

		IConfigurationElement[] children = element.getChildren("property");
		for (int i = 0; i < children.length; i++) {
			IConfigurationElement child = children[i];
			PropertyPluginConfig propConfig = new PropertyPluginConfig(child
					.getAttribute("name"), child.getAttribute("type"));
			config.defineProperty(propConfig);
			dealPropertyConfig(propConfig, componentNodeName, extensionNamespace);
		}
		pluginConfigs.put(componentNodeName, config);
	}

	private static void dealPropertyConfig(PropertyPluginConfig propConfig,
			String componentNodeName, String extensionNamespace) {
		String propertyName = propConfig.getName();
		CustCompPropDescKey propDescID = new CustCompPropDescKey(componentNodeName,
				propertyName);
		String type = propConfig.getType();
		IPropertyDescriptor descriptor = null;
		if (type.equals("readonly")) {
			descriptor = new PropertyDescriptor(propDescID, propertyName);
		}
		if (type.equals("maskattext")) {
			descriptor = new MaskatTextPropertyDescriptor(propDescID, propertyName);
		}
		if (type.equals("event")) {
			return;
			// descriptor = new EventPropertyDescriptor(propDescID,
			// propertyName);
		}
		if (descriptor == null) {
			MaskatIDEPlugin.getDefault().getLog().log(
					new Status(IStatus.ERROR, extensionNamespace, IStatus.ERROR,
							"The 'type' attribute defined for the property("
									+ propertyName + ") of component ("
									+ componentNodeName + ") is of invalid value(" + type
									+ ").", null));
			return;
		}
		propDescriptors.put(propDescID, descriptor);
	}

	/**
	 * Create an instance of the customized component specified by the nodeName
	 * and namespace. If a customized component with the nodeName and namespace
	 * exists in the plugin registry, then the instance of the component will be
	 * created, if not, a <code>PluginDefUnavailableException</code> will be
	 * thrown.
	 * 
	 * Firstly the nodeName and namespace will be checked if conform to the
	 * definition in the plugin.xml.
	 * 
	 * @param componentNodeName
	 * @param namespace
	 * @param prefix
	 * @return
	 * @throws PluginDefUnavailableException
	 */
	public static IPluginComponentDef newComponentDef(String componentNodeName,
			String namespace, String prefix) throws PluginDefUnavailableException {
		IComponentPluginConfig config = (IComponentPluginConfig) getPluginConfigs().get(
				componentNodeName);

		boolean notFound = false;
		// Firstly checks if the component with the namespace is defined in the
		// plugin definition
		if (config == null) {
			notFound = true;
		} else {
			// namespace一致チェック
			if (namespace == null)
				namespace = "";

			if (config.getNamespace() == null) {
				if (!"".equals(namespace)) {
					notFound = true;
				}
			} else {
				if (!namespace.equals(config.getNamespace())) {
					notFound = true;
				}
			}
		}
		if (notFound)
			throw new PluginDefUnavailableException("Component definition for \""
					+ componentNodeName + "\" is not found.");
		if (prefix == null || "".equals(prefix)) {
			if (namespace != null && !("".equals(namespace))) {
				// If the namespace is defined without a prefix, use the default
				// prefix.
				prefix = DEFAULT_PREFIX;
			}
		}
		IPluginComponentDef def;
		if (config.getContentFactory() != null) {
			// if contentFactory is defined, then create an instance of the
			// IPluginComponentDef.(def must be a subclass of
			// IPluginComponentDef)
			def = config.getContentFactory().newDef(new PluginDefProxy(config, prefix));
			if (def != null) {
				def.setAttribute("name", "new" + config.getNodeName());
				return def;
			}
		}

		if (config.isContainer()) {
			// new a ContainerDef if isContainer is true.
			def = new PluginContainerDef(config, prefix);
		} else {
			def = new PluginComponentDef(config, prefix);
		}
		def.setAttribute("name", "new" + config.getNodeName());
		return def;
	}

	// public IPropertyDescriptor getPropertyDescriptor(String compNodeName,
	// String propertyName) throws PluginDefUnavailableException {
	// if (propertyName == null)
	// throw new PluginDefUnavailableException("No IPropertyDescriptor
	// defined.");
	// return (IPropertyDescriptor) propDescriptors.get(new CustCompPropDescKey(
	// compNodeName, propertyName));
	// }

	/**
	 * Get the IPropertyDescriptor array of the customized component specified
	 * by the nodeName of the component.
	 * 
	 * @param compNodeName
	 * @return
	 */
	public static IPropertyDescriptor[] getPropDescs(String compNodeName) {
		IComponentPluginConfig config = (IComponentPluginConfig) getPluginConfigs().get(
				compNodeName);
		Iterator it = config.getPropertyNames();
		List temp = new ArrayList();
		while (it != null && it.hasNext()) {
			String propName = (String) it.next();
			Object propDesc = propDescriptors.get(new CustCompPropDescKey(compNodeName,
					propName));
			if (propDesc != null) {
				// if the propDesc is not registered(maybe because the type
				// attribute of the "property" node is wrongly set
				temp.add(propDesc);
			}
		}
		IPropertyDescriptor[] result = new IPropertyDescriptor[temp.size()];
		return (IPropertyDescriptor[]) temp.toArray(result);
	}

	public static PropertyPluginConfig getPropConfig(String compNodeName, String propName)
			throws PluginDefUnavailableException {
		IComponentPluginConfig compConfig = (IComponentPluginConfig) getPluginConfigs()
				.get(compNodeName);
		if (compConfig == null)
			throw new PluginDefUnavailableException("Component definition for \""
					+ compNodeName + "\" is not found.");
		PropertyPluginConfig propConfig = compConfig.getPropertyConfig(propName);
		if (propConfig == null)
			throw new PluginDefUnavailableException("Property definition for \""
					+ propName + "\" is not found in component " + compNodeName + ".");
		return propConfig;
	}

	/**
	 * Get the iterator for the <code>IComponentPluginConfig</code> for all
	 * the component plugged in.
	 * 
	 * @return
	 */
	public static Iterator getPluginComps() {
		return new NoRemoveIterator(getPluginConfigs().values().iterator());
	}
}