/**
 * License Agreement.
 *
 * Rich Faces - Natural Ajax for Java Server Faces (JSF)
 *
 * Copyright (C) 2007 Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package org.richfaces.skin;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.faces.FacesException;
import javax.faces.context.FacesContext;

import org.ajax4jsf.Messages;
import org.ajax4jsf.resource.util.URLToStreamHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Base factory class ( implement Singleton design pattern ). Produce self
 * instance to build current skin configuration. At present, realised as lazy
 * creation factory. TODO - select point to initialize.
 * 
 * @author asmirnov@exadel.com (latest modification by $Author: alexsmirnov $)
 * @version $Revision: 1.1.2.1 $ $Date: 2007/01/09 18:59:43 $
 * 
 */
public abstract class SkinFactory {

	/**
	 * static instance variable.
	 */
	private static Map<ClassLoader, SkinFactory> instances = 
		Collections.synchronizedMap(new HashMap<ClassLoader, SkinFactory>());

	private static final Log log = LogFactory.getLog(SkinFactory.class);


	public static void reset() {
		instances = Collections.synchronizedMap(new HashMap<ClassLoader, SkinFactory>());
	}

	/**
	 * Initialize skin factory. TODO - make call from init() method of any
	 * servlet or custom faces element method ??? If exist resource
	 * META-INF/services/org.richfaces.skin.SkinFactory , create
	 * instance of class by name from first line of this file. If such class
	 * have constructor with {@link SkinFactory} parameter, instantiate it with
	 * instance of default factory ( as usual in JSF ). If any error occurs in
	 * instantiate custom factory, return default.
	 */
	public static final SkinFactory getInstance() {
	    ClassLoader loader = Thread.currentThread().getContextClassLoader();
	    SkinFactory instance = (SkinFactory) instances.get(loader);
			if (instance == null) {
				// Pluggable factories.
				InputStream input = null; // loader.getResourceAsStream(SERVICE_RESOURCE);
				input = URLToStreamHelper.urlToStreamSafe(
						loader.getResource(SERVICE_RESOURCE));
				// have services file.
				if (input != null) {
					try {
						BufferedReader reader = new BufferedReader(
								new InputStreamReader(input));
						String factoryClassName = reader.readLine();
						if (log.isDebugEnabled()) {
							log.debug(Messages.getMessage(
									Messages.SET_SKIN_FACTORY_INFO,
									factoryClassName));
						}
						instance = instantiateSkinFactory(factoryClassName, loader);						
					} catch (Exception e) {
						log
								.warn(
										Messages
												.getMessage(Messages.CREATING_SKIN_FACTORY_ERROR),
										e);
					} finally {
						try {
							input.close();
						} catch (IOException e) {
							// can be ignored
						}

					}
				}
				if (instance == null) {				    
				    // instantiate default implementation of SkinFactory - org.richfaces.skin.SkinFactoryImpl,
				    // placed in the richfaces-impl.jar
				    instance = instantiateSkinFactory(DEFAULT_SKIN_FACTORY_IMPL_CLASS, loader);				    
				}
			instances.put(loader, instance);	
			}

		return instance;
	}
	
	/**
	 * Create new instance of class with given name with the help of given <code>ClassLoader</code>.
	 * Instantiated class should extend <code>SkinFactory</code> base class.
	 * @param factoryClassName - class name of SkinFactory
	 * @param classLoader - class loader
	 * @return - instantiated <code>SkinFactory</code>
	 * @throws FacesException - FacesException is thrown when instantiation fails;
	 *                          causing exception is wrapped into <code>FacesException</code>  
	 */
	private static SkinFactory instantiateSkinFactory(String factoryClassName, ClassLoader classLoader) throws FacesException {
	    SkinFactory instance = null;
	    try {
		Class<?> clazz = Class.forName(factoryClassName, false, classLoader);
		try {
		    // try construct factory chain.
		    Constructor<?> factoryConstructor = clazz.getConstructor(new Class[] { SkinFactory.class });
		    instance = (SkinFactory) factoryConstructor.newInstance(new Object[] { instance });
		} catch (NoSuchMethodException e) {
		    // no chain constructor - attempt default.
		    instance = (SkinFactory) clazz.newInstance();
		}
	    } catch (InvocationTargetException ite) {
		log.error(Messages.getMessage(Messages.CREATING_SKIN_FACTORY_ERROR), ite);
		throw new FacesException("Exception when creating instance of [" + SkinFactory.class.getName() + "]", ite);
	    } catch (InstantiationException ie) {
		log.error(Messages.getMessage(Messages.CREATING_SKIN_FACTORY_ERROR), ie);
		throw new FacesException("Exception when creating instance of [" + SkinFactory.class.getName() + "]", ie);
	    } catch (IllegalAccessException iae) {
		log.error(Messages.getMessage(Messages.CREATING_SKIN_FACTORY_ERROR), iae);
		throw new FacesException("Exception when creating instance of [" + SkinFactory.class.getName() + "]", iae);
	    } catch (ClassNotFoundException cnfe) {
		log.error(Messages.getMessage(Messages.CREATING_SKIN_FACTORY_ERROR), cnfe);
		throw new FacesException("Exception when creating instance of [" + SkinFactory.class.getName() + "]", cnfe);
	    }

	    return instance;
	}

	/**
	 * Get default {@link Skin} implementation.
	 * 
	 * @param context
	 * @return
	 */
	public abstract Skin getDefaultSkin(FacesContext context);
	
	/**
	 * Get current {@link Skin} implementation.
	 * @param context
	 * @return
	 */
	public abstract Skin getSkin(FacesContext context);
	
//	public abstract SkinConfiguration getSkinConfiguration(FacesContext context);
	
	
	
	/**
	 * Resource Uri for file with name of class for application-wide SkinFactory same as SPI definitions for common Java SAX, Jsf etc. factories
	 */
	public static final String SERVICE_RESOURCE = "META-INF/services/" + SkinFactory.class.getName();

	/**
	 * Name of web application init parameter for current skin . Can be simple
	 * String for non-modified name, or EL-expression for calculate current
	 * skin. If EL evaluated to <code>String</code> - used as skin name, if to
	 * instance of {@link Skin } - used this instance. by default -
	 * "org.exadel.chameleon.SKIN"
	 */
	public static final String SKIN_PARAMETER = "org.richfaces.SKIN";

	public static final String BASE_SKIN_PARAMETER = "org.richfaces.BASE_SKIN";
	
	/**
	 * Full class name of default implementation of the SkinFactory class 
	 */
	public static final String DEFAULT_SKIN_FACTORY_IMPL_CLASS = "org.richfaces.skin.SkinFactoryImpl";

	/**
	 * Get base {@link Skin} implementation
	 * @param facesContext
	 * @return
	 */
	public abstract Skin getBaseSkin(FacesContext facesContext);
	
	/**
	 * @param facesContext
	 * @param name
	 * @return
	 */
	public abstract Theme getTheme(FacesContext facesContext, String name);

}
