

package gov.va.med.cds.vdm.vhim400.template;


import static org.junit.Assert.assertEquals;
import gov.va.med.cds.junit.runners.BeforeTestsOnce;
import gov.va.med.cds.junit.runners.SuiteAwareSpringRunner;
import gov.va.med.cds.junit.runners.Suite;
import gov.va.med.cds.template.AbstractTemplateManagerTest;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;


@RunWith( SuiteAwareSpringRunner.class )
public abstract class AbstractTemplateConcurrentTest
    extends
        AbstractTemplateManagerTest

{
    private Log logger = LogFactory.getLog( AbstractTemplateConcurrentTest.class );
    protected static final int RUN_TIME_IN_MILLI_SECONDS = 5000; //Sync with MAX_MILLIS_PER_THREAD
    protected static final int FIXED_THREAD_POOL_SIZE = 50; //50;
    protected static final int CALLABLE_COUNT = FIXED_THREAD_POOL_SIZE;
    protected static final int MAX_MILLIS_PER_THREAD = 10000; //10000;
    protected static final int MAX_MILLIS_PER_FULL_POOL_EXECUTION = MAX_MILLIS_PER_THREAD * CALLABLE_COUNT;
    protected static List<Callable<Boolean>> callables = new LinkedList<Callable<Boolean>>();
    private static final String APPLICATION_NAME = "CDS TEMPLATE TEST";
    protected static final long MAX_MEMORY_USED_IN_RUN = 50000000;

    @BeforeTestsOnce
    @Suite( groups = { "checkintest" } )
    public void beforeAbstractTemplateConcurrentTestsSetUp( )
        throws Exception
    {
        //workaround @BeforeTestOnce not working correctly
    	if(callables.size() != CALLABLE_COUNT){
    		for ( int i = 0; i < CALLABLE_COUNT; i++ )
    		{
    			callables.add( new TestRunner() );
    		}
    	}
    }


    /**
     * Tests concurrent clinical operations and invokes a set of <callable> objects concurrently and then loops through
     * the set of <future> objects returned to ensure that all threads have completed - any exceptions would then be
     * captured and thrown at that point.
     * 
     * @throws Exception
     */
    @Test
    @Suite( groups = { "checkintest" } )
    public void testConcurrentOperation( )
        throws Exception
    {
    	//TODO temp workaround
    	beforeAbstractTemplateConcurrentTestsSetUp( );
    	
    	ThreadPoolExecutor executor = createExecutor();

        if ( logger.isDebugEnabled() )
        {
            logger.debug( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, null, APPLICATION_NAME, "invoking all runners." ) );
        }

        long startMillis = System.currentTimeMillis();

        long startMemory = Runtime.getRuntime().freeMemory();

        List<Future<Boolean>> futures = executor.invokeAll( callables, MAX_MILLIS_PER_FULL_POOL_EXECUTION, TimeUnit.MILLISECONDS );

        long endMemory = Runtime.getRuntime().freeMemory();
        long usedMemory = startMemory - endMemory;
        long memoryUsedInRun = ( usedMemory * 100 ) / startMemory;

        //assertTrue( usedMemory < MAX_MEMORY_USED_IN_RUN );
        //System.out.println("memory used: " + usedMemory);
        //System.out.println("% memory used: " + memoryUsedInRun);

        long elapsedMillis = System.currentTimeMillis() - startMillis;

        if ( logger.isDebugEnabled() )
        {
            logger.debug( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, null, APPLICATION_NAME, "finished invoking runners in "
                            + elapsedMillis + " millis." ) );
            logger.debug( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, null, APPLICATION_NAME, "% memory used in run " + memoryUsedInRun
                            + "%." ) );
            logger.debug( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, null, APPLICATION_NAME, "Total memory used in run " + usedMemory
                            + "." ) );
        }

        int failedRunCount = 0;
        Exception lastException = null;

        // Collect results. This will capture any exceptions thrown and ensure that all tasks complete.
        for ( Future<Boolean> future : futures )
        {
            try
            {
                future.get();
            }
            catch ( Exception e )
            {
                failedRunCount++ ;
                lastException = e;
            }
        }

        if ( lastException != null )
        {
            throw new Exception( failedRunCount + " of " + CALLABLE_COUNT + " runs failed.", lastException );
        }

        // Make sure all the threads succeeded.
        assertEquals( "Expected " + CALLABLE_COUNT + " completed invocation, but only finished " + executedCount + " in " + elapsedMillis
                        + " millis.", executedCount, CALLABLE_COUNT );
    }


    protected ThreadPoolExecutor createExecutor( )
    {
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>( CALLABLE_COUNT );

        ThreadPoolExecutor executor = new ThreadPoolExecutor( FIXED_THREAD_POOL_SIZE, FIXED_THREAD_POOL_SIZE, MAX_MILLIS_PER_FULL_POOL_EXECUTION,
                        TimeUnit.MILLISECONDS, workQueue );

        if ( logger.isDebugEnabled() )
        {
            logger.debug( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, null, APPLICATION_NAME, "starting up all threads." ) );
        }
        executor.prestartAllCoreThreads();
        return executor;
    }


    protected abstract void performTemplateManagerOperation( )
        throws Exception;

    public static int invocationCount = 0;
    public static int executedCount = 0;

    class TestRunner
        implements
            Callable<Boolean>
    {
        private int invocationId;


        public TestRunner( )
        {
            synchronized ( AbstractTemplateConcurrentTest.this )
            {
                invocationId = ++invocationCount;
                if ( logger.isDebugEnabled() )
                {
                    logger.debug( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, null, APPLICATION_NAME, "Constructing TestRunner ID="
                                    + invocationId ) );
                }
            }
        }


        public int getInvocationId( )
        {
            return invocationId;
        }

        private boolean alreadyCalled = false;


        public Boolean call( )
            throws Exception
        {
            if ( alreadyCalled )
            {
                throw new Exception( "Already Called" );
            }
            alreadyCalled = true;
            if ( logger.isDebugEnabled() )
            {
                logger.debug( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, null, APPLICATION_NAME, "Executing - ID = " + invocationId ) );
            }

            performTemplateManagerOperation();

            if ( logger.isDebugEnabled() )
            {
                logger.debug( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, null, APPLICATION_NAME, "ID=" + invocationId + ". The time is "
                                + System.currentTimeMillis() ) );
            }
            synchronized ( AbstractTemplateConcurrentTest.this )
            {
                ++executedCount;
            }

            if ( logger.isDebugEnabled() )
            {
                logger
                                .debug( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, null, APPLICATION_NAME, "Finished Read - ID = "
                                                + invocationId ) );
            }
            return new Boolean( true );
        }

    }

}
