// **********************************************************************
//
// Copyright (c) 2003-2006 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************

public class Client
{
    private static void
    test(boolean b)
    {
        if(!b)
        {
            throw new RuntimeException();
        }
    }

    private static class AMI_Servant_setValueAsyncI extends Test.AMI_Servant_setValueAsync
    {
        public void
        ice_response()
        {
        }

        public void
        ice_exception(Ice.LocalException ex)
        {
        }
    }

    static class ReadThread extends  Thread
    {
	ReadThread(Test.ServantPrx[] servants)
	{
	    _servants = servants;    
	} 

	public void run()
	{
	    int loops = 10;
	    while(loops-- > 0)
	    {
		try
		{
		    _servants[0].getValue();
		    test(false);
		}
		catch(Ice.ObjectNotExistException e)
		{
		    // Expected
		}
		catch(Exception e)
		{
		    test(false);
		}
		
		for(int i = 1; i < _servants.length; ++i)
		{
		    test(_servants[i].getValue() == i);
		}
	    }
	}
	
	private Test.ServantPrx[] _servants;
    }

    public static final int StateRunning = 0;
    public static final int StateDeactivating = 1;
    public static final int StateDeactivated = 2;

    static class ReadForeverThread extends Thread
    {
	ReadForeverThread(Test.ServantPrx[] servants)
	{
	    _servants = servants;
	}
	
	public void
	run()
	{
	    for(;;)
	    {
		try
		{
		    for(int i = 0; i < _servants.length; ++i)
		    {
			if(getEvictorState() == StateDeactivated)
			{
			    _servants[i].slowGetValue();
			    test(false);
			}
			else
			{
			    test(_servants[i].slowGetValue() == i);
			}
		    }
		}
		catch(Ice.SocketException e)
		{
		    //
		    // Expected
		    //
		    test(validEx());
		    return;
		}
		catch(Ice.LocalException e)
		{
		    System.err.println("Caught unexpected : " + e.toString());
		    test(false);
		    return;
		}
		catch(Exception e)
		{
		    test(false);
		    return;
		}
	    }
	}

	synchronized int
	getEvictorState()
	{
	    return _state;
	}
	
	synchronized boolean
	validEx()
	{
	    return _state == StateDeactivating || _state == StateDeactivated;
	}
	
	synchronized void
	setEvictorState(int s)
	{
	    _state = s;
	}

	private Test.ServantPrx[] _servants;
	private int _state = StateRunning;
    }


    static class AddForeverThread extends Thread
    {	
	AddForeverThread(Test.RemoteEvictorPrx evictor, int prefix)
	{
	    _evictor = evictor;
	    _prefix = "" + prefix;
	}

	public void
	run()
	{
	    int index = 0;
	    
	    for(;;)
	    {
		try
		{
		    String id = _prefix + "-" + index++;
		    if(getEvictorState() == StateDeactivated)
		    {
			_evictor.createServant(id, 0);
			test(false);
		    }
		    else
		    {
			_evictor.createServant(id, 0);
		    }
		}
		catch(Test.EvictorDeactivatedException e)
		{
		    //
		    // Expected
		    //
		    test(validEx());
		    return;
		}
		catch(Ice.ObjectNotExistException e)
		{
		    //
		    // Expected
		    //
		    test(validEx());
		    return;
		}
		catch(Test.AlreadyRegisteredException e)
		{
		    System.err.println("Caught unexpected AlreadyRegistedException:" + e.toString());
		    System.err.println("index is " + index);
		    test(false);
		    return;
		}
		catch(Ice.LocalException e)
		{
		    System.err.println("Caught unexpected : " + e.toString());
		    e.printStackTrace();
		    test(false);
		    return;
		}
		catch(Exception e)
		{
		    e.printStackTrace();
		    test(false);
		    return;
		}
	    }
	}

	synchronized int
	getEvictorState()
	{
	    return _state;
	}
	
	synchronized boolean
	validEx()
	{
	    return _state == StateDeactivating || _state == StateDeactivated;
	}
	
	synchronized void
	setEvictorState(int s)
	{
	    _state = s;
	}

	private Test.RemoteEvictorPrx _evictor;
	private String _prefix;
	private int _state = StateRunning;
    }


    static class CreateDestroyThread extends Thread
    {
	CreateDestroyThread(Test.RemoteEvictorPrx evictor, int id, int size) 
	{
	    _evictor = evictor;
	    _id = "" + id;
	    _size = size;
	}
		   
	public void
	run()
	{
	    try
	    {
		int loops = 50;
		while(loops-- > 0)
		{
		    for(int i = 0; i < _size; i++)
		    {
			String id = "" + i;
			if(id.equals(_id))
			{
			    //
			    // Create when odd, destroy when even.
			    //
			    
			    if(loops % 2 == 0)
			    {
				Test.ServantPrx servant = _evictor.getServant(id);
				servant.destroy();
				
				//
				// Twice
				//
				try
				{
				    servant.destroy();
				    test(false);
				}
				catch(Ice.ObjectNotExistException e)
				{
				    // Expected
				}
			    }
			    else
			    {
				Test.ServantPrx servant = _evictor.createServant(id, i);
				
				//
				// Twice
				//
				try
				{
				    servant = _evictor.createServant(id, 0);
				    test(false);
				}
				catch(Test.AlreadyRegisteredException e)
				{
				    // Expected
				}
			    }
			}
			else
			{
			    //
			    // Just read/write the value
			    //
			    Test.ServantPrx servant = _evictor.getServant(id);
			    try
			    {
				int val = servant.getValue();
				test(val == i || val == -i);
				servant.setValue(-val);
			    }
			    catch(Ice.ObjectNotExistException e)
			    {
				// Expected from time to time
			    }
			}
		    }
		}
	    }
	    catch(Exception e)
	    {
		//
		// Unexpected!
		//
		test(false);
	    }
	}

	private Test.RemoteEvictorPrx _evictor;
	private String _id;
	private int _size;
    }



    private static int
    run(String[] args, Ice.Communicator communicator)
	throws Test.AlreadyRegisteredException, Test.NotRegisteredException, Test.EvictorDeactivatedException
    {
        String ref = "factory:default -p 12010 -t 30000";
        Ice.ObjectPrx base = communicator.stringToProxy(ref);
        test(base != null);
        Test.RemoteEvictorFactoryPrx factory = Test.RemoteEvictorFactoryPrxHelper.checkedCast(base);

     
	System.out.print("testing Freeze Evictor... ");
	System.out.flush();

	final int size = 5;
	
	Test.RemoteEvictorPrx evictor = factory.createEvictor("Test");
	evictor.setSize(size);

	//
	// Create some servants 
	//
	Test.ServantPrx[] servants = new Test.ServantPrx[size];
	for(int i = 0; i < size; i++)
	{
	    String id = "" + i;
	    servants[i] = evictor.createServant(id, i);
	    servants[i].ice_ping();
	    
	    Test.FacetPrx facet1 = Test.FacetPrxHelper.uncheckedCast(servants[i], "facet1");
	    try
	    {
		facet1.ice_ping();
		test(false);
	    }
	    catch(Ice.FacetNotExistException e)
	    {
		//
		// Expected
		//
	    }
	    servants[i].addFacet("facet1", "data");
	    facet1.ice_ping();
	    facet1 = Test.FacetPrxHelper.checkedCast(servants[i], "facet1");
	    test(facet1 != null);
	    facet1.setValue(10 * i);
	    facet1.addFacet("facet2", "moreData");
	    Test.FacetPrx facet2 = Test.FacetPrxHelper.checkedCast(facet1, "facet2");
	    test(facet2 != null);
	    facet2.setValue(100 * i);
	}

	//
	// Evict and verify values.
	//
	evictor.setSize(0);
	evictor.setSize(size);
	for(int i = 0; i < size; i++)
	{
	    servants[i].ice_ping();
	    test(servants[i].getValue() == i);
	    Test.FacetPrx facet1 = Test.FacetPrxHelper.checkedCast(servants[i], "facet1");
	    test(facet1 != null);
	    test(facet1.getValue() == 10 * i);
	    test(facet1.getData().equals("data"));
	    Test.FacetPrx facet2 = Test.FacetPrxHelper.checkedCast(facet1, "facet2");
	    test(facet2 != null);
	    test(facet2.getData().equals("moreData"));
	}
	
	//
	// Mutate servants.
	//
	for(int i = 0; i < size; i++)
	{
	    servants[i].setValue(i + 100);
	    Test.FacetPrx facet1 = Test.FacetPrxHelper.checkedCast(servants[i], "facet1");
	    test(facet1 != null);
	    facet1.setValue(10 * i + 100);
	    Test.FacetPrx facet2 = Test.FacetPrxHelper.checkedCast(facet1, "facet2");
	    test(facet2 != null);
	    facet2.setValue(100 * i + 100);
	}
	
	for(int i = 0; i < size; i++)
	{
	    test(servants[i].getValue() == i + 100);
	    Test.FacetPrx facet1 = Test.FacetPrxHelper.checkedCast(servants[i], "facet1");
	    test(facet1 != null);
	    test(facet1.getValue() == 10 * i + 100);
	    Test.FacetPrx facet2 = Test.FacetPrxHelper.checkedCast(facet1, "facet2");
	    test(facet2 != null);
	    test(facet2.getValue() == 100 * i + 100);
	}
	
	//
	// Evict and verify values.
	//
	evictor.setSize(0);
	evictor.setSize(size);
	for(int i = 0; i < size; i++)
	{
	    test(servants[i].getValue() == i + 100);
	    Test.FacetPrx facet1 = Test.FacetPrxHelper.checkedCast(servants[i], "facet1");
	    test(facet1 != null);
	    test(facet1.getValue() == 10 * i + 100);
	    Test.FacetPrx facet2 = Test.FacetPrxHelper.checkedCast(facet1, "facet2");
	    test(facet2 != null);
	    test(facet2.getValue() == 100 * i + 100);
	}

	// 
	// Test saving while busy
	//

	AMI_Servant_setValueAsyncI setCB = new AMI_Servant_setValueAsyncI();
	for(int i = 0; i < size; i++)
	{
	    //
	    // Start a mutating operation so that the object is not idle.
	    //
	    servants[i].setValueAsync_async(setCB, i + 300);
	    
	    //
	    // Wait for setValueAsync to be dispatched.
	    //
	    try
	    {
		Thread.sleep(100);
	    }
	    catch(InterruptedException ex)
	    {
	    }
	    
	    test(servants[i].getValue() == i + 100);
	    //
	    // This operation modifies the object state but is not saved
	    // because the setValueAsync operation is still pending.
	    //
	    servants[i].setValue(i + 200);
	    test(servants[i].getValue() == i + 200);
	    
	    //
	    // Force the response to setValueAsync
	    //
	    servants[i].releaseAsync();
	    test(servants[i].getValue() == i + 300);
	}


	//
	// Add duplicate facet and catch corresponding exception
	// 
	for(int i = 0; i < size; i++)
	{
	    try
	    {
		servants[i].addFacet("facet1", "foobar");
		test(false);
	    }
	    catch(Test.AlreadyRegisteredException ex)
	    {
	    }
	}
	
	//
	// Remove a facet that does not exist
	// 
	try
	{
	    servants[0].removeFacet("facet3");
	    test(false);
	}
	catch(Test.NotRegisteredException ex)
	{
	}

	//
	// Remove all facets
	//
	for(int i = 0; i < size; i++)
	{
	    servants[i].removeFacet("facet1");
	    servants[i].removeFacet("facet2");
	}

	evictor.setSize(0);
	evictor.setSize(size);
	
	//
	// Destroy servants and verify ObjectNotExistException.
	//
	for(int i = 0; i < size; i++)
	{
	    servants[i].destroy();
	    try
	    {
		servants[i].getValue();
		test(false);
	    }
	    catch(Ice.ObjectNotExistException ex)
	    {
		// Expected
	    }

	    try
	    {
		servants[i].ice_ping();
		test(false);
	    }
	    catch(Ice.ObjectNotExistException ex)
	    {
		// Expected
	    }

	}

	//
	// Allocate space for size servants.
	//
	servants = new Test.ServantPrx[size];

	//
	// Recreate servants, set transient value
	//  
	for(int i = 0; i < size; i++)
	{
	    String id = "" + i;
	    servants[i] = evictor.createServant(id, i);
	    servants[i].setTransientValue(i);
	}

	//
	// Evict
	//
	evictor.saveNow();
	evictor.setSize(0);
	evictor.setSize(size);

	//
	// Check the transient value
	//
	for(int i = 0; i < size; i++)
	{
	    test(servants[i].getTransientValue() == -1);
	}
    
	//
	// Now with keep
	//
	for(int i = 0; i < size; i++)
	{
	    servants[i].keepInCache();
	    servants[i].keepInCache();
	    servants[i].setTransientValue(i);
	}
	evictor.saveNow();
	evictor.setSize(0);
	evictor.setSize(size);
	
	//
	// Check the transient value
	//
	for(int i = 0; i < size; i++)
	{
	    test(servants[i].getTransientValue() == i);
	}
	
	//
	// Again, after one release
	//
	for(int i = 0; i < size; i++)
	{
	    servants[i].release();
	}
	evictor.saveNow();
	evictor.setSize(0);
	evictor.setSize(size);
	for(int i = 0; i < size; i++)
	{
	    test(servants[i].getTransientValue() == i);
	}

	//
	// Again, after a second release
	//
	for(int i = 0; i < size; i++)
	{
	    servants[i].release();
	}
	evictor.saveNow();
	evictor.setSize(0);
	evictor.setSize(size);
	
	for(int i = 0; i < size; i++)
	{
	    test(servants[i].getTransientValue() == -1);
	}
		
	//
	// Release one more time
	//
	for(int i = 0; i < size; i++)
	{
	    try
	    {
		servants[i].release();
		test(false);
	    }
	    catch(Test.NotRegisteredException e)
	    {
		// Expected
	    }
	}
	
	//
	// Deactivate and recreate evictor, to ensure that servants
	// are restored properly after database close and reopen.
	//
	evictor.deactivate();
	evictor = factory.createEvictor("Test");
	evictor.setSize(size);
	for(int i = 0; i < size; i++)
	{
	    String id = "" + i;
	    servants[i] = evictor.getServant(id);
	    test(servants[i].getValue() == i);
	}
	

	//
	// Test concurrent lookups with a smaller evictor
	// size and one missing servant
	//
	evictor.setSize(size / 2);
	servants[0].destroy();
	
	{
	    int threadCount = size * 2;
	    
	    Thread[] threads = new Thread[threadCount];
	    for(int i = 0; i < threadCount; i++)
	    {
		threads[i] = new ReadThread(servants);
		threads[i].start();
	    }
	    
	    for(int i = 0; i < threadCount; i++)
	    {
		for(;;)
		{
		    try
		    {
			threads[i].join(0);
			break;
		    }
		    catch(InterruptedException e)
		    {
		    }
		}
	    }
	}
	
	//
	// Clean up.
	//
	evictor.destroyAllServants("");
	evictor.destroyAllServants("facet1");
	evictor.destroyAllServants("facet2");
	
	//
	// CreateDestroy threads
	//
	{
	    int threadCount = size;;
	    
	    Thread[] threads = new Thread[threadCount];
	    for(int i = 0; i < threadCount; i++)
	    {
		threads[i] = new CreateDestroyThread(evictor, i, size);
		threads[i].start();
	    }
	    
	    for(int i = 0; i < threadCount; i++)
	    {
		for(;;)
		{
		    try
		    {
			threads[i].join(0);
			break;
		    }
		    catch(InterruptedException e)
		    {
		    }
		}
	    }
	    
	    //
	    // Verify all destroyed
	    // 
	    for(int i = 0; i < size; i++)   
	    {
		try
		{
		    servants[i].getValue();
		    test(false);
		}
		catch(Ice.ObjectNotExistException e)
		{
		    // Expected
		}
	    }
	}
	
	//
	// Recreate servants.
	//  
	servants = new Test.ServantPrx[size];
	for(int i = 0; i < size; i++)
	{
	    String id = "" + i;
	    servants[i] = evictor.createServant(id, i);
	}
	
	//
	// Deactivate in the middle of remote AMD operations
	// (really testing Ice here)
	//
	{
	    int threadCount = size;
	    
	    Thread[] threads = new Thread[threadCount];
	    for(int i = 0; i < threadCount; i++)
	    {
		threads[i] = new ReadForeverThread(servants);
		threads[i].start();
	    }
	    
	    try
	    {
		Thread.currentThread().sleep(500);
	    }
	    catch(InterruptedException e)
	    {
	    }

	    for(int i = 0; i < threadCount; i++)
	    {
		ReadForeverThread t = (ReadForeverThread)threads[i];
		t.setEvictorState(StateDeactivating);
	    }
	    evictor.deactivate();
	    for(int i = 0; i < threadCount; i++)
	    {
		ReadForeverThread t = (ReadForeverThread)threads[i];
		t.setEvictorState(StateDeactivated);
	    }
	    
	    for(int i = 0; i < threadCount; i++)
	    {
		for(;;)
		{
		    try
		    {
			threads[i].join(0);
			break;
		    }
		    catch(InterruptedException e)
		    {
		    }
		}
	    }
	}
	
	//
	// Resurrect
	//
	evictor = factory.createEvictor("Test");
	evictor.destroyAllServants("");
	
	//
	// Deactivate in the middle of adds
	//
	{
	    int threadCount = size;
	    
	    Thread[] threads = new Thread[threadCount];
	    for(int i = 0; i < threadCount; i++)
	    {
		threads[i] = new AddForeverThread(evictor, i);
		threads[i].start();
	    }
	    
	    try
	    {
		Thread.currentThread().sleep(500);
	    }
	    catch(InterruptedException e)
	    {
	    }

	    for(int i = 0; i < threadCount; i++)
	    {
		AddForeverThread t = (AddForeverThread)threads[i];
		t.setEvictorState(StateDeactivating);
	    }
	    evictor.deactivate();
	    for(int i = 0; i < threadCount; i++)
	    {
		AddForeverThread t = (AddForeverThread)threads[i];
		t.setEvictorState(StateDeactivated);
	    }

	    for(int i = 0; i < threadCount; i++)
	    {
		for(;;)
		{
		    try
		    {
			threads[i].join(0);
			break;
		    }
		    catch(InterruptedException e)
		    {
		    }
		}
	    }
	}
	
	
	//
	// Clean up.
	//
	evictor = factory.createEvictor("Test");
	evictor.destroyAllServants("");
	evictor.deactivate();


	System.out.println("ok");
        
        factory.shutdown();

        return 0;
    }

    public static void
    main(String[] args)
    {
        int status = 0;
        Ice.Communicator communicator = null;

        try
        {
            communicator = Ice.Util.initialize(args);
            status = run(args, communicator);
        }
        catch(Ice.LocalException ex)
        {
            ex.printStackTrace();
            status = 1;
        }
	catch(Test.AlreadyRegisteredException ex)
	{
	    ex.printStackTrace();
            status = 1;
	}
	catch(Test.NotRegisteredException ex)
	{
	    ex.printStackTrace();
            status = 1;
	}
	catch(Test.EvictorDeactivatedException ex)
	{
	    ex.printStackTrace();
            status = 1;
	}

        if(communicator != null)
        {
            try
            {
                communicator.destroy();
            }
            catch(Ice.LocalException ex)
            {
                ex.printStackTrace();
                status = 1;
            }
        }

	System.gc();
        System.exit(status);
    }
}
