/* ======================================================================
   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.server;

import java.util.Enumeration;
import java.util.Hashtable;

import org.apache.log4j.Logger;
import org.bodington.database.PrimaryKey;
import org.bodington.server.realm.PassPhrase;
import org.bodington.server.realm.User;
import org.bodington.server.resources.Resource;


//  This class is due to be really simplified - the client will get
//  the navigation session just once per session so this class doesn't
//  need to reference it.  In the future resource sessions ought to
//  be doled out by the navigation session.

public class BuildingSessionManagerImpl 
	implements org.bodington.server.BuildingSessionManager
	{
    
    private static Logger log = Logger.getLogger(BuildingSessionManagerImpl.class);
    
	//sessions hashed against user_id_resource_id or
	// user_idclass name
	private static Hashtable session_table= new Hashtable();
	private static long last_checked = System.currentTimeMillis();
	
	
	
	public BuildingSessionManagerImpl()
		{
		super();
		}
	
        public static void reset()
        {
            log.info("Reset");
            session_table= new Hashtable();
        }
	
	public static void dumpSessionTable()
		{
		synchronized ( session_table )
			{
			Enumeration enumeration = session_table.keys();
			String key;
			BuildingSession session;
			log.debug( "Current sessions: " );
			while ( enumeration.hasMoreElements() )
				{
				key = (String)enumeration.nextElement();
				session = (BuildingSession)session_table.get( key );
				log.debug( key );
				log.debug( "\t" );
				log.debug( session.getClass().getName() );
				try
					{
					if ( session.getResourceId() != null )
						{
						log.debug( "\t" );
						log.debug( session.getResourceId().toString() );
						}
					}
				catch ( Exception ex )
					{
					}
				}
			}
		}
	
	public static BuildingSession getSession( Resource resource )
		throws BuildingServerException
		{
		if ( resource == null || resource.getResourceId() == null )
			throw new BuildingServerException( "Session requested for unspecified resource." );
		return getSession( resource, null );
		}
	
        // this method now always returns a new session.  The client is responsible for
        // keeping a reference to it for the duration of the session.
	public static NavigationSession getSession( Class session_class )
		throws BuildingServerException
		{
		if ( session_class == null )
			throw new BuildingServerException( "Session of unknown type requested." );
		if ( !NavigationSession.class.isAssignableFrom( session_class ) )
			throw new BuildingServerException( "Requested session must be used with a specific resource." );
                try
                {
                    return (NavigationSession)session_class.newInstance();
                }
                catch ( java.lang.InstantiationException iex )
                        {
                        log.error( iex.getMessage(), iex );
                        throw new BuildingServerException( "Technical error starting session: " + iex );
                        }
                catch ( java.lang.IllegalAccessException iaex )
                        { 
                        log.error( iaex.getMessage(), iaex );
                        throw new BuildingServerException( "Technical error starting session: " + iaex );
                        }
		}
		
	private static String sessionCode( Resource r, Class session_class, User u )
		{
		// if a specific class is requested hash against user+class name
		if ( session_class!=null )
			return u.getPrimaryKey().toString() + session_class.toString();
			
		// we can be certain that a resource has been specified at this point
		// but what type of session does that imply? 
		
		Class c = r.sessionClass();
		// if resources of this type have a session and all share one session 
		// per user hash against user + RESOURCE class name
		if ( c!=null && ReusableResourceSession.class.isAssignableFrom( c ) )
			return u.getPrimaryKey().toString() + r.getClass().toString();
		
		// each resource of this type must have its own session so
		// hash against user + "_" + resource.
		return u.getPrimaryKey().toString() + "_" + r.getPrimaryKey().toString();
		}

	private static BuildingSession getSession( Resource resource, Class session_class )
		throws BuildingServerException
		{
		String code;
		BuildingSessionImpl session, othersession;
		synchronized ( session_table )
			{
			//clear out old sessions before dealing with current request
			    
			long now = System.currentTimeMillis();
			last_checked = now;
			for ( Enumeration enumeration = session_table.keys(); enumeration.hasMoreElements(); )
				{
				code = (String)enumeration.nextElement();
				othersession = (BuildingSessionImpl)session_table.get( code );
				//unused for 2 minutes...
				if ( (now - othersession.last_used) > (2*60*1000) )
					session_table.remove( code );
				}


            //now ready to fetch or create requested session
			BuildingContext context = BuildingContext.getContext();
			User user = (User)context.getUser();
			if ( user == null )
				throw new BuildingServerException( "Can't start session without user." );
			
			code = sessionCode( resource, session_class, user );
			session = (BuildingSessionImpl)session_table.get( code );
			if ( session!=null )
				{
				session.last_used = now;
				}
				
		    // if session wasn't found in the table one must be created
			if ( session == null )
				{
				try
					{
					if ( resource != null )
						{
						Class c = resource.sessionClass();
						if ( c!=null )
							session = (BuildingSessionImpl)c.newInstance();
						}
					else
						{
						session = (BuildingSessionImpl)session_class.newInstance();
						}
					}
				catch ( java.lang.InstantiationException iex )
					{
					log.error(iex.getMessage(),iex );
					throw new BuildingServerException( "Technical error starting session: " + iex );
					}
				catch ( java.lang.IllegalAccessException iaex )
					{
					log.error( iaex.getMessage(), iaex );
					throw new BuildingServerException( "Technical error starting session: " + iaex );
					}
				
				if ( session!=null )
					{
                        session.setUserId(user.getUserId());
                        if ( resource!=null )
                        	session.setResourceId( resource.getResourceId() );
					code = sessionCode( resource, session_class, user );
					session_table.put( code, session );
					}
				}
			}
			
		return session;
		}    	
	}
	
