package org.jctools.maps.nbhs_test;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Iterator;

import org.jctools.maps.NonBlockingSetInt;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

/*
 * Written by Cliff Click and released to the public domain, as explained at
 * http://creativecommons.org/licenses/publicdomain
 */

// Test NonBlockingSetInt via JUnit
public class nbsi_tester {

  static private NonBlockingSetInt _nbsi;
  @BeforeClass public static void setUp   () { _nbsi = new NonBlockingSetInt(); }
  @AfterClass  public static void tearDown() { _nbsi = null; }

  // Test some basic stuff; add a few keys, remove a few keys
  @Test
  public void testBasic() {
    assertTrue ( _nbsi.isEmpty() );
    assertTrue ( _nbsi.add(1) );
    checkSizes (1);
    assertTrue ( _nbsi.add(2) );
    checkSizes (2);
    assertFalse( _nbsi.add(1) );
    assertFalse( _nbsi.add(2) );
    checkSizes (2);
    assertThat ( _nbsi.remove(1), is(true ) );
    checkSizes (1);
    assertThat ( _nbsi.remove(1), is(false) );
    assertTrue ( _nbsi.remove(2) );
    checkSizes (0);
    assertFalse( _nbsi.remove(2) );
    assertFalse( _nbsi.remove(3) );
    assertTrue ( _nbsi.isEmpty() );
    assertTrue ( _nbsi.add(63) );
    checkSizes (1);
    assertTrue ( _nbsi.remove(63) );
    assertFalse( _nbsi.remove(63) );


    assertTrue ( _nbsi.isEmpty() );
    assertTrue ( _nbsi.add(10000) );
    checkSizes (1);
    assertTrue ( _nbsi.add(20000) );
    checkSizes (2);
    assertFalse( _nbsi.add(10000) );
    assertFalse( _nbsi.add(20000) );
    checkSizes (2);
    assertThat ( _nbsi.remove(10000), is(true ) );
    checkSizes (1);
    assertThat ( _nbsi.remove(10000), is(false) );
    assertTrue ( _nbsi.remove(20000) );
    checkSizes (0);
    assertFalse( _nbsi.remove(20000) );
    _nbsi.clear();
  }

  // Check all iterators for correct size counts
  private void checkSizes(int expectedSize) {
    assertEquals( "size()", _nbsi.size(), expectedSize );
    Iterator it = _nbsi.iterator();
    int result = 0;
    while (it.hasNext()) {
      result++;
      it.next();
    }
    assertEquals( "iterator missed", expectedSize, result );
  }


  @Test
  public void testIteration() {
    assertTrue ( _nbsi.isEmpty() );
    assertTrue ( _nbsi.add(1) );
    assertTrue ( _nbsi.add(2) );

    StringBuilder buf = new StringBuilder();
    for( Integer val : _nbsi )
      buf.append(val);
    assertThat("found all vals",buf.toString(),anyOf(is("12"),is("21")));

    assertThat("toString works",_nbsi.toString(), anyOf(is("[1, 2]"),is("[2, 1]")));
    _nbsi.clear();
  }

  @Test
  public void testIterationBig() {
    for( int i=0; i<100; i++ )
      _nbsi.add(i);
    assertThat( _nbsi.size(), is(100) );

    int sz =0;
    int sum = 0;
    for( Integer x : _nbsi ) {
      sz++;
      sum += x;
      assertTrue(x>=0 && x<=99);
    }
    assertThat("Found 100 ints",sz,is(100));
    assertThat("Found all integers in list",sum,is(100*99/2));

    assertThat( "can remove 3", _nbsi.remove(3), is(true) );
    assertThat( "can remove 4", _nbsi.remove(4), is(true) );
    sz =0;
    sum = 0;
    for( Integer x : _nbsi ) {
      sz++;
      sum += x;
      assertTrue(x>=0 && x<=99);
    }
    assertThat("Found 98 ints",sz,is(98));
    assertThat("Found all integers in list",sum,is(100*99/2 - (3+4)));
    _nbsi.clear();
  }

  @Test
  public void testSerial() {
    assertTrue ( _nbsi.isEmpty() );
    assertTrue ( _nbsi.add(1) );
    assertTrue ( _nbsi.add(2) );

    // Serialize it out
    try {
      FileOutputStream fos = new FileOutputStream("NBSI_test.txt");
      ObjectOutputStream out = new ObjectOutputStream(fos);
      out.writeObject(_nbsi);
      out.close();
    } catch(IOException ex) {
      ex.printStackTrace();
    }

    // Read it back
    try {
      File f = new File("NBSI_test.txt");
      FileInputStream fis = new FileInputStream(f);
      ObjectInputStream in = new ObjectInputStream(fis);
      NonBlockingSetInt nbsi = (NonBlockingSetInt)in.readObject();
      in.close();
      assertEquals(_nbsi.toString(),nbsi.toString());
      if( !f.delete() ) throw new IOException("delete failed");
    } catch(IOException|ClassNotFoundException ex) {
      ex.printStackTrace();
    }
    _nbsi.clear();
  }

  // Do some simple concurrent testing
  @Test
  public void testConcurrentSimple() throws InterruptedException {
    final NonBlockingSetInt nbsi = new NonBlockingSetInt();

    // In 2 threads, add & remove even & odd elements concurrently
    Thread t1 = new Thread() { public void run() { work_helper(nbsi,"T1",1); } };
    t1.start();
    work_helper(nbsi,"T0",1);
    t1.join();

    // In the end, all members should be removed
    StringBuffer buf = new StringBuffer();
    buf.append("Should be emptyset but has these elements: {");
    boolean found = false;
    for( Integer x : nbsi ) {
      buf.append(" ").append(x);
      found = true;
    }
    if( found ) System.out.println(buf);
    assertThat( "concurrent size=0", nbsi.size(), is(0) );
    for( Integer x : nbsi ) {
      assertTrue("No elements so never get here",false);
    }
    _nbsi.clear();
  }

  void work_helper(NonBlockingSetInt nbsi, String thrd, int d) {
    final int ITERS = 100000;
    for( int j=0; j<10; j++ ) {
      //long start = System.nanoTime();
      for( int i=d; i<ITERS; i+=2 )
        nbsi.add(i);
      for( int i=d; i<ITERS; i+=2 )
        nbsi.remove(i);
      //double delta_nanos = System.nanoTime()-start;
      //double delta_secs = delta_nanos/1000000000.0;
      //double ops = ITERS*2;
      //System.out.println("Thrd"+thrd+" "+(ops/delta_secs)+" ops/sec size="+nbsi.size());
    }
  }
}
