/////////////////////////////////////////////////////////////////////////
//
// © University of Southampton IT Innovation Centre, 2004
//
// Copyright in this library belongs to the IT Innovation Centre of
// 2 Venture Road, Chilworth Science Park, Southampton, SO16 7NP, UK.
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
//      Created by:             Stuart E. Middleton
//      Created date:           2004/04/30
//      Created for project:    GEMSS
//
/////////////////////////////////////////////////////////////////////////
//
//      Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//      Last commit info:       $Author: $
//                              $Date: $
//                              $Revision: $
//
/////////////////////////////////////////////////////////////////////////

package uk.ac.soton.itinnovation.gemss.state;

import java.lang.*;
import java.util.*;
import java.io.*;
import java.nio.channels.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/*
 * State repository that provides a primary key to a collection of state objects. For each
 * key (e.g. conversation ID) there is a state object, which contains a set of objects and flags.
 */
public class StateRepository extends Object {

	// member variables
	private Hashtable mhashObjects;
	private String mstrLockFilename;
	private String mstrRepositoryFilename;
	private boolean mbDestroy;
	private boolean mLockAvailable = true;

	// logger variable
	private Logger mlogger = Logger.getLogger("uk.ac.soton.itinnovation.gemss.state.StateRepository");

	/**
	 * constructor
	 * @param strRepositoryFilename location of the respository
	 */
	public StateRepository( String strRepositoryFilename ) throws IOException
	{
		super();

		// init
		mhashObjects = new Hashtable();
		mstrRepositoryFilename = strRepositoryFilename;
		mbDestroy = false;
		// all done
	}

	/**
	 * destructor, will delete the lock file if it exists
	 */
	public void finalize() throws Throwable
	{
		// flag to stop any blocked operations
		mbDestroy = true;

		// clear the contents of the hash tables (state destructor will release memory)
		mhashObjects.clear();

        unlock();

		// all done
		return;
	}

      /**
       * Lock the state repository to prevent access. This is so that only one client can change !any! state
	   * Is that too strong??
       */
      public synchronized void lock()
      {
			while(!mLockAvailable) {
				try {
					wait();
				}
				catch(InterruptedException ex) {

				}
			}
			
			mLockAvailable= false;
      }

      /**
       * Unlock the state repository to prevent access. This method will block until unlock is achieved.
       */
      public synchronized void unlock()
      {
            mLockAvailable = true;
			notifyAll();
      }

      /**
       * Update repository from file, loading the current state
       */
      public synchronized void load() throws Exception
      {
            ObjectInputStream inStream;
            File file;
            int nObjects;
            int i;
            String strKey;

            try {
                  // clear existing repository
                  mhashObjects.clear();

                  // check lock
                  if( mLockAvailable ) throw new Exception("Cannot load if repository not locked");

                  // check file exists
                  file = new File( mstrRepositoryFilename );
                  if( !file.exists() ) return; // nothing to load

                  // load all state from the repository
                  inStream = new ObjectInputStream( new FileInputStream( mstrRepositoryFilename ) );

                  // init hash of state objects
                  if( mhashObjects == null )
                        mhashObjects = new Hashtable();
                  mhashObjects.clear();

                  // read the number of object/label pairs
                  nObjects = inStream.readInt();

                  // read all object/label pairs [key,object]
                  for( i=0;i<nObjects;i++ ) {
                        strKey = (String) inStream.readObject();
                        mhashObjects.put( strKey,inStream.readObject() );
                  }

                  // close up
                  inStream.close();

                  // loaded ok
                  return;
            }
            catch( Throwable th ) {
                  mlogger.log( Level.SEVERE,"Failed to load state from repository",th );
                  throw new Exception("Failed to load state from repository");
            }
      }

      /**
       * Save state to the respository, overwriting all existing state
       */
      public synchronized void save() throws Exception
      {
            ObjectOutputStream outStream;
            int i;
            Enumeration enumKeys;
            String strKey;
			Object obj;

            try {
                  // check lock
                  if( mLockAvailable ) throw new Exception("Cannot save if repository not locked");

                  // save all state to the repository
                  outStream = new ObjectOutputStream( new FileOutputStream( mstrRepositoryFilename,false ) );

                  // write the number of object/label pairs
                  outStream.writeInt( mhashObjects.size() );

                  // write the hash of state objects to the stream
                  enumKeys = mhashObjects.keys();
                  while( enumKeys.hasMoreElements() ) {
                        strKey = (String) enumKeys.nextElement();
                        outStream.writeObject( strKey );
						obj = mhashObjects.get( strKey );
                        outStream.writeObject( obj );
                  }

                  // close up
                  outStream.close();

                  // saved ok
                  return;
            }
            catch( Throwable th ) {
                  mlogger.log( Level.SEVERE,"Failed to load state from repository",th );
                  throw new Exception("Failed to load state from repository");
            }
      }


	/**
	 * add a flag to the state respository
       * @param strKey primary key identifying the state collection
	 * @param strFlagName The name of the flag to be asserted
	 */
	synchronized public void addFlag( String strKey, String strFlagName )
	{
            State state;

		try
		{
			// check params
			if( strKey == null ) throw new Exception("Null parameters");

                  // get state object (or make it)
                  state = (State) mhashObjects.get( strKey );
                  if( state == null ) state = makeState( strKey );

   			// add string to vector of flags
			state.addFlag( strFlagName );

			// all done
			return;
		}
		catch ( Exception ex )
		{
			mlogger.log( Level.SEVERE,"Exception during addFlag",ex);
			return;
		}

	}

	/**
	 * check to see if a flag has been asserted
       * @param strKey primary key identifying the state collection
	 * @param strFlagName The name of the flag to check
	 * @return true if flag is asserted, false if not asserted or on error
	 */
	synchronized public boolean checkFlag( String strKey, String strFlagName )
	{
            State state;

		try
		{
			// check params
			if( strKey == null ) throw new Exception("Null parameters");

                  // get state object (or make it)
                  state = (State) mhashObjects.get( strKey );
                  if( state == null ) return false;


			// return if flag exists
			return state.checkFlag( strFlagName );
		}
		catch ( Exception ex )
		{
			mlogger.log( Level.SEVERE,"Exception during checkFlag",ex);
			return false;
		}
	}

	/**
	 * Get all flags. Returns null on error.
       * @param strKey primary key identifying the state collection
	 * @return vector conaining flag strings
	 */
	synchronized public Vector getFlags( String strKey )
	{
            State state;

		try
		{
                  // check params
                  if( strKey == null ) throw new Exception("Null parameter");

                  // get state object (or make it)
                  state = (State) mhashObjects.get( strKey );
                  if( state == null ) return null;

                  // return flags
                  return state.getFlags();
		}
		catch ( Exception ex )
		{
			mlogger.log( Level.SEVERE,"Exception during getFlags",ex);
			return null;
		}
	}

	/**
	 * Add an object to this state's object list.
       * @param strKey primary key identifying the state collection
	 * @param strObjectName The name of the object's label
	 * @param object The object itself
	 */
	synchronized public void addObject( String strKey, String strObjectName, Object object )
	{
            State state;

		try
		{
                  // check params
                  if( strKey == null ) throw new Exception("Null parameter");

                  // get state object (or make it)
                  state = (State) mhashObjects.get( strKey );
                  if( state == null ) state = makeState( strKey );

                  // add object
                  state.addObject( strObjectName, object );

                  return;
		}
		catch ( Exception ex )
		{
			mlogger.log( Level.SEVERE,"Exception during addObject",ex);
			return;
		}
	}

	/**
	 * get an object from this states object store, or null if not found
       * @param strKey primary key identifying the state collection
	 * @param strObjectName The label of the object wanted
	 * @return object attached to the tag provided
	 */
	synchronized public Object getObject( String strKey, String strObjectName )
	{
            State state;

		try
		{
                  // check params
                  if( strKey == null ) throw new Exception("Null parameter");

                  // get state object (or make it)
                  state = (State) mhashObjects.get( strKey );
                  if( state == null ) return null;

                  return state.getObject( strObjectName );
		}
		catch ( Exception ex )
		{
			mlogger.log( Level.SEVERE,"Exception during getObject",ex);
			return null;
		}
	}

      /**
       * delete a key and the state associated with it permanently
       * @param strKey primary key identifying the state collection
       */
      synchronized public void deleteKey( String strKey )
      {
            State state;

            try
            {
                  // check params
                  if( strKey == null ) throw new Exception("Null parameter");

                  // get state object (if its there)
                  state = (State) mhashObjects.get( strKey );
                  if( state == null ) return;

                  // delete key
                  mhashObjects.remove( strKey );
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.SEVERE,"Exception during deleteKey",ex);
                  return;
            }
      }

      /**
       * check a key exists
       * @param strKey primary key identifying the state collection
       * @return true if the key exists
       */
      synchronized public boolean checkKey( String strKey )
      {
            State state;

            try
            {
                  // check params
                  if( strKey == null ) throw new Exception("Null parameter");

                  // get state object (if its there)
                  state = (State) mhashObjects.get( strKey );
                  if( state == null ) return false;

                  // ok
                  return true;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.SEVERE,"Exception during deleteKey",ex);
                  return false;
            }
      }

      /**
       * make a new state object entry within the hash of state objects
       * @param strKey primary key identifying the state collection
       * @return new state object, or null on error
       */
      synchronized private State makeState( String strKey )
      {
            State state;
			
            try
            {
                  // check params
                  if( strKey == null ) throw new Exception("Null parameters");

                  // get state object (or make it)
                  state = new State();
                  mhashObjects.put( strKey, state );

                  // all done
                  return state;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.SEVERE,"Exception during makeState",ex);
                  return null;
            }
      }

} // end of StateRepository
