#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "pub_core_basics.h"
#include "pub_core_libcbase.h"
#include "pub_core_libcassert.h"
#include "pub_core_libcprint.h"

// I need this to avoid some signedness warnings, not sure why
// #define Char char
// jrs 19 Aug 2005: m_oset.c relies on Char being a signed char.
// It appears that plain 'char' on ppc32 is unsigned and so the
// above #define screws up the AVL tree balancing logic and
// leads to segfaults.  Commenting it out and using the standard
// definition of Char from pub_core_basics.h seems a good solution
// as that has the same signedness on all platforms.

// Crudely redirect various VG_(foo)() functions to their libc equivalents.
#undef vg_assert
#define vg_assert(e)                   assert(e)
#undef vg_assert2
#define vg_assert2(e, fmt, args...)    assert(e)

#define vgPlain_printf                 printf
#define vgPlain_memset                 memset
#define vgPlain_memcpy                 memcpy
#define vgPlain_memmove                memmove

// Crudely replace some functions (in m_xarray.c, but not needed for
// this unit test) by (hopefully) failing asserts.
#define vgPlain_ssort(a,b,c,d) assert(a)
#define vgPlain_vcbprintf(a,b,...) assert(a == b)
#include "coregrind/m_xarray.c"
#undef vgPlain_ssort
#undef vgPlain_vcbprintf
#include "coregrind/m_poolalloc.c"
#include "coregrind/m_oset.c"

#define NN  1000       // Size of OSets being created


/* Consistent random number generator, so it produces the
   same results on all platforms. */

#define random error_do_not_use_libc_random

static UInt seed = 0;
static UInt myrandom( void )
{
  seed = (1103515245 * seed + 12345);
  return seed;
}

static void* allocate_node(const HChar* cc, SizeT szB)
{ return malloc(szB); }

static void free_node(void* p)
{ return free(p); }


//---------------------------------------------------------------------------
// Word example
//---------------------------------------------------------------------------

// This example shows that an element can be a single value (in this
// case a Word), in which case the element is also the key.

__attribute__((unused))
static const HChar *wordToStr(const void *p)
{
   static HChar buf[32];
   sprintf(buf, "%ld", *(Word*)p);
   return buf;
}

__attribute__((unused))
static Word wordCmp(void* vkey, void* velem)
{
   return *(Word*)vkey - *(Word*)velem;
}

void example1singleset(OSet* oset, char *descr)
{
   Int  i, n;
   UWord v, prev;
   UWord* vs[NN];
   UWord *pv;
   UWord  sorted_elts[NN]; // Used to test VG_(OSetGen_ResetIterAt)

   // Try some operations on an empty OSet to ensure they don't screw up.
   vg_assert( ! VG_(OSetGen_Contains)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Lookup)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Remove)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Next)(oset) );
   vg_assert( 0 == VG_(OSetGen_Size)(oset) );

   // Create some elements, with gaps (they're all even) but no dups,
   // and shuffle them randomly.
   for (i = 0; i < NN; i++) {
      vs[i] = VG_(OSetGen_AllocNode)(oset, sizeof(Word));
      *(vs[i]) = 2*(i+1);
      sorted_elts[i] = *(vs[i]);
   }
   seed = 0;
   for (i = 0; i < NN; i++) {
      UWord r1  = myrandom() % NN;
      UWord r2  = myrandom() % NN;
      UWord* tmp= vs[r1];
      vs[r1]   = vs[r2];
      vs[r2]   = tmp;
   }

   // Insert the elements
   for (i = 0; i < NN; i++) {
      VG_(OSetGen_Insert)(oset, vs[i]);
   }

   // Check the size
   vg_assert( NN == VG_(OSetGen_Size)(oset) );

   // Check we can find all the elements we inserted
   for (i = 0; i < NN; i++) {
      assert( VG_(OSetGen_Contains)(oset, vs[i]) );
   }

#define FULLCHECKEVERY 20
   // Check VG_(OSetGen_ResetIterAt) works before, at, and after each element.
   // For some elements, we check all the successive elements.
   for (i = 0; i < NN; i++) {
      UWord k;
      UWord *pval;
      Int j;

      // check ResetIterAt just before an elt gives this elt.
      k = sorted_elts[i] - 1;
      VG_(OSetGen_ResetIterAt) (oset, &k);
      // Check all elts till the end
      for (j = i; j < NN; j++) {
         pval = VG_(OSetGen_Next)(oset);
         assert (*pval == sorted_elts[j]);
         if (i % FULLCHECKEVERY != 0) break;
      }

      // check ResetIterAt at an elt gives this elt.
      k = sorted_elts[i];
      VG_(OSetGen_ResetIterAt) (oset, &k);
      // Check all elts till the end
      for (j = i; j < NN; j++) {
         pval = VG_(OSetGen_Next)(oset);
         assert (*pval == sorted_elts[j]);
         if (i % FULLCHECKEVERY != 0) break;
      }

      // check ResetIterAt after an elt gives the next elt or nothing
      // when we reset after the last elt.
      k = sorted_elts[i] + 1;
      VG_(OSetGen_ResetIterAt) (oset, &k);
      if (i < NN - 1) {
         for (j = i+1; j < NN; j++) {
            pval = VG_(OSetGen_Next)(oset);
            assert (*pval == sorted_elts[j]);
            if (i % FULLCHECKEVERY != 0) break;
         }
      } else {
         pval = VG_(OSetGen_Next)(oset);
         assert (pval == NULL);
      }
      
   }

   // Check we cannot find elements we did not insert, below, within (odd
   // numbers), and above the inserted elements.
   v = 0;
   assert( ! VG_(OSetGen_Contains)(oset, &v) );
   for (i = 0; i < NN; i++) {
      v = *(vs[i]) + 1;
      assert( ! VG_(OSetGen_Contains)(oset, &v) );
   }
   v = 2*(NN+1);
   assert( ! VG_(OSetGen_Contains)(oset, &v) );

   // Check we can find all the elements we inserted, and the right values
   // are returned.
   for (i = 0; i < NN; i++) {
      assert( vs[i] == VG_(OSetGen_Lookup)(oset, vs[i]) );
   }

   // Check that we can iterate over the OSet elements in sorted order, and
   // there is the right number of them.
   n = 0;
   pv = NULL;
   prev = 0;
   VG_(OSetGen_ResetIter)(oset);
   while ( (pv = VG_(OSetGen_Next)(oset)) ) {
      UWord curr = *pv;
      assert(prev < curr); 
      prev = curr;
      n++;
   }
   assert(NN == n);
   vg_assert( ! VG_(OSetGen_Next)(oset) );
   vg_assert( ! VG_(OSetGen_Next)(oset) );

   // Check that we can remove half of the elements, and that their values
   // are as expected.
   for (i = 0; i < NN; i += 2) {
      assert( pv = VG_(OSetGen_Remove)(oset, vs[i]) );
      assert( pv == vs[i] );
   }

   // Check the size
   vg_assert( NN/2 == VG_(OSetGen_Size)(oset) );

   // Check we can find the remaining elements (with the right values).
   for (i = 1; i < NN; i += 2) {
      assert( pv = VG_(OSetGen_LookupWithCmp)(oset, vs[i], NULL) );
      assert( pv == vs[i] );
   }

   // Check we cannot find any of the elements we removed.
   for (i = 0; i < NN; i += 2) {
      assert( ! VG_(OSetGen_Contains)(oset, vs[i]) );
   }

   // Check that we can remove the remaining half of the elements, and that
   // their values are as expected.
   for (i = 1; i < NN; i += 2) {
      assert( pv = VG_(OSetGen_Remove)(oset, vs[i]) );
      assert( pv == vs[i] );
   }

   // Try some more operations on the empty OSet to ensure they don't screw up.
   vg_assert( ! VG_(OSetGen_Contains)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Lookup)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Remove)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Next)(oset) );
   vg_assert( 0 == VG_(OSetGen_Size)(oset) );

   // Free a few elements
   VG_(OSetGen_FreeNode)(oset, vs[0]);
   VG_(OSetGen_FreeNode)(oset, vs[1]);
   VG_(OSetGen_FreeNode)(oset, vs[2]);

   // Re-insert remaining elements, to give OSetGen_Destroy something to
   // work with.
   for (i = 3; i < NN; i++) {
      VG_(OSetGen_Insert)(oset, vs[i]);
   }

   // Print the list
   OSet_Print(oset, descr, wordToStr);

}

void example1(void)
{
   OSet *oset, *oset_empty_clone;

   // Create a static OSet of UWords.  This one uses fast (built-in)
   // comparisons.

   // First a single oset, no pool allocator.
   oset = VG_(OSetGen_Create)(0,
                              NULL,
                              allocate_node, "oset_test.1", free_node);
   example1singleset(oset, "single oset, no pool allocator");

   // Destroy the OSet
   VG_(OSetGen_Destroy)(oset);

   // Then same, but with a group allocator
   oset = VG_(OSetGen_Create_With_Pool)(0,
                                        NULL,
                                        allocate_node, "oset_test.1",
                                        free_node,
                                        101, sizeof(Word));
   example1singleset(oset, "single oset, pool allocator");

   // Destroy the OSet
   VG_(OSetGen_Destroy)(oset);


   // Then two sets, sharing a group allocator.
   oset = VG_(OSetGen_Create_With_Pool)
      (0,
       NULL,
       allocate_node, "oset_test.1", free_node,
       101, sizeof(Word));
   oset_empty_clone = VG_(OSetGen_EmptyClone) (oset);
   example1singleset(oset, "oset, shared pool");
   example1singleset(oset_empty_clone, "cloned oset, shared pool");
   // Destroy both OSet
   
   VG_(OSetGen_Destroy)(oset_empty_clone);
   VG_(OSetGen_Destroy)(oset);
   
}

void example1b(void)
{
   Int  i, n;
   UWord v = 0, prev;
   UWord vs[NN];

   // Create a static OSet of UWords.  This one uses fast (built-in)
   // comparisons.
   OSet* oset = VG_(OSetWord_Create)(allocate_node, "oset_test.2", free_node);

   // Try some operations on an empty OSet to ensure they don't screw up.
   vg_assert( ! VG_(OSetWord_Contains)(oset, v) );
   vg_assert( ! VG_(OSetWord_Remove)(oset, v) );
   vg_assert( ! VG_(OSetWord_Next)(oset, (UWord *)&v) );
   vg_assert( 0 == VG_(OSetWord_Size)(oset) );

   // Create some elements, with gaps (they're all even) but no dups,
   // and shuffle them randomly.
   for (i = 0; i < NN; i++) {
      vs[i] = 2*i;
   }
   seed = 0;
   for (i = 0; i < NN; i++) {
      UWord r1  = myrandom() % NN;
      UWord r2  = myrandom() % NN;
      UWord tmp = vs[r1];
      vs[r1]   = vs[r2];
      vs[r2]   = tmp;
   }

   // Insert the elements
   for (i = 0; i < NN; i++) {
      VG_(OSetWord_Insert)(oset, vs[i]);
   }

   // Check the size
   vg_assert( NN == VG_(OSetWord_Size)(oset) );

   // Check we can find all the elements we inserted
   for (i = 0; i < NN; i++) {
      assert( VG_(OSetWord_Contains)(oset, vs[i]) );
   }

   // Check we cannot find elements we did not insert, below, within (odd
   // numbers), and above the inserted elements.
   v = 0xffffffff;
   assert( ! VG_(OSetWord_Contains)(oset, v) );
   for (i = 0; i < NN; i++) {
      v = vs[i] + 1;
      assert( ! VG_(OSetWord_Contains)(oset, v) );
   }
   v = NN*2;
   assert( ! VG_(OSetWord_Contains)(oset, v) );

   // Check we can find all the elements we inserted.
   for (i = 0; i < NN; i++) {
      assert( VG_(OSetWord_Contains)(oset, vs[i]) );
   }

   // Check that we can iterate over the OSet elements in sorted order, and
   // there is the right number of them.
   n = 0;
   prev = 0;
   VG_(OSetWord_ResetIter)(oset);
   while ( VG_(OSetWord_Next)(oset, (UWord *)&v) ) {
      UWord curr = v;
      if (n == 0)
         assert(prev == curr); 
      else
         assert(prev < curr); 
      prev = curr;
      n++;
   }
   assert(NN == n);
   vg_assert( ! VG_(OSetWord_Next)(oset, (UWord *)&v) );
   vg_assert( ! VG_(OSetWord_Next)(oset, (UWord *)&v) );

   // Check that we can remove half of the elements.
   for (i = 0; i < NN; i += 2) {
      assert( VG_(OSetWord_Remove)(oset, vs[i]) );
   }

   // Check the size
   vg_assert( NN/2 == VG_(OSetWord_Size)(oset) );

   // Check we can find the remaining elements (with the right values).
   for (i = 1; i < NN; i += 2) {
      assert( VG_(OSetWord_Contains)(oset, vs[i]) );
   }

   // Check we cannot find any of the elements we removed.
   for (i = 0; i < NN; i += 2) {
      assert( ! VG_(OSetWord_Contains)(oset, vs[i]) );
   }

   // Check that we can remove the remaining half of the elements.
   for (i = 1; i < NN; i += 2) {
      assert( VG_(OSetWord_Remove)(oset, vs[i]) );
   }

   // Try some more operations on the empty OSet to ensure they don't screw up.
   vg_assert( ! VG_(OSetWord_Contains)(oset, v) );
   vg_assert( ! VG_(OSetWord_Remove)(oset, v) );
   vg_assert( ! VG_(OSetWord_Next)(oset, (UWord *)&v) );
   vg_assert( 0 == VG_(OSetWord_Size)(oset) );

   // Re-insert remaining elements, to give OSetWord_Destroy something to
   // work with.
   for (i = 3; i < NN; i++) {
      VG_(OSetWord_Insert)(oset, vs[i]);
   }

   // Print the list
   OSet_Print(oset, "oset1b", wordToStr);

   // Destroy the OSet
   VG_(OSetWord_Destroy)(oset);
}


//---------------------------------------------------------------------------
// Struct example
//---------------------------------------------------------------------------

// This element shows that a key can be in the middle of the element, and
// be of arbitrary size and even span multiple (contiguous) fields.  It
// also demonstrates how an OSet can be used to implement a list of
// non-overlapping intervals.

typedef struct {
   Int   b1;
   Addr  first;
   Addr  last;
   Int   b2;
}
Block;

__attribute__((unused))
static HChar *blockToStr(void *p)
{
   static HChar buf[32];
   Block* b = (Block*)p;
   sprintf(buf, "<(%d) %lu..%lu (%d)>", b->b1, b->first, b->last, b->b2);
   return buf;
}

static Word blockCmp(const void* vkey, const void* velem)
{
   Addr   key  = *(const Addr*)vkey;
   const Block* elem = (const Block*)velem;

   assert(elem->first <= elem->last);
   if (key < elem->first) return -1;
   if (key > elem->last)  return  1;
   return 0;
}

void example2(void)
{
   Int i, n;
   Addr a;
   Block* vs[NN];
   Block v, prev;
   Block *pv;

   // Create a dynamic OSet of Blocks.  This one uses slow (custom)
   // comparisons.
   OSet* oset = VG_(OSetGen_Create)(offsetof(Block, first),
                                    blockCmp,
                                    allocate_node, "oset_test.3", free_node);

   // Try some operations on an empty OSet to ensure they don't screw up.
   vg_assert( ! VG_(OSetGen_Contains)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Lookup)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Remove)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Next)(oset) );
   vg_assert( 0 == VG_(OSetGen_Size)(oset) );

   // Create some inputs, with gaps -- intervals are 1..3, 11..13, ... -- but
   // no dups, and shuffle them randomly.
   for (i = 0; i < NN; i++) {
      vs[i] = VG_(OSetGen_AllocNode)(oset, sizeof(Block));
      vs[i]->b1    = i;
      vs[i]->first = i*10 + 1;
      vs[i]->last  = vs[i]->first + 2;
      vs[i]->b2    = i+1;
   }
   seed = 0;
   for (i = 0; i < NN; i++) {
      Int r1  = myrandom() % NN;
      Int r2  = myrandom() % NN;
      Block* tmp = vs[r1];
      vs[r1]  = vs[r2];
      vs[r2]  = tmp;
   }

   // Insert the elements
   for (i = 0; i < NN; i++) {
      VG_(OSetGen_Insert)(oset, vs[i]);
   }

   // Check the size
   vg_assert( NN == VG_(OSetGen_Size)(oset) );

   // Check we can find all the elements we inserted, within the full range
   // of each Block.
   for (i = 0; i < NN; i++) {
      a = vs[i]->first + 0;    assert( VG_(OSetGen_Contains)(oset, &a) );
      a = vs[i]->first + 1;    assert( VG_(OSetGen_Contains)(oset, &a) );
      a = vs[i]->first + 2;    assert( VG_(OSetGen_Contains)(oset, &a) );
   }

   // Check we cannot find elements we did not insert, below and above the
   // ranges of the inserted elements.
   a = 0;
   assert( ! VG_(OSetGen_Contains)(oset, &a) );
   for (i = 0; i < NN; i++) {
      a = vs[i]->first - 1;    assert( ! VG_(OSetGen_Contains)(oset, &a) );
      a = vs[i]->first + 3;    assert( ! VG_(OSetGen_Contains)(oset, &a) );
   }

   // Check we can find all the elements we inserted, and the right values
   // are returned.
   for (i = 0; i < NN; i++) {
      a = vs[i]->first + 0;    assert( vs[i] == VG_(OSetGen_Lookup)(oset, &a) );
      a = vs[i]->first + 1;    assert( vs[i] == VG_(OSetGen_Lookup)(oset, &a) );
      a = vs[i]->first + 2;    assert( vs[i] == VG_(OSetGen_Lookup)(oset, &a) );
      assert( vs[i] == VG_(OSetGen_LookupWithCmp)(oset, &a, blockCmp) );
   }

   // Check that we can iterate over the OSet elements in sorted order, and
   // there is the right number of them.
   n = 0;
   pv = NULL;
   prev.last = 0;
   VG_(OSetGen_ResetIter)(oset);
   while ( (pv = VG_(OSetGen_Next)(oset)) ) {
      Block curr = *pv;
      assert(prev.last < curr.first); 
      prev = curr;
      n++;
   }
   assert(NN == n);
   vg_assert( ! VG_(OSetGen_Next)(oset) );
   vg_assert( ! VG_(OSetGen_Next)(oset) );

   // Check that we can remove half of the elements, and that their values
   // are as expected.
   for (i = 0; i < NN; i += 2) {
      a = vs[i]->first;    assert( vs[i] == VG_(OSetGen_Remove)(oset, &a) );
   }

   // Check the size
   vg_assert( NN/2 == VG_(OSetGen_Size)(oset) );

   // Check we can find the remaining elements (with the right values).
   for (i = 1; i < NN; i += 2) {
      a = vs[i]->first + 0;    assert( vs[i] == VG_(OSetGen_Lookup)(oset, &a) );
      a = vs[i]->first + 1;    assert( vs[i] == VG_(OSetGen_Lookup)(oset, &a) );
      a = vs[i]->first + 2;    assert( vs[i] == VG_(OSetGen_Lookup)(oset, &a) );
   }

   // Check we cannot find any of the elements we removed.
   for (i = 0; i < NN; i += 2) {
      a = vs[i]->first + 0;    assert( ! VG_(OSetGen_Contains)(oset, &a) );
      a = vs[i]->first + 1;    assert( ! VG_(OSetGen_Contains)(oset, &a) );
      a = vs[i]->first + 2;    assert( ! VG_(OSetGen_Contains)(oset, &a) );
   }

   // Check that we can remove the remaining half of the elements, and that
   // their values are as expected.
   for (i = 1; i < NN; i += 2) {
      a = vs[i]->first;    assert( vs[i] == VG_(OSetGen_Remove)(oset, &a) );
   }

   // Try some more operations on the empty OSet to ensure they don't screw up.
   vg_assert( ! VG_(OSetGen_Contains)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Lookup)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Remove)(oset, &v) );
   vg_assert( ! VG_(OSetGen_Next)(oset) );
   vg_assert( 0 == VG_(OSetGen_Size)(oset) );

   // Re-insert all elements, to give OSetGen_Destroy something to work with.
   for (i = 0; i < NN; i++) {
      VG_(OSetGen_Insert)(oset, vs[i]);
   }

   // Destroy the OSet
   VG_(OSetGen_Destroy)(oset);
}

//-----------------------------------------------------------------------
// main()
//-----------------------------------------------------------------------

int main(void)
{
   example1();
   example1b();
   example2();
   return 0;
}