/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.marshall;

import junit.framework.TestCase;
import org.jboss.cache.Fqn;
import org.jgroups.blocks.MethodCall;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

public abstract class TreeCacheMarshallerTestBase extends TestCase
{
   protected String currentVersion;
   protected int currentVersionShort;
   protected Class expectedMarshallerClass;
   protected VersionAwareMarshaller marshaller;


   protected void setUp()
   {
      marshaller = new VersionAwareMarshaller(new RegionManager(), false, false, currentVersion);
   }

   protected void tearDown()
   {
      marshaller = null;
   }

   protected void assertMethodCallsEquals(MethodCall call1, MethodCall call2)
   {
      if (call1 == call2) return;

      assertEquals("Method IDs should match", ((JBCMethodCall) call1).getMethodId(), ((JBCMethodCall) call2).getMethodId());
      assertEquals("Method names should match", call1.getName(), call2.getName());
      assertEquals("Method reflection objects should match", call1.getMethod(), call2.getMethod());
      if (call1.getArgs() == null || call2.getArgs() == null)
      {
         assertNull("Both args should be null", call1.getArgs());
         assertNull("Both args should be null", call2.getArgs());
      }
      else
      {
         Object[] call1Args = call1.getArgs();
         Object[] call2Args = call2.getArgs();

         assertObjectArraysAreEqual(call1Args, call2Args);
      }
   }

   protected void assertObjectArraysAreEqual(Object[] a1, Object[] a2)
   {
      assertEquals("Number of args should match", a1.length, a2.length);

      for (int i = 0; i < a1.length; i++)
      {
         if (a1[i] instanceof MethodCall && a2[i] instanceof MethodCall)
         {
            assertMethodCallsEquals((MethodCall) a1[i], (MethodCall) a2[i]);
         }
         else if (a1[i] instanceof List && a2[i] instanceof List)
         {
            Object[] a1Elements = ((List) a1[i]).toArray();
            Object[] a2Elements = ((List) a2[i]).toArray();

            assertObjectArraysAreEqual(a1Elements, a2Elements);
         }
         else
         {
            assertEquals("Argument # " + i + " should be equal", a1[i], a2[i]);
         }
      }

   }


   public void testGetMarshaller()
   {
      assertEquals("Only one marshaller should be in the map by this stage", 1, marshaller.marshallers.size());

      assertEquals(expectedMarshallerClass, marshaller.getMarshaller(currentVersionShort).getClass());
      assertEquals(LegacyTreeCacheMarshaller.class, marshaller.getMarshaller(15).getClass());
      assertEquals(LegacyTreeCacheMarshaller.class, marshaller.getMarshaller(1).getClass());
      assertEquals(LegacyTreeCacheMarshaller.class, marshaller.getMarshaller(-1).getClass());
      assertEquals(LegacyTreeCacheMarshaller.class, marshaller.getMarshaller(0).getClass());

      assertEquals("Two marshallers should be in the map by this stage", 2, marshaller.marshallers.size());
   }

   public void testStringBasedFqn() throws Exception
   {
      Fqn fqn = new Fqn(new Object[]{"JSESSIONID", "1010.10.5:3000", "1234567890", "1"});
      byte[] asBytes = marshaller.objectToByteBuffer(fqn);
      System.out.println("Marshalled to " + asBytes.length + " bytes");
      Object o2 = marshaller.objectFromByteBuffer(asBytes);
      assertEquals(fqn, o2);
   }

   public void testNonStringBasedFqn() throws Exception
   {
      Fqn fqn = new Fqn(new Object[]{new Integer(3), Boolean.FALSE});
      byte[] asBytes = marshaller.objectToByteBuffer(fqn);
      Object o2 = marshaller.objectFromByteBuffer(asBytes);
      assertEquals(fqn, o2);
   }

   public void testMethodCall() throws Exception
   {
      Fqn fqn = new Fqn(new Object[]{new Integer(3), Boolean.FALSE});
      MethodCall call = MethodCallFactory.create(MethodDeclarations.putKeyValMethodLocal, new Object[]{null, fqn, "key", "value", Boolean.TRUE});
      byte[] asBytes = marshaller.objectToByteBuffer(call);
      Object o2 = marshaller.objectFromByteBuffer(asBytes);

      assertTrue("Unmarshalled object should be a method call", o2 instanceof MethodCall);
      MethodCall m2 = (MethodCall) o2;

      assertMethodCallsEquals(call, m2);
   }

   public void testNestedMethodCall() throws Exception
   {
      Fqn fqn = new Fqn(new Object[]{new Integer(3), Boolean.FALSE});
      MethodCall call = MethodCallFactory.create(MethodDeclarations.putKeyValMethodLocal, new Object[]{null, fqn, "key", "value", Boolean.TRUE});
      MethodCall replicateCall = MethodCallFactory.create(MethodDeclarations.replicateMethod, new Object[]{call});
      byte[] asBytes = marshaller.objectToByteBuffer(replicateCall);
      Object o2 = marshaller.objectFromByteBuffer(asBytes);
      assertTrue("Unmarshalled object should be a method call", o2 instanceof MethodCall);
      MethodCall m2 = (MethodCall) o2;

      assertMethodCallsEquals(replicateCall, m2);
   }

   public void testLargeString() throws Exception
   {
      StringBuffer sb = new StringBuffer();
      int stringSize = 32767;

      for (int i = 0; i < stringSize; i++) sb.append('a');

      String largeString = sb.toString();

      assertEquals(stringSize, largeString.length());

      byte[] buf = marshaller.objectToByteBuffer(largeString);

      assertEquals(largeString, marshaller.objectFromByteBuffer(buf));
   }

   public void testLargerString() throws Exception
   {
      StringBuffer sb = new StringBuffer();
      int stringSize = 32768;

      for (int i = 0; i < stringSize; i++) sb.append('a');

      String largeString = sb.toString();

      assertEquals(stringSize, largeString.length());

      byte[] buf = marshaller.objectToByteBuffer(largeString);

      assertEquals(largeString, marshaller.objectFromByteBuffer(buf));
   }

   public void testEvenLargerString() throws Exception
   {
      StringBuffer sb = new StringBuffer();
      int stringSize = 32769;

      for (int i = 0; i < stringSize; i++) sb.append('a');

      String largeString = sb.toString();

      assertEquals(stringSize, largeString.length());

      byte[] buf = marshaller.objectToByteBuffer(largeString);

      assertEquals(largeString, marshaller.objectFromByteBuffer(buf));
   }

   public void testReplicationQueue() throws Exception
   {
      doReplicationQueueTest();
   }

   public void testReplicationQueueWithRegionBasedMarshalling() throws Exception
   {
      marshaller = new VersionAwareMarshaller(new RegionManager(), false, true, currentVersion);
      doReplicationQueueTest();
   }

   protected void doReplicationQueueTest() throws Exception
   {
      // replication queue takes a list of replicate() MethodCalls and wraps them in a single replicate call.
      List calls = new ArrayList();

      Fqn f = new Fqn(new Object[]{"BlahBlah", new Integer(3), Boolean.FALSE});
      String k = "key", v = "value";

      MethodCall actualCall = MethodCallFactory.create(MethodDeclarations.putKeyValMethodLocal, new Object[]{null, f, k, v, Boolean.TRUE});
      MethodCall replicateCall = MethodCallFactory.create(MethodDeclarations.replicateMethod, new Object[]{actualCall});

      calls.add(replicateCall);

      actualCall = MethodCallFactory.create(MethodDeclarations.putKeyValMethodLocal, new Object[]{null, f, k, v, Boolean.TRUE});
      replicateCall = MethodCallFactory.create(MethodDeclarations.replicateMethod, new Object[]{actualCall});

      calls.add(replicateCall);

      MethodCall call = MethodCallFactory.create(MethodDeclarations.replicateAllMethod, new Object[]{calls});

      byte[] buf = marshaller.objectToByteBuffer(call);

      assertMethodCallsEquals(call, (MethodCall) marshaller.objectFromByteBuffer(buf));
   }

   public void testHandleLegacyStream() throws Exception
   {
      // create a 'legacy' stream.
      ByteArrayOutputStream bout = new ByteArrayOutputStream();
      ObjectOutputStream out = new ObjectOutputStream(bout);

      out.writeObject("hello");

      Object unmarshalled = marshaller.objectFromByteBuffer(bout.toByteArray());

      assertEquals("hello", unmarshalled);
   }

}
