/*
 * Created on Sep 15, 2008 (C) Copyright TANDBERG Television Ltd.
 */

package com.tandbergtv.watchpoint.pmm.title.conf;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;

import org.apache.log4j.Logger;
import org.java.plugin.PluginClassLoader;
import org.java.plugin.PluginManager;
import org.java.plugin.registry.Extension;
import org.java.plugin.registry.ExtensionPoint;
import org.java.plugin.registry.PluginDescriptor;
import org.java.plugin.registry.PluginRegistry;
import org.java.plugin.registry.Extension.Parameter;

import com.tandbergtv.metadatamanager.spec.ISpecHandler;

/**
 * @author Vijay Silva
 */
public class SpecificationBuilder {

	/* The Logger */
	private static final Logger logger = Logger.getLogger(SpecificationBuilder.class);

	/* The Plug-in ID */
	private static final String PLUGIN_ID = "com.tandbergtv.metadata";

	/* The Extension Point ID */
	private static final String EXTENSION_POINT_ID = "provider";

	/* The Plug-in Resource Path to the Specification Definition XML */
	private static final String DEFINITION_PATH_PARAMETER = "definitionResource";
	
	/* The Plug-in Resource Path to the Specification Table Configuration XML */
	private static final String TABLE_CONFIG_PARAMETER = "tableConfigurationResource";
	
	private static final String SPEC_ALIAS = "alias";
	
	private static final String BUNDLE_NAME = "resourcebundle";

	/* The JPF Plug-in Manager */
	private PluginManager pluginManager = null;
	
	private Map<String, ISpecHandler> spechandlers;

	/* Internal Constructor */
	private SpecificationBuilder(PluginManager pluginManager, Map<String, ISpecHandler> spechandlers) {
		this.pluginManager = pluginManager;
		this.spechandlers = spechandlers;
	}

	/**
	 * Create an instance of the Specification Builder
	 * 
	 * @return The Specification Builder
	 */
	public static SpecificationBuilder createInstance(PluginManager pluginManager, 
		Map<String, ISpecHandler> spechandlers) {
		return new SpecificationBuilder(pluginManager, spechandlers);
	}

	/**
	 * Build the collection of specifications that extend the provided specification extension
	 * point.
	 * 
	 * @return The collection of specifications
	 */
	public Collection<Specification> buildSpecifications() {
		Collection<Specification> specifications = new ArrayList<Specification>();

		PluginRegistry pluginRegistry = this.pluginManager.getRegistry();
		ExtensionPoint point = pluginRegistry.getExtensionPoint(PLUGIN_ID, EXTENSION_POINT_ID);
		Collection<Extension> extensions = point.getAvailableExtensions();

		for (Extension extension : extensions) {
			PluginDescriptor descriptor = extension.getDeclaringPluginDescriptor();
			
			try {
				if (!pluginManager.isPluginActivated(descriptor))
						pluginManager.activatePlugin(descriptor.getId());
				
				/* Validate the extension */
				this.validateExtension(extension);

				/* Build the specification */
				Specification specification = this.buildSpecification(extension);

				/* Validate the specification */
				this.validateSpecification(specification);

				/* Store the list of specifications */
				specifications.add(specification);
				
				logger.debug("Added support for " + specification.getName());
			} catch (Exception e) {
				String key = descriptor.getId() + ":" + extension.getId();
				logger.error("Failed to load title specification extension: " + key, e);
			}
		}

		/* Exclude all duplicate specifications, logging appropriate warning */
		this.removeDuplicates(specifications);
		
		/* Return the set of successfully loaded specifications */
		return specifications; 
	}

	/* Build the Specification object given the Extension */
	private Specification buildSpecification(Extension extension) throws SpecificationValidationException {
		PluginDescriptor descriptor = extension.getDeclaringPluginDescriptor();
		PluginClassLoader classloader = this.pluginManager.getPluginClassLoader(descriptor);

		String definitionPath = extension.getParameter(DEFINITION_PATH_PARAMETER).valueAsString();
		InputStream stream = classloader.getResourceAsStream(definitionPath);

		SpecificationReader reader = new SpecificationReader();
		Specification specification = reader.unmarshal(stream);
		
		specification.setClassLoader(classloader);
		
		String tableConfigPath = extension.getParameter(TABLE_CONFIG_PARAMETER).valueAsString();
		specification.setDefinitionPath(definitionPath);
		specification.setTableConfigurationPath(tableConfigPath);
		
		setSpecHandler(specification, extension);
		
		Parameter bundleparameter = extension.getParameter(BUNDLE_NAME);
		
		if (bundleparameter != null) {
			logger.debug("Bundle name " + bundleparameter.valueAsString());
			ResourceBundle bundle = 
				ResourceBundle.getBundle(bundleparameter.valueAsString(), Locale.getDefault(), classloader);
			
			specification.setBundle(bundle);
		}
		
		return specification;
	}
	
	private void setSpecHandler(Specification specification, Extension extension) {
		Parameter alias = extension.getParameter(SPEC_ALIAS);
		
		if (alias == null) {
			logger.warn("No alias found, specification " + specification.getName() + " won't have an ISpecHandler");
			return;
		}
		
		ISpecHandler sh = this.spechandlers.get(alias.valueAsString());

		if (sh != null)
			logger.debug("Found " + sh.getClass().getName() + " for " + specification.getName());
		
		specification.setSpecHandler(sh);
	}

	/* Validate the extension parameters, ensure that the schemas are obeyed */
	private void validateExtension(Extension extension) throws SpecificationValidationException {
	}

	/* Validate the Specification ensuring that the data in the definition xml is valid */
	private void validateSpecification(Specification specification)
			throws SpecificationValidationException {
	}

	/* Go through all specifications, and remove any that have the same name */
	private void removeDuplicates(Collection<Specification> allSpecifications) {
		Map<String, List<Specification>> specificationNameMap = new HashMap<String, List<Specification>>();

		/* Group all specifications by name */
		for (Specification specification : allSpecifications) {
			String name = specification.getName();
			List<Specification> specifications = specificationNameMap.get(name);
			if (specifications == null) {
				specifications = new ArrayList<Specification>();
				specificationNameMap.put(name, specifications);
			}
			specifications.add(specification);
		}

		/* Remove specifications with duplicate names */
		for (String name : specificationNameMap.keySet()) {
			List<Specification> specifications = specificationNameMap.get(name);
			if (specifications.size() > 1) {
				allSpecifications.removeAll(specifications);
				logger.warn("Detected duplicate specifications(" + specifications.size()
						+ ") with same name: " + name
						+ ", ignoring all specifications with this name.");
			}
		}
	}
}
