package org.apache.sandesha2.workers;

import java.io.File;

import org.apache.axiom.soap.SOAP11Constants;
import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.sandesha2.SandeshaException;
import org.apache.sandesha2.SandeshaTestCase;
import org.apache.sandesha2.client.SandeshaClient;
import org.apache.sandesha2.client.SandeshaClientConstants;
import org.apache.sandesha2.client.SequenceReport;
import org.apache.sandesha2.storage.StorageManager;
import org.apache.sandesha2.storage.Transaction;
import org.apache.sandesha2.storage.beans.RMDBean;
import org.apache.sandesha2.storage.beans.RMSBean;
import org.apache.sandesha2.util.RangeString;
import org.apache.sandesha2.util.SandeshaUtil;

public class ForceInboundDispatchTest extends SandeshaTestCase  {

	private static ConfigurationContext serverConfigCtx = null;
	private boolean startedServer = false;
	
	public ForceInboundDispatchTest () {
        super ("ForceDispatchTest");
	}
	
	public void setUp () throws Exception {
		super.setUp();
		String repoPath = "target" + File.separator + "repos" + File.separator + "server";
		String axis2_xml = "target" + File.separator + "repos" + File.separator + "server" + File.separator + "server_axis2.xml";
		if (!startedServer)
			serverConfigCtx = startServer(repoPath, axis2_xml);
		startedServer = true;
	}
	
	/**
	 * Override the teardown processing
	 */
	public void tearDown () throws Exception {
		super.tearDown();
	}

	public void testForceInvoke () throws AxisFault,InterruptedException  {
		
		String to = "http://127.0.0.1:" + serverPort + "/axis2/services/RMSampleService";
		
		String repoPath = "target" + File.separator + "repos" + File.separator + "client";
		String axis2_xml = "target" + File.separator + "repos" + File.separator + "client" + File.separator + "client_axis2.xml";

		ConfigurationContext configContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(repoPath,axis2_xml);

		Options clientOptions = new Options ();
		clientOptions.setAction(pingAction);
		clientOptions.setSoapVersionURI(SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI);
		
		clientOptions.setTo(new EndpointReference (to));
		
		String sequenceKey = "sequence1";
		clientOptions.setProperty(SandeshaClientConstants.SEQUENCE_KEY,sequenceKey);
		ServiceClient serviceClient = new ServiceClient (configContext,null);
		serviceClient.setOptions(clientOptions);
		
		try{
			serviceClient.fireAndForget(getPingOMBlock("ping1"));		
			
			//now deliver the next out of order 
			clientOptions.setProperty(SandeshaClientConstants.MESSAGE_NUMBER,new Long(3));
			serviceClient.fireAndForget(getPingOMBlock("ping3"));
	
			Thread.sleep(5000);
			StorageManager mgr = SandeshaUtil.getInMemoryStorageManager(configContext);
			Transaction t = mgr.getTransaction();
			String inboundSequenceID = SandeshaUtil.getSequenceIDFromInternalSequenceID(SandeshaUtil.getInternalSequenceID(to, sequenceKey),
					mgr);
			t.commit();
			
			SandeshaClient.forceDispatchOfInboundMessages(serverConfigCtx, 
					inboundSequenceID, 
					true); //allow later msgs to be delivered 
			
			//check that the server is now expecting msg 4
			StorageManager serverStore = SandeshaUtil.getInMemoryStorageManager(serverConfigCtx);
			t = serverStore.getTransaction();
			RMDBean rMDBean = 
				serverStore.getRMDBeanMgr().retrieve(inboundSequenceID);
			assertNotNull(rMDBean);
			assertEquals(rMDBean.getNextMsgNoToProcess(), 4);
			
			//also check that the sequence has an out of order gap that contains msg 2			
			assertNotNull(rMDBean.getOutOfOrderRanges());
			RangeString rangeString = rMDBean.getOutOfOrderRanges();
			assertTrue(rangeString.isMessageNumberInRanges(2));
			t.commit();
			
			//we deliver msg 2
			//set highest out msg number to 1
			String internalSequenceId = SandeshaUtil.getInternalSequenceID(to, sequenceKey);
			t = mgr.getTransaction();
			RMSBean rmsBean = SandeshaUtil.getRMSBeanFromInternalSequenceId(mgr, internalSequenceId);
			rmsBean.setNextMessageNumber(1);
			// Update the bean
			mgr.getRMSBeanMgr().update(rmsBean);
			t.commit();
			
			clientOptions.setProperty(SandeshaClientConstants.MESSAGE_NUMBER,new Long(2));
			serviceClient.fireAndForget(getPingOMBlock("ping2"));
		}
		finally{
			configContext.getListenerManager().stop();
			serviceClient.cleanup();			
		}

	}
	
	public void testForceInvokeWithDiscardGaps () throws AxisFault  {
		
		String to = "http://127.0.0.1:" + serverPort + "/axis2/services/RMSampleService";
		
		String repoPath = "target" + File.separator + "repos" + File.separator + "client";
		String axis2_xml = "target" + File.separator + "repos" + File.separator + "client" + File.separator + "client_axis2.xml";

		ConfigurationContext configContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(repoPath,axis2_xml);

		Options clientOptions = new Options ();
		clientOptions.setAction(pingAction);
		clientOptions.setSoapVersionURI(SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI);
		
		clientOptions.setTo(new EndpointReference (to));
		
		String sequenceKey = "sequence2";
		clientOptions.setProperty(SandeshaClientConstants.SEQUENCE_KEY,sequenceKey);
		ServiceClient serviceClient = new ServiceClient (configContext,null);
		serviceClient.setOptions(clientOptions);
		try
		{
			serviceClient.fireAndForget(getPingOMBlock("ping1"));		
			
			//now deliver the next out of order 
			clientOptions.setProperty(SandeshaClientConstants.MESSAGE_NUMBER,new Long(3));
			serviceClient.fireAndForget(getPingOMBlock("ping3"));
	
			String internalSequenceId = SandeshaUtil.getInternalSequenceID(to, sequenceKey);
			waitForMessageToBeAcked(serviceClient, internalSequenceId);
			
			StorageManager mgr = SandeshaUtil.getInMemoryStorageManager(configContext);
			Transaction t = mgr.getTransaction();
			String inboundSequenceID = SandeshaUtil.getSequenceIDFromInternalSequenceID(internalSequenceId,
					mgr);
			t.commit();
			
			SandeshaClient.forceDispatchOfInboundMessages(serverConfigCtx, inboundSequenceID, false);
			
			//check that the server is now expecting msg 4
			StorageManager serverMgr = SandeshaUtil.getInMemoryStorageManager(serverConfigCtx);
			t = serverMgr.getTransaction();
			RMDBean rMDBean = serverMgr.getRMDBeanMgr().retrieve(inboundSequenceID);
			assertNotNull(rMDBean);
			assertEquals(rMDBean.getNextMsgNoToProcess(), 4);
			t.commit();
	  }
		finally{
			configContext.getListenerManager().stop();
			serviceClient.cleanup();			
		}

	}
	
  /**
   * Waits for the maximum of "waittime" for a message to be acked, before returning control to the application.
   * @throws SandeshaException 
   */
  private void waitForMessageToBeAcked(ServiceClient serviceClient, String internalSequenceId) throws SandeshaException
  {
    // Get the highest out message number
    ConfigurationContext context = serviceClient.getServiceContext().getConfigurationContext();
    StorageManager storageManager = SandeshaUtil.getSandeshaStorageManager(context, context.getAxisConfiguration());
    
    // Get a transaction for the property finding
    Transaction transaction = storageManager.getTransaction();
    
    // Get the highest out message property
    RMSBean rmsBean = SandeshaUtil.getRMSBeanFromInternalSequenceId(storageManager, internalSequenceId);
    
    transaction.commit();
    
    long highestOutMsgNum = rmsBean.getHighestOutMessageNumber();
    Long highestOutMsgKey = new Long(highestOutMsgNum);
    
    long timeNow = System.currentTimeMillis();
    long timeToComplete = timeNow + waitTime;
    boolean complete = false;    
    
    while (!complete && timeNow < timeToComplete)
    {
      timeNow = System.currentTimeMillis();

      try
      {                              
        SequenceReport sequenceReport = SandeshaClient.getOutgoingSequenceReport(serviceClient);
        
        if (sequenceReport.getCompletedMessages().contains(highestOutMsgKey))
          complete = true;
        else
          Thread.sleep(tickTime);
  
      }
      catch (Exception e)
      {
        // Ignore
      }
    }
  }
}
