package org.jboss.cache.pojo.collection;

import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.pojo.PojoCache;
import org.jboss.cache.pojo.PojoCacheFactory;
import org.jboss.cache.pojo.test.Address;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * Set interface testing.
 *
 * @author Ben Wang
 */

@Test(groups = {"functional"})
public class CachedSetTest
{
   Log log = LogFactory.getLog(CachedSetTest.class);
   PojoCache cache_;
   Set<String> skills;


   @BeforeMethod(alwaysRun = true)
   protected void setUp() throws Exception
   {
      log.info("setUp() ....");
      String configFile = "META-INF/local-service.xml";
      boolean toStart = false;
      cache_ = PojoCacheFactory.createCache(configFile, toStart);
      cache_.start();

      stage();
   }

   @AfterMethod(alwaysRun = true)
   protected void tearDown() throws Exception
   {
      cache_.stop();
   }

   @SuppressWarnings("unchecked")
   protected void stage() throws Exception
   {
      skills = new HashSet<String>();
      skills.add("Java");
      skills.add("C++");
      skills.add("Perl");

      cache_.attach("/person/test7", skills);
      skills = (Set<String>) cache_.find("/person/test7");
      int size = skills.size();
      assertEquals("Size is ", 3, size);
   }

   public void testClear() throws Throwable
   {
      int size = skills.size();
      assertEquals("Size is ", 3, size);

      skills.clear();
      size = skills.size();
      assertEquals("Size is ", 0, size);

      assertTrue("Should be empty", skills.isEmpty());
   }

   public void testAddAndRemoveIndex() throws Throwable
   {
      skills.add("Golf");
      int size = skills.size();
      assertEquals("Size is ", 4, size);

      skills.add("Golf");
      size = skills.size();
      assertEquals("Size is ", 4, size);

      assertTrue("Skill shuold contain Golf ", skills.contains("Golf"));
      skills.remove("C++");
      size = skills.size();
      assertEquals("Size is ", 3, size);
      assertFalse("Skill does not contain C++ anymore ", skills.contains("C++"));

      skills.add("Golf");
      size = skills.size();
      assertEquals("Size is ", 3, size);
      skills.clear();
      size = skills.size();
      assertEquals("Size is ", 0, size);

      assertTrue("Should be empty", skills.isEmpty());
   }

   private static class DumbObject implements java.io.Serializable
   {
      private static final long serialVersionUID = 1L;
      int i;
      DumbObject(int i) { this.i = i; }
      public int hashCode()
      {
         return 0;
      }
      public String toString()
      {
         return "" + i;
      }
   }

   @SuppressWarnings("unchecked")
   public void testConflictingHash() throws Exception
   {
      Set<Serializable> set = new HashSet<Serializable>();
      String nulls = null;
      DumbObject o1 = new DumbObject(1);
      DumbObject o2 = new DumbObject(2);
      DumbObject o3 = new DumbObject(3);
      set.add(o1);
      set.add(o2);
      set.add(o3);
      set.add(nulls);
      Set<Serializable> set2 = Collections.unmodifiableSet(new HashSet<Serializable>(set));

      cache_.attach("/test", set); // attach
      set = (Set<Serializable>) cache_.find("/test");
      assertEquals("Same", set2, set);
      assertEquals(true, set.remove(o2));
      assertEquals(true, set.remove(nulls));
      assertEquals(false, set.remove(o2));
      assertEquals("Same minus 2 ", set2.size()-2, set.size());
      assertEquals("set " + set, true, set.add(o2));
      assertEquals("set " + set, true, set.add(nulls));
      assertEquals(false, set.add(o2));
      assertEquals(false, set.add(nulls));
      assertEquals("Same", set2, set);
   }

   public void testAddAndRemoveAll() throws Throwable
   {
      List<String> list = new ArrayList<String>();
      list.add("Tennis");
      list.add("Polo");
      list.add("Baseball");

      skills.addAll((Collection<String>) list);
      int size = skills.size();
      assertEquals("Size is ", 6, size);
      assertTrue("Skill contains Polo ", skills.contains("Polo"));

      skills.removeAll((Collection<String>) list);
      size = skills.size();
      assertEquals("Size is ", 3, size);
      assertFalse("Skill does not contain Polo ", skills.contains("Polo"));
      assertTrue("Skill contains C++ ", skills.contains("C++"));

   }

   public void testRemoveAndAdd()
   {
      assertTrue(skills.remove("C++"));
      assertTrue(skills.add("C++"));
      assertEquals("removeAndAdd: size is 3", 3, skills.size());
   }

   public void testIterator()
   {
      Iterator<String> it = skills.iterator();
      int counter = 0;
      while (it.hasNext())
      {
         counter++;
         it.next();
         it.remove();
      }

      assertEquals("iterator: Size should be ", 3, counter);
      assertEquals("iterator: Skills should be empty ", 0, skills.size());

      List<String> list = new ArrayList<String>();
      list.add("Tennis");
      list.add("Polo");
      list.add("Baseball");
      list.add("Soccer");
      list.add("Hockey");
      list.add("Lacrosse");
      skills.addAll(list);
      it = skills.iterator();
      while (it.hasNext())
      {
         counter++;
         if ("Polo".equals(it.next()))
         {
            it.remove();
         }
      }
      assertFalse("iterator: item removed via iterator", skills.contains("Polo"));
      assertTrue("iterator: item not removed via iterator", skills.contains("Lacrosse"));

      // Check for proper relationship between hasNext and next
      list = new ArrayList<String>();
      list.add("Tennis");
      list.add("Polo");
      list.add("Baseball");
      skills.addAll(list);
      it = skills.iterator();
      while (it.hasNext())
      {
         it.next();
      }
      try
      {
         it.next();
         fail("iterator: Didn't fail on next() when hasNext() == false");
      }
      catch (NoSuchElementException good)
      {
      }

      // Check for proper relationship between next and remove
      it = skills.iterator();
      try
      {
         it.remove();
      }
      catch (IllegalStateException good)
      {
         // behaves correctly per Iterator contract
      }
      catch (Exception e)
      {
         StringWriter stackTrace = new StringWriter();
         e.printStackTrace(new PrintWriter(stackTrace));
         fail("iterator: failed with incorrect exception type when removing " +
                 "without first calling next() -- " + e.getClass().getName() + ".  stackTrace=" + stackTrace);
      }


   }

   @SuppressWarnings("unchecked")
   public void testEquals() throws Throwable
   {
      Set<String> set = (Set<String>) cache_.find("/person/test7");
      assertTrue("iterator: List should be the same ", set.equals(skills));
      set = new HashSet<String>();
      set.add("German");
      set.add("test");
      set.add("English");
      assertFalse("List should not be the same ", set.equals(skills));
      assertFalse("List should not be the same ", skills.equals(set));
   }


   @SuppressWarnings("unchecked")
   public void testAttachAndDetach() throws Exception
   {
      Set<String> set = new HashSet<String>();
      set.add("English");
      set.add("French");
      set.add("Taiwanese");

      cache_.attach("/test", set); // attach
      set = (Set<String>) cache_.find("/test");
      assertEquals("Size ", 3, set.size());

      set = (Set<String>) cache_.detach("/test");
      assertEquals("Size ", 3, set.size());

      System.out.println("**** End of cache content **** ");
      set.remove("French");
      set.add("Hoklo");
      assertEquals("Size ", 3, set.size());
      assertTrue("Content ", set.contains("Hoklo"));

      // Try to re-attach
      cache_.attach("/test", set);
      set.remove("Taiwanese");
      assertEquals("Size ", 2, set.size());
   }

   @SuppressWarnings("unchecked")
   public void testSerialize() throws Exception
   {
      Set<String> set = new HashSet<String>();
      set.add("English");
      set.add("French");
      set.add("Taiwanese");

      cache_.attach("/test", set); // attach
      set = (Set<String>) cache_.find("/test");
      ByteArrayOutputStream bout = new ByteArrayOutputStream();
      ObjectOutputStream out = new ObjectOutputStream(bout);
      out.writeObject(set);
      Set<String> newSet = (Set<String>)
         (new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()))).readObject();

      assertTrue(newSet instanceof HashSet);
      assertEquals(set, newSet);
   }

   @SuppressWarnings("unchecked")
   public void testPojoAttachAndDetach() throws Exception
   {
      Address add1 = new Address();
      add1.setCity("San Jose");
      add1.setZip(95123);

      Address add2 = new Address();
      add2.setCity("Sunnyvale");
      add2.setZip(94086);

      Address add3 = new Address();
      add3.setCity("Santa Clara");
      add3.setZip(951131);

      Set<Address> set = new HashSet<Address>();
      set.add(add1);
      set.add(add2);
      set.add(add3);

      cache_.attach("/test", set); // attach
      set = (Set<Address>) cache_.find("/test");
      assertEquals("Size ", 3, set.size());

      set = (Set<Address>) cache_.detach("/test");
      assertEquals("Size ", 3, set.size());

      System.out.println("**** End of cache content **** ");
      set.remove(add2);
      set.add(add2);
      assertEquals("Size ", 3, set.size());
      assertTrue("Content ", set.contains(add2));

      // Try to re-attach
      cache_.attach("/test", set);
      set.remove(add2);
      assertEquals("Size ", 2, set.size());
   }

   public void testToArray() throws Exception
   {
      Object[] arr = skills.toArray();
      assertEquals("toArray: array length is correct", 3, arr.length);
      testLanguagesFound(arr);

      Object[] arr1 = skills.toArray(arr);
      assertTrue("toArray: arrays match", Arrays.equals(arr, arr1));

      arr = new Object[5];
      arr = skills.toArray(arr);
      assertEquals("toArray: array length is correct", 5, arr.length);
      testLanguagesFound(arr);
      assertEquals(null, arr[3]);
      assertEquals(null, arr[4]);

      arr = new Object[2];
      arr = skills.toArray(arr);
      assertEquals("toArray: array length is correct", 3, arr.length);
      testLanguagesFound(arr);

   }

   private void testLanguagesFound(Object[] arr)
   {
      boolean[] found = new boolean[3];
      for (int i = 0; i < arr.length; i++)
      {
         if ("Java".equals(arr[i]))
            found[0] = true;
         else if ("C++".equals(arr[i]))
            found[1] = true;
         else if ("Perl".equals(arr[i]))
            found[2] = true;
      }
      assertTrue("toArray: all elements found", found[0] && found[1] && found[2]);

   }

   public void testRetainAll() throws Exception
   {
      LinkedList<String> list2 = new LinkedList<String>();
      list2.add("Java");

      assertTrue("testRetainAll", skills.retainAll(list2));
      // should only have Java left
      assertTrue("testRetainAll, skills size should be 1 but is " + skills.size(), skills.size() == 1);
      assertTrue("testRetainAll", skills.contains("Java"));

   }

   public void testContainsAll() throws Exception
   {
      LinkedList<String> list2 = new LinkedList<String>();
      list2.add("Java");
      list2.add("C++");
      list2.add("Perl");

      skills.clear();

      assertTrue(skills.addAll(list2));
      list2.remove("Java");
      assertTrue("testContainsAll", skills.containsAll(list2));
      skills.remove("C++");
      assertFalse("testContainsAll", skills.containsAll(list2));
   }




}

