import com.sun.jini.lease.LeaseRenewalManager;
import com.sun.jini.lookup.JoinManager;
import com.sun.jini.lookup.ServiceIDListener;
import java.io.IOException;
import java.rmi.activation.*;
import java.rmi.Remote;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Arrays;
import java.util.Map;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Properties;
import net.jini.core.entry.Entry;
import net.jini.core.lease.LeaseDeniedException;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceRegistrar; 
import net.jini.core.lookup.ServiceTemplate; 
import net.jini.core.transaction.CannotJoinException;
import net.jini.core.transaction.server.CrashCountException;
import net.jini.core.transaction.server.TransactionConstants;
import net.jini.core.transaction.server.TransactionManager.Created;
import net.jini.core.transaction.server.TransactionManager;
import net.jini.core.transaction.server.TransactionParticipant;
import net.jini.core.transaction.UnknownTransactionException;
import net.jini.discovery.LookupDiscovery;
import net.jini.lookup.entry.Name;
import sdsu.util.ProgramProperties;
import sdsu.io.LocalRepository;
import sdsu.logging.*;
import net.jini.core.transaction.CannotCommitException;

public class Auctioneer extends UnicastRemoteObject 
	implements AuctionInterface, ServiceIDListener, TransactionParticipant
	{
	private ServiceID auctionServiceID;

	int earnings = 0;
	int itemsToSell = 100;
	
	TransactionManager clerk;
	AuctionClock timer;
	Map pendingBids = Collections.synchronizedMap( new HashMap());

	LocalRepository dataStore;
	
	int winningBid = NOT_SET;
	
	static final int NOT_SET = -1;
	static final String PENDING_BIDS = "pendingBids";
	static final String SERVICE_ID = "serviceID";
	static final String EARNINGS = "earnings";
	static final String ITEMS_TO_SELL = "itemsToSell";
	
	public Auctioneer( String[] groups, String database) throws Exception
		{
		try
			{
			System.out.println( "Start Auctioneer" );

			if (database == null )
				System.out.println( "Null database" );
			System.out.println( "Start repository" );
			dataStore = new LocalRepository( database );
			System.out.println( "B repository" );
			if ( dataStore.exists() )
				restoreState();
			else
				dataStore.create();
				
			System.out.println( "Look for reggie" );
			ServiceRegistrar lookupSevice = joinReggie( groups );
			setClerk( lookupSevice );
			
			if ( pendingBids.size() > 0 )
				validateBids();
				
			timer = new AuctionClock( 1000 * 30 );
			timer.setDaemon( false);
			timer.start();
			}
		catch (Exception error)
			{
			System.out.println( "Startup error" );
			Logger.error( error );
			throw error;
			}
		}

	private void validateBids()
		{
		synchronized (pendingBids)
			{
			Iterator bids = pendingBids.keySet().iterator();
			
			while (bids.hasNext() )
				{
				Long wrapped =(Long) bids.next();
				int status = getStatus( wrapped.longValue() );
				switch (status)
					{
					case VOTING:
					case ABORTED:
						bids.remove();
						break;
					case COMMITTED:
						Integer bidAmount = (Integer) pendingBids.get( wrapped );
						commitBid( bidAmount.intValue() );
						bids.remove();
						break;
					case ACTIVE:
						boolean rejoined = rejoinTransaction( wrapped.longValue() );
						if (!rejoined)
							bids.remove();
					}
				}
			}
		}
	
	private boolean rejoinTransaction( long transactionID )
		{
		try
			{
			clerk.join( transactionID, this, 0  );
			return true;
			}
		catch (RemoteException connectionProblem )
			{// should retry here
			return false;
			}
		catch (Exception cantJoin)
			{
			return false;
			}
		}
	private int getStatus( long bid )
		{
		try
			{
			return clerk.getState( bid );
			}
		catch (UnknownTransactionException removeIt)
			{
			return ABORTED;
			}
		catch (RemoteException whatToDoHere)
			{
			Logger.error( "Remote exception on validating a bid " + whatToDoHere );
			return 0;
			}
		}
		
	private void restoreState()
		{
		System.out.println( "RestoreState" );
		try
			{
			if (dataStore.containsKey( PENDING_BIDS ) )
				pendingBids = (Map) dataStore.get( PENDING_BIDS );
				
			if (dataStore.containsKey( SERVICE_ID ) )
				auctionServiceID = (ServiceID) dataStore.get( SERVICE_ID );

			if (dataStore.containsKey( EARNINGS ) )
				{
				Integer wrapped = (Integer) dataStore.get( EARNINGS );
				earnings = wrapped.intValue();
				}

			if (dataStore.containsKey( ITEMS_TO_SELL ) )
				{
				Integer wrapped = (Integer) dataStore.get( EARNINGS );
				itemsToSell = wrapped.intValue();
				}
			}
		catch (IOException saveError )
			{
			Logger.error( "IOException on restoring state " + saveError );
			}
		}

	private void saveState()
		{
		System.out.println( "Save State" );
		try
			{
			dataStore.put( PENDING_BIDS, pendingBids );
			dataStore.put( SERVICE_ID, auctionServiceID );
			dataStore.put( EARNINGS, new Integer( earnings) );
			dataStore.put( ITEMS_TO_SELL, new Integer( itemsToSell) );
			}
		catch (IOException saveError )
			{
			Logger.error( "IOException on saving state " + saveError );
			}
		}
	
	private ServiceRegistrar joinReggie( String[] groups )
		{
		JoinManager myManager = null;
		ServiceRegistrar[] joinSet = null;;
		try
			{
			Entry[] labels = new Entry[1];
			labels[0] = new Name("Auction");
			myManager = new JoinManager (this, labels,groups,null, this, 
					new LeaseRenewalManager () );
			do
				{
				System.out.println( "Looking for reggie" );
				Thread.sleep( 1000 * 10 );
				joinSet =  myManager.getJoinSet();
				}
			while ( joinSet.length == 0 );
			}
		catch (Exception startupProblem)
			{
			System.err.println( "Could not start Auction server: " + startupProblem );
			Logger.error( "Could not start Auction server: " + startupProblem);
			System.exit( 0 );
			}
		 return joinSet[0];
		}
		
	public void serviceIDNotify (ServiceID uniqueID)
		{
		auctionServiceID = uniqueID;
		System.out.println("server: ID set: " + auctionServiceID );
		try
			{
			dataStore.put( PENDING_BIDS, uniqueID );
			}
		catch (IOException writeProblem)
			{
			Logger.error( "Could not store serviceID" );
			}
		}

	private void setClerk( ServiceRegistrar registrar  ) throws RemoteException
		{
		Entry[] serverAttributes = new Entry[1];
		serverAttributes[0] = new Name ("TransactionManager");

		ServiceTemplate template = new ServiceTemplate (null, null, serverAttributes);
		System.out.println( "Get Transaction M" );
		clerk = (TransactionManager) registrar.lookup (template);
		}
		
	public int earnings()
		{
		return earnings;
		}

	public synchronized Bid bid( int amount, TransactionParticipant bidder)
		throws RemoteException, LeaseDeniedException
		{
		System.out.println( "bid: " + amount );
		try
			{
			Created leasedTransaction = clerk.create( 1000 * 60 * 2 );
			long bidID = leasedTransaction.id;
			clerk.join( bidID, this, 0 );
			clerk.join(bidID, bidder, 0 );

			System.out.println( "Bid joined" );
			putBid( bidID , amount );
			saveState();
			return new Bid( clerk, bidID, 1);
			}
		catch ( Exception bidProblem )
			{
			bidProblem.printStackTrace();
			throw new LeaseDeniedException(" Can not accept bid " );
			}
		}
	
	void processAuction()
		{
		System.out.println( "Process auction Bids" );
		Object bids[];
		
		synchronized (pendingBids)
			{
			bids = pendingBids.keySet().toArray();
			}
		
		// Process bids from largest to smallest in case
		// largest bid aborts.	
		Arrays.sort( bids );
		for ( int k =  bids.length-1; k >= 0; k-- )
			{
			long bidID = ((Long) bids[k]).longValue();
			try
				{
				clerk.commit( bidID );
				}
			catch (UnknownTransactionException badTransaction )
				{
				removeBid( bidID );
				}
			catch (CannotCommitException  killBid )
				{
				removeBid( bidID );
				}
			catch ( RemoteException retryLater )
				{
				Logger.error("RemoteException on commit: " + retryLater );
				}			
			}
		//prepare for next auction
		winningBid = NOT_SET;
		saveState();
		}
		
	public void abort(TransactionManager manager, long transactionID) 
		throws UnknownTransactionException, RemoteException
		{
		System.out.println( "Abort" );
		identifyTransaction( manager, transactionID );
		int invalidBid = getBid( transactionID );
		removeBid(  transactionID );
		if (winningBid == invalidBid )
			winningBid = getMaxBid();
		}
			
	public void commit(TransactionManager manager, long transactionID) 
		throws UnknownTransactionException, RemoteException
		{
		System.out.println( "commit" );
		identifyTransaction( manager, transactionID );
		commitBid( getBid( transactionID ));
		removeBid(  transactionID  );
		}
	
	private void commitBid( int bidAmount )
		{
		earnings = earnings +  bidAmount;
		System.out.println( "commit: bid " + bidAmount + " earnings " + earnings );
		itemsToSell--;
		try
			{
			dataStore.put( EARNINGS, new Integer( earnings) );
			dataStore.put( ITEMS_TO_SELL, new Integer( itemsToSell) );
			}
		catch (IOException writeError )
			{
			Logger.error( "IO exception on committing bid "  );
			Logger.error( writeError  );
			}
		}
		
	public int prepare(TransactionManager manager, long transactionID) 
		throws UnknownTransactionException, RemoteException
		{
		System.out.println( "prepare" );
		identifyTransaction( manager, transactionID );
			
		int bidValue = getBid( transactionID );
		System.out.println( "prepare bidValue: " + bidValue );
		
		if ( bidValue == 0 )
			return NOTCHANGED;
		System.out.println( "prepare maxBid: " + getMaxBid()  );
		
		if (winningBid == NOT_SET)
			winningBid = getMaxBid();
			
		if ( bidValue < winningBid )
			{
			abort( manager, transactionID );
			return ABORTED;
			}		
		return PREPARED;
		}
		
	public int prepareAndCommit(TransactionManager mgr, long transactionID) 
		throws UnknownTransactionException, RemoteException
		{
		System.out.println( "prepare & commit" );
		int result = prepare( mgr, transactionID );
		if ( result == PREPARED )
			{
			commit(  mgr, transactionID );
			result = COMMITTED;
			}
		return result;
		}

	private void putBid( long bidID, int bidAmount ) throws IOException
		{
		try
			{
			pendingBids.put( new Long( bidID ), new Integer( bidAmount ) );
			dataStore.put( PENDING_BIDS, pendingBids );
			}
		catch (IOException writeProblem)
			{
			Logger.error( "IOException in writing pendingBids to dataStore on put" + writeProblem );
			pendingBids.remove(  new Long( bidID ));
			throw writeProblem;
			}
		}
		
	private void removeBid(long bidID)
		{
		try
			{
			pendingBids.remove( new Long( bidID ) );
			dataStore.put( PENDING_BIDS, pendingBids );
			}
		catch (IOException writeProblem)
			{
			Logger.error( "IOException in writing pendingBids to dataStore on remove" + writeProblem );
			}
		}

	private int getBid(long bidID)
		{
		Integer bid =(Integer) pendingBids.get( new Long( bidID ) );
		return bid.intValue();
		}
		
	private int getMaxBid()
		{
		Integer maxBid;
		synchronized (pendingBids)
			{
			Collection values = pendingBids.values();
			maxBid = (Integer) Collections.max( values );
			}
		return maxBid.intValue();
		}

	private void identifyTransaction(TransactionManager manager, long transactionID)
		throws UnknownTransactionException
		{
		System.out.println( "Start identify");
		if ( !manager.equals(clerk) )
			throw  new UnknownTransactionException( "Unknown Transaction Manager");
		
		Long id = new Long( transactionID );
		if ( !pendingBids.containsKey( id ) )
			throw  new UnknownTransactionException( "Unknown bid id " + id);
		}

	public static void main( String[] args ) throws Exception
		{
		System.setSecurityManager(new RMISecurityManager());
		System.out.println("Start main");

		
		String database = "/export/home/whitney/java/whitney/jini/examples/transaction/AuctionData";

		System.out.println("Middle");

		ProgramProperties flags = new ProgramProperties( args);
		String groupsString = flags.getString( "groups", "ALL" );
		String[] groups = formateGroupList( groupsString );

		Auctioneer anAuctioneer = new Auctioneer( groups, database );
		
		System.out.println("Class activated" + anAuctioneer.earnings() );
		}		
	
	private static String[] formateGroupList( String groupList )
		{
		if (groupList.equals( "NONE") )
			{
			System.out.println( "Usage: java HelloServer -groups=group1,group2 " );
			System.exit( 0 );
			}
		if (groupList.equals( "ALL") )
			return LookupDiscovery.ALL_GROUPS;
		
		if ( groupList.indexOf( ',' ) < 0 )
			return new String[] { groupList.trim() };
		
		StringTokenizer groups = new StringTokenizer( groupList, ",'");
		
		String[] formatedGroups = new String[ groups.countTokens() ];
		
		for ( int k = 0; k < formatedGroups.length; k++ )
			{
			formatedGroups[k] = groups.nextToken().trim();
			}
		return formatedGroups;
		}		

	private class AuctionClock extends Thread
		{
		long auctionDuration;
		public AuctionClock( long duration )
			{
			auctionDuration = duration;
			}
		
		public void run()
			{
			System.out.println( "Start clock" );
			try
				{
				while( true )
					{
					System.out.println( "Sleep" );
					sleep( auctionDuration );
					processAuction();
					}
				}
			catch (InterruptedException  exitTime )
				{
				Logger.log( "AuctionClock stoped" );
				}
			}
		}
	}
