/**
 * 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.util.Collections;
import java.util.HashMap;
import java.util.Map;

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));
						}
						Class<?> clazz = Class.forName(factoryClassName, false, loader);
						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 (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) {
				    try{
					// instantiate default implementation of SkinFactory - org.richfaces.skin.SkinFactoryImpl,
				    	// placed in the richfaces-impl.jar
					instance = (SkinFactory) Class.forName(DEFAULT_SKIN_FACTORY_IMPL_CLASS).newInstance();
				    } catch (InstantiationException ie) {
					log.error(Messages.getMessage(Messages.CREATING_SKIN_FACTORY_ERROR), ie);
				    } catch (IllegalAccessException iae) {
					log.error(Messages.getMessage(Messages.CREATING_SKIN_FACTORY_ERROR), iae);
				    } catch (ClassNotFoundException cnfe) {
					log.error(Messages.getMessage(Messages.CREATING_SKIN_FACTORY_ERROR), cnfe);
				    }
				}
			instances.put(loader, instance);	
			}

		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);

}
