/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

====================================================================== */

package org.bodington.servlet;

import java.io.File;
import java.util.*;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import org.bodington.servlet.facilities.*;
import org.bodington.server.BuildingContext;
import org.bodington.server.ServerClassLoader;

/**
 * Builds a list of all the Facilities in Bodington.
 * It uses the bodington properties to configure itself.
 * Currently this is a singleton.
 * 
 * The configuration properties that this class uses are:
 * <pre>
 * buildingservlet.facilities=default,alias
 * buildingservlet.facility.alias=25,org.bodington.servlet.facilities.AliasFacility
 * buildingservlet.facility.default=1,org.bodington.servlet.facilities.Facility
 * ...
 * </pre>
 * 
 * The property
 * <code>buildingservlet.facilities</code> is the list of Facilities that should
 * be loaded as specified by their short name. The nicknames are comma separated.
 * The nickname is used to lookup the Facility property so the nickname of
 * <code>alias</code> maps to the property
 * <code>buildingservlet.facility.alias</code>.
 * 
 * The properties <code>buildingservlet.facility.nickname</code> is followed
 * by the Facility number which is used when mapping resources to Facilities 
 * {@link org.bodington.server.resources.Resource#getHttpFacilityNo()}. After the
 * comma is the classname that should be used to handle this Facility. It should
 * have a no argument constructor and should extend {@link Facility}. For each
 * entry a new instance of the class is created even if the classname is the same.
 * So in a typical Bodington installation there will several instances of the 
 * Facility class as it handles several different resource types.
 * 
 * The (flawed?) reasoning for this is so that the same resource could be handled 
 * by different Facilities at different points in the same tree, although this
 * isn't currently used in any installation.
 * 
 * @see Facility
 */
public class FacilityList
	{
	
    private static final String BUILDINGSERVLET_FACILITY = "buildingservlet.facility.";

    private static final String BUILDINGSERVLET_FACILITIES = "buildingservlet.facilities";

    private Logger log = Logger.getLogger(FacilityList.class);

	Hashtable facilities;
	Hashtable bynickname;
	Facility deff=null;
	
	private static FacilityList instance;
	

	/**
	 * Gets a FailcityList object back.
	 * @return A FacilityList.
	 */
	public static FacilityList getFacilities()
		{
		if ( instance == null )
			{
			instance = new FacilityList();
			}
		return instance;
		}

	
    /**
     * Constructor for FacilityList.
     * This is not private so to allow testing of the parsing of configuration.
     */
	FacilityList()
		{
		Facility f;
		Class c;
		String name, nickname, archive;
		Integer id=null;
		StringTokenizer tokenizer, f_tokenizer;
		
		log.debug( "FacilityList constructor." );

		facilities=new Hashtable();
		bynickname=new Hashtable();

		String loaded = BuildingContext.getProperty( BUILDINGSERVLET_FACILITIES );
		if ( loaded == null )
        {
            log.error("Facilities are unconfigured. "+
                "Please set the property buildingservlet.facilities");
			return;
        }

		log.debug( loaded );
			
		tokenizer = new StringTokenizer( loaded, "," );
		String next_nickname, config_line;
		
		while ( tokenizer.hasMoreTokens() )
			{

			next_nickname = tokenizer.nextToken();
			config_line = BuildingContext.getProperty( BUILDINGSERVLET_FACILITY + next_nickname );
		
            if (config_line == null)
            {
                log.error("Failed to load " + next_nickname
                    + ". Can't find property: " + BUILDINGSERVLET_FACILITY
                    + next_nickname);
                continue;
            }


			f_tokenizer = new StringTokenizer( config_line, "," );
			if ( f_tokenizer.countTokens() < 2 )
            {
                log.error("Property is badly formatted: "+ config_line);
				continue;
            }
			try
				{
				id = new Integer( f_tokenizer.nextToken() );
				}
			catch ( NumberFormatException nfex )
				{
                log.error("Cannot convert to number: "+ config_line);
				continue;
				}
		
			log.debug( "ID = " + id );

			nickname=next_nickname;
    		
			name=f_tokenizer.nextToken();
   			name=name.trim();
			if ( name.equalsIgnoreCase( "null" ) || name.length()==0 )
				name=null;
    			
			log.debug( "class = [" + name + "] [" + nickname + "]" );

            
            if ( f_tokenizer.hasMoreTokens() )
                archive = f_tokenizer.nextToken().trim();
            else
                archive=null;

			try
				{
				if ( archive == null )
    				c = Class.forName( name );
    		    else
    		        {
			        ServerClassLoader loader = ServerClassLoader.getClassLoader( new File( archive ), this.getClass().getClassLoader() );
			        c = loader.loadClass( name );
    		        }
				}
			catch ( Exception e )
				{
				log.error( "Problem loading class: "+ name );
				continue;
				}
			try
				{
				f=(Facility)c.newInstance();
				f.facilityname=nickname;
				}
			catch ( Exception e )
				{
				log.error( "Problem instantiating class: "+ name );
				continue;
				}

			log.debug( "instantiated" );
			if ( nickname.startsWith( "default" ) )
			    {
				deff=f;
                log.debug("Default is set to: "+ deff.getClass());
				}
			//can't init the facilities until we're done with the
			//database connection
			
			//store the instantiated class in hashtables
			//for future use
            if (facilities.containsKey(id) && log.isEnabledFor(Level.WARN))
                log.warn("Replacing ID " + id + ". Changing from "
                    + facilities.get(id).getClass() + " with " + f.getClass());
			facilities.put( id, f );
            
            if (bynickname.containsKey(nickname) && log.isEnabledFor(Level.WARN))
                log.warn("Replacing Nickname "+ nickname+ ". Changing from "
                    + bynickname.get(nickname).getClass() + " with " + f.getClass());
			bynickname.put( nickname, f );
			}		


		//call init method in each
		//facility
        int facilityCount = 0;
		for ( Enumeration enumeration=facilities.keys(); enumeration.hasMoreElements(); )
			{
			id=(Integer)enumeration.nextElement();
			if ( id!=null )
				{
				f=(Facility)facilities.get( id );

				if ( f != null )
					{
                    List contains = getContainable(f.facilityname);
					f.init( id, deff, contains );
                    facilityCount++;
					}
				}
			}
        if (log.isInfoEnabled())
            log.info("Setup Complete. Loaded "+ facilityCount+ " facilities. Default is: ("+ deff.facilityname+ ") "+ deff.getClass());
		}


    private List getContainable(String name)
    {
        String containables = BuildingContext.getProperty(BUILDINGSERVLET_FACILITY+name+".contains");
        List contains = new LinkedList(); 
        if (containables != null)
        {
            Enumeration containableNames = new StringTokenizer(containables,",");
            while( containableNames.hasMoreElements() )
            {
                String facilityName = (String)containableNames.nextElement();
                Facility facility = (Facility)bynickname.get(facilityName);
                if (facility != null)
                    contains.add(facility);
                else
                    log.warn("Couldn't find facility by name of: "+ facilityName+ " to add to "+ name);
            }
            
        }
        return contains;
    }
		
	/**
	 * Returns the Facility associated with the id.
	 * @param id A integer to represent a facility.
	 * @return The Facility for this id, if it can't be found return the default.
	 */
	public Facility get( Integer id )
		{
		Facility f = (Facility)facilities.get( id );
		if ( f == null )
			f = deff;
		return f;
		}
	
	/**
	 * Looks up the Facility associated with this nickname.
	 * @param nickname A short string to represent a facility.
	 * @return The Facility for this nickname, if it can't be found return the default.
	 */
	public Facility get( String nickname )
		{
		Facility f = (Facility)bynickname.get( nickname );
		if ( f == null )
			f = deff;
		return f;
		}
	}
