/**
 * TitleProviderFactory.java
 * Created May 20, 2008
 * Copyright (c) TANDBERG Television 2007-2008
 */
package com.tandbergtv.watchpoint.pmm.title.provider.internal;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;

import org.java.plugin.PluginManager;
import org.java.plugin.registry.Extension;
import org.java.plugin.registry.Extension.Parameter;

import com.tandbergtv.watchpoint.pmm.title.provider.ITitleProvider;
import com.tandbergtv.watchpoint.pmm.title.provider.ITitleProviderProperties;
import com.tandbergtv.watchpoint.pmm.title.provider.ITitleSearchStrategy;

/**
 * Creates an {@link ITitleProvider} instance given the JPF extension
 * 
 * @author Sahil Verma
 */
public class TitleProviderFactory {
	
	/**
	 * Creates a factory instance
	 * 
	 * @return
	 */
	public static TitleProviderFactory newInstance() {
		return new TitleProviderFactory();
	}
	
	/**
	 * Creates the {@link ITitleProvider}
	 * 
	 * @param pluginManager
	 * @param extension
	 * @return
	 */
	public ITitleProvider createProvider(PluginManager pluginManager, Extension extension) {
		/* Read Extension Properties */
		String systemId = extension.getParameter("systemId").valueAsString();
		String name = extension.getParameter("name").valueAsString();
		Parameter classParameter = extension.getParameter("class");
		String className = (classParameter != null) ? classParameter.valueAsString() : null;
		String searchClassName = extension.getParameter("searchClass").valueAsString();
		Collection<Parameter> specParameters = extension.getParameters("specification");
		Collection<String> specNames = new ArrayList<String>();
		for (Parameter specParameter : specParameters)
			specNames.add(specParameter.valueAsString());

		/* Remember to load the class using the right classloader */
		ClassLoader loader = pluginManager.getPluginClassLoader(extension.getDeclaringPluginDescriptor());
		ITitleSearchStrategy strategy = createSearchStrategy(loader, searchClassName);

		/* Update the provider properties and create provider */
		ITitleProviderProperties properties = new TitleProviderProperties();
		properties.setSystemID(systemId);
		properties.setName(name);
		properties.setSupportedSpecifications(specNames);
		properties.setSearchStrategy(strategy);
		ITitleProvider provider = createTitleProvider(loader, className, properties);
		strategy.setTitleProvider(provider);

		return provider;
	}
	
	@SuppressWarnings("unchecked")
	private ITitleSearchStrategy createSearchStrategy(ClassLoader loader, String className) {
		ITitleSearchStrategy instance = null;
		Class<ITitleSearchStrategy> clazz;
		
		try {
			clazz = (Class<ITitleSearchStrategy>)loader.loadClass(className);
			instance = clazz.newInstance();
		} catch (Exception e) {
			throw new RuntimeException("Failed to create instance of " + className, e);
		}
		
		return instance;
	}

	/*
	 * Create an instance of the input class name using the class loader, and ensure that the
	 * instance created can be cast from the given type.
	 */
	private ITitleProvider createTitleProvider(ClassLoader loader, String className, ITitleProviderProperties properties) {
		ITitleProvider provider = null;
		
		try {
			Class<?> clazz = loader.loadClass(className);
			
			provider = getTitleProvider(clazz, properties, loader);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("Failed to load " + className, e);
		} catch (Exception e) {
			throw new RuntimeException("Failed to create provider of class " + className, e);
		}
		
		return provider;
	}
	
	private ITitleProvider getTitleProvider(Class<?> clazz, ITitleProviderProperties properties, ClassLoader loader) 
		throws Exception {
		ITitleProvider provider = null;
		Constructor<?> constructor = null;
		
		try {
			constructor = clazz.getConstructor(ITitleProviderProperties.class, ClassLoader.class);
			provider = (ITitleProvider) constructor.newInstance(properties, loader);
		} catch (NoSuchMethodException e) {
			/* Keep back-compat */
			constructor = clazz.getConstructor(ITitleProviderProperties.class);
			provider = (ITitleProvider) constructor.newInstance(properties);
		}
		
		return provider;
	}
}
