/**
 * 
 */
package com.tandbergtv.metadatamanager.conf;

import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

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 org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.tandbergtv.metadatamanager.MetadataManagerDAO;
import com.tandbergtv.metadatamanager.RuleManagerDAO;
import com.tandbergtv.metadatamanager.factoryImpl.SpecHandlerFactory;
import com.tandbergtv.metadatamanager.search.AssetSearchService;
import com.tandbergtv.metadatamanager.spec.ISpecHandler;
import com.tandbergtv.metadatamanager.spec.ITranslator;
import com.tandbergtv.metadatamanager.spec.IValidator;
import com.tandbergtv.metadatamanager.specimpl.RuleManagerBase;
import com.tandbergtv.metadatamanager.util.MappingFileParser;

/**
 * @author vaibhav
 *
 */
public class SpecificationBuilder {
	/* The Logger */
	private static final Logger logger = Logger.getLogger(SpecificationBuilder.class);

	private static final String PLUGIN_ID = "com.tandbergtv.metadata";
	/* The Extension Point ID */
	private static final String EXTENSION_POINT_ID = "specification";
	
	/* The JPF Plug-in Manager */
	private PluginManager pluginManager = null;
	
	protected MetadataManagerDAO metadataManagerDAO;
	protected RuleManagerDAO ruleManagerDAO;
	protected AssetSearchService searchService;

	protected ISpecHandler specHandler;
	
	protected Map<String, ISpecHandler> specHandlerMap;
	protected Map<String, URL> identifierMappingResourceMap;

	private ApplicationContext context;
	
	public SpecificationBuilder(PluginManager pluginManager, ApplicationContext context) {
		this.pluginManager = pluginManager;
		this.context = context;
		
		specHandlerMap = new HashMap<String, ISpecHandler>();
		identifierMappingResourceMap = new HashMap<String, URL>();
		
		metadataManagerDAO = (MetadataManagerDAO) context.getBean("metadataManagerDAOImpl");
		ruleManagerDAO = (RuleManagerDAO) context.getBean("ruleManagerDAOImpl");
		searchService = (AssetSearchService) context.getBean("assetSearch");
	}
	
	@SuppressWarnings("unchecked")
	public void buildSpecifications() {
		logger.info("Building Specifications");
		
		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();
			PluginClassLoader classLoader = this.pluginManager.getPluginClassLoader(descriptor);
			
			String alias = extension.getParameter(SpecificationBuilderConstants.ALIAS).valueAsString();
			if(alias == null || alias.equals("")) {
				throw new RuntimeException("Alias Not Specified!");
			}
			
			String contextFile = extension.getParameter("spechandler").valueAsString();
			URL url1 = classLoader.getResource(contextFile);
			String path = url1.getPath();

			path = "file:" + path;
			
			FileSystemXmlApplicationContext jpfContext = new FileSystemXmlApplicationContext(context);
			jpfContext.setConfigLocation(path);
			jpfContext.setClassLoader(classLoader);
			
			jpfContext.refresh();

			specHandler = (ISpecHandler) jpfContext.getBean(alias);

			Parameter param = extension.getParameter(SpecificationBuilderConstants.MAPPING_RESOURCE);
			if(param != null) {
				String mappingResource = param.valueAsString();
				if(mappingResource != null && !mappingResource.equals("")) {
					URL url = classLoader.getResource(mappingResource);				
					specHandler.setMappingResourceUrl(url);
					identifierMappingResourceMap.put(alias, url);
				}
			}

			param = extension.getParameter(SpecificationBuilderConstants.SPECSPECIFIC_TTV_XPATHS_FILENAME);
			if(param != null) {
				String specSpecificTTVXpathsFileName = param.valueAsString();
				if(specSpecificTTVXpathsFileName != null && !specSpecificTTVXpathsFileName.equals("") ) {
					InputStream specSpecificTTVXpathsFileNameStream = classLoader.getResourceAsStream(specSpecificTTVXpathsFileName);
					specHandler.setSpecSpecificTTVXpathsStream(specSpecificTTVXpathsFileNameStream);
				}
			}

			/* create the toTTVTranslator */
			ITranslator toTranslator = createToTranslator(extension,
					classLoader);
			param = extension.getParameter(SpecificationBuilderConstants.TOTTV);
			if(param != null) {
				String toTTVXslPath = param.valueAsString();
				if(toTTVXslPath != null && !toTTVXslPath.equals("") ) {
					InputStream toTTVXslStream = classLoader.getResourceAsStream(toTTVXslPath);
					toTranslator.setClassLoader(classLoader);
					toTranslator.setXslStream(toTTVXslStream);
				}
			}
			
			specHandler.setToTTV(toTranslator);

			/* create the fromTTVTranslator */
			ITranslator fromTranslator = createFromTranslator(extension, classLoader);
			param = extension.getParameter(SpecificationBuilderConstants.FROMTTV);
			if(param != null) {
				String fromTTVXslPath = param.valueAsString();
				if(fromTTVXslPath != null && !fromTTVXslPath.equals("") ) {
					InputStream fromTTVXslStream = classLoader.getResourceAsStream(fromTTVXslPath);
					fromTranslator.setClassLoader(classLoader);
					fromTranslator.setXslStream(fromTTVXslStream);
				}
			}
			
			specHandler.setFromTTV(fromTranslator);
			
			/* create the schematron validator */
			param = extension.getParameter(SpecificationBuilderConstants.SCHEMATRON_VALIDATOR);
			if(param != null) {
				String validatorFilePath = param.valueAsString();
				if(validatorFilePath != null && !validatorFilePath.equals("")) {
					InputStream validatorStream = classLoader.getResourceAsStream(validatorFilePath);
					IValidator validator = createValidator(extension, classLoader, alias, validatorStream);
					Map validatorMap = new HashMap<String, IValidator>();
					validatorMap.put(alias + "Validator", validator);

					specHandler.setRuleValidators(validatorMap);
				}
			}
			
			/* create the RuleManager */
			RuleManagerBase ruleManager = createRuleManager(extension, classLoader);
			ruleManager.setSpec(alias);
			ruleManager.setRuleManagerDAO(ruleManagerDAO);

			param = extension.getParameter(SpecificationBuilderConstants.DEFAULT_RULESET);
			if(param != null) {
				String defaultRuleSetFilePath = param.valueAsString();
				
				if(defaultRuleSetFilePath != null && !defaultRuleSetFilePath.equals("")) {
					InputStream ruleSetStream = classLoader.getResourceAsStream(defaultRuleSetFilePath);
					Map<String, Boolean> defaultRuleSetMap = MappingFileParser.readFieldMappingWithBooleanValue(ruleSetStream);
					ruleManager.setDefaultRuleSet(defaultRuleSetMap);
				}
			}
			
			specHandler.setRuleManager(ruleManager);
			
			specHandlerMap.put(alias, specHandler);
		}

		SpecHandlerFactory specHandlerfactory = (SpecHandlerFactory) context.getBean("specHandlerFactory");
		specHandlerfactory.getHandlers().putAll(specHandlerMap);

		logger.info("Finished building Specifications");
	}


	/**
	 * @param extension
	 * @param classLoader
	 * @return
	 * @throws RuntimeException
	 */
	@SuppressWarnings("unchecked")
	private ITranslator createToTranslator(Extension extension,
			PluginClassLoader classLoader) throws RuntimeException {
		Class<ITranslator> toTranslatorClass = (Class<ITranslator>) loadClass(extension, classLoader, "toTtvTranslator");
		ITranslator toTranslator;
		try {
			toTranslator = toTranslatorClass.newInstance();
		} catch (Exception e) {
			throw new RuntimeException("Failed to create instance of "
					+ toTranslatorClass.getClass().getName(), e);
		}
		
		return toTranslator;
	}

	/**
	 * @param extension
	 * @param classLoader
	 * @return
	 * @throws RuntimeException
	 */
	@SuppressWarnings("unchecked")
	private ITranslator createFromTranslator(Extension extension,
			PluginClassLoader classLoader) throws RuntimeException {
		Class<ITranslator> fromTranslatorClass = (Class<ITranslator>) loadClass(extension, classLoader, "fromTtvTranslator");
		ITranslator fromTranslator;
		try {
			fromTranslator = fromTranslatorClass.newInstance();
		} catch (Exception e) {
			throw new RuntimeException("Failed to create instance of "
					+ fromTranslatorClass.getClass().getName(), e);
		}
		
		return fromTranslator;
	}
	
	/**
	 * @param extension
	 * @param classLoader
	 * @return
	 * @throws RuntimeException
	 */
	@SuppressWarnings("unchecked")
	private RuleManagerBase createRuleManager(Extension extension,
			PluginClassLoader classLoader) throws RuntimeException {
		Class<RuleManagerBase> ruleManagerClass = (Class<RuleManagerBase>) loadClass(extension, classLoader, "ruleManager");
		RuleManagerBase ruleManager;
		try {
			ruleManager = ruleManagerClass.newInstance();
		} catch (Exception e) {
			throw new RuntimeException("Failed to create instance of "
					+ ruleManagerClass.getClass().getName(), e);
		}
		
		return ruleManager;
	}

	/**
	 * @param extension
	 * @param classLoader
	 * @param validatorFilePath 
	 * @return
	 * @throws RuntimeException
	 */
	@SuppressWarnings({ "unchecked" })
	private IValidator createValidator(Extension extension,
			PluginClassLoader classLoader, String alias, InputStream validatorStream) throws RuntimeException {
		Class<IValidator> validatorClass = (Class<IValidator>) loadClass(extension, classLoader, "validator");
		IValidator validator;
		try {
			validator = validatorClass.getConstructor(String.class, InputStream.class, ClassLoader.class).newInstance(alias + "Validator", validatorStream, classLoader);
		} catch (Exception e) {
			throw new RuntimeException("Failed to create instance of "
					+ validatorClass.getClass().getName(), e);
		}
		
		return validator;
	}


	/**
	 * @param extension
	 * @param classLoader
	 * @return
	 * @throws RuntimeException
	 */
	private Class<?> loadClass(Extension extension,
			PluginClassLoader classLoader, String paramName)
			throws RuntimeException {

		Parameter className = extension.getParameter(paramName);
		String classNameStr = (className != null) ? className
				.valueAsString() : null;

		if (classNameStr == null
				|| classNameStr.trim().length() == 0) {
			throw new RuntimeException(
					"CLass not configured -  " + paramName
							+ "Make sure to set the class in the plugin ");
		}

		Class<?> clazz;
		try {
			clazz = classLoader.loadClass(classNameStr);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("Failed to load class "
					+ classNameStr, e);
		}

		return clazz;
	}
	
}
