///////////////////////////////////////////////////////////////////////////////
// Unit Test for Loki
//
// Copyright Terje Slettebų and Pavel Vozenilek 2002.
//
// Permission to use, copy, modify, and distribute this software for any
// purpose is hereby granted without fee, provided that this copyright and
// permissions notice appear in all copies and derivatives.
//
// This software is provided "as is" without express or implied warranty.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef ASSOCVECTORTEST_H
#define ASSOCVECTORTEST_H

// $Id: AssocVectorTest.h 897 2008-08-08 22:53:19Z syntheticpp $


#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <loki/AssocVector.h>
#include "UnitTest.h"

#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__MWERKS__)
 #define C_Namespace
#else
 #define C_Namespace std
#endif

///////////////////////////////////////////////////////////////////////////////
// AssocVectorTest
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// STL compatible allocator
///////////////////////////////////////////////////////////////////////////////

template <class T> class TestAllocator : public std::allocator<T>
{
public:
  typedef T                      value_type;
  typedef value_type*            pointer;
  typedef const value_type*      const_pointer;
  typedef value_type&            reference;
  typedef const value_type&      const_reference;
  typedef C_Namespace::size_t    size_type;
  typedef C_Namespace::ptrdiff_t difference_type;

  template <class U>
  struct rebind { typedef TestAllocator<U> other; };

  TestAllocator() {}
  TestAllocator(const TestAllocator&) {}

#if !(defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__MWERKS__))

  template <class U>
  TestAllocator(const TestAllocator<U>&) {}

#endif

  ~TestAllocator() {}

  pointer address(reference x) const { return &x; }
  const_pointer address(const_reference x) const {
    return x;
  }

  pointer allocate(size_type n, const_pointer = 0) {
    return static_cast<pointer>(::operator new(n * sizeof(T)));
  }

  void deallocate(pointer p, size_type size) {
    ::operator delete(p);
  }

  size_type max_size() const {
    return static_cast<size_type>(-1) / sizeof(T);
  }

  void construct(pointer p, const value_type& x) {
    new(p) value_type(x);
  }
  void destroy(pointer p) {
#ifndef _USE_OLD_RW_STL  // ?? failed to compile when RogueWave is enabled !?!
    p->~value_type();
#endif
  }

private:
  void operator=(const TestAllocator&);
};

///////////////////////////////////////////////////////////////////////////////
// AVTestClass
///////////////////////////////////////////////////////////////////////////////

class AVTestClass
{
public:
    AVTestClass(int value) : value_(value) {}
    AVTestClass(const AVTestClass& other) : value_(other.value_) {}
    AVTestClass& operator=(const AVTestClass& other) {
        value_ = other.value_;
        return *this;
    }

    int value_;
};

bool operator<(const AVTestClass& lhs, const AVTestClass& rhs)
{
    return lhs.value_ < rhs.value_;
}

///////////////////////////////////////////////////////////////////////////////
// str_less
///////////////////////////////////////////////////////////////////////////////

struct str_less : public std::binary_function<const char*, const char*, bool> {
    bool operator()(const char* x, const char* y) const {
        return strcmp(x, y) < 0;
    }
};

///////////////////////////////////////////////////////////////////////////////
// int_less
///////////////////////////////////////////////////////////////////////////////

unsigned int_test_count = 0; // to verify usage
struct int_less : public std::less<int>
{
    bool operator()(int x, int y) const {
        ++int_test_count;
        return x < y;
    }
};

///////////////////////////////////////////////////////////////////////////////
// is_sorted
///////////////////////////////////////////////////////////////////////////////

template<class Vect>
bool is_sorted(const Vect& v) {
    if (v.size() < 2) return true;
    typename Vect::const_iterator it = v.begin();
    typename Vect::key_type previous = it->first;
    ++it;
    while (it != v.end()) {
        typename Vect::key_type current = it->first;
        if (!v.key_comp()(previous, current)) return false;
        previous = current;
        ++it;
    }
    return true;
}

///////////////////////////////////////////////////////////////////////////////

typedef Loki::AssocVector<int, int, std::less<int>, TestAllocator<std::pair<int, int> > > test_vect1_t;
typedef Loki::AssocVector<const char*, int, str_less, TestAllocator<std::pair<const char*, int> > > test_vect2_t;
typedef Loki::AssocVector<std::string, std::string> test_vect3_t;
typedef Loki::AssocVector<AVTestClass, AVTestClass, std::less<AVTestClass>, TestAllocator<std::pair<AVTestClass, AVTestClass> > > test_vect4_t;
typedef Loki::AssocVector<int, const char*> test_vect5_t;

///////////////////////////////////////////////////////////////////////////////

void check_insert1(test_vect1_t& v)
{
    C_Namespace::srand(10);
    for (unsigned i = 0; i < 100; ++i) {
        int x = C_Namespace::rand();
        v.insert(std::make_pair(x, x));
        assert(is_sorted(v));
    }
    assert(v.size() == 100);
}

///////////////////////////////////////////////////////////////////////////////
// check_swap
///////////////////////////////////////////////////////////////////////////////

template <class Vect>
void check_swap(Vect& v1, Vect& v2)
{
    size_t size1 = v1.size(); (void) size1;
    size_t size2 = v2.size(); (void) size2;
    v1.swap(v2);
    assert(v1.size() == size2);
    assert(v2.size() == size1);
}

///////////////////////////////////////////////////////////////////////////////
// test_vect1
///////////////////////////////////////////////////////////////////////////////

void test_vect1()
{
    test_vect1_t vec11;
    assert(vec11.size() == 0);

    check_insert1(vec11);
    size_t size1 = vec11.size();
    (void) size1;
    assert(size1);

    test_vect1_t vec12(vec11.begin(), vec11.end());
    assert(vec12.size() == vec11.size());
    assert(vec11.size() == size1);

    test_vect1_t vec13;
    vec13 = vec11;
    assert(vec13.size() == vec11.size());
    assert(vec11.size() == size1);

    // this doesn't compile on Borland C++ 6.0 !?!
    //test_vect1_t vec99(test_vect1_t::key_compare(), TestAllocator<test_vect1_t::value_type>());
    //assert(vec99.size() == 0); //- here ir cries

    test_vect1_t::key_compare comp = test_vect1_t::key_compare();
    test_vect1_t vec14(comp, TestAllocator<test_vect1_t::value_type>());
    assert(vec14.size() == 0);

    check_swap(vec11, vec14);
    assert(vec14.size() == size1);
    assert(vec11.size() == 0);

    // this compiles, unlike object on stack
    test_vect1_t* vec15 = new test_vect1_t(test_vect1_t::key_compare(), TestAllocator<test_vect1_t::value_type>());
    assert(vec15->size() == 0);
    check_insert1(*vec15);
    delete vec15;

    // different comparator test - doesn't work for Loki
    //comp = int_less();
    //test_vect1_t vec16(comp);
    //assert(vec16.size() == 0);
    //assert(int_test_count == 0);
    //check_insert1(vec16);
    //assert(int_test_count != 0);
}

///////////////////////////////////////////////////////////////////////////////
// test_vect2
///////////////////////////////////////////////////////////////////////////////

void test_vect2()
{
    test_vect2_t vec21;
    vec21.insert(std::make_pair("abc", 1));
    vec21.insert(std::make_pair("xyz", 3));
    vec21.insert(std::make_pair("def", 2));
    assert(vec21.size() == 3);
    assert(is_sorted(vec21));

    test_vect2_t::iterator it = vec21.find("xyz");
    assert(it != vec21.end());
    assert(it->second == 3);

    std::pair<test_vect2_t::iterator, bool> aux = vec21.insert(std::make_pair("xyz", 99));
    assert(!aux.second);
    assert(aux.first->first == "xyz");
    assert(aux.first->second == 3);

    it = vec21.find("xyz");
    assert(it != vec21.end());
    assert(it->second == 3);

    vec21.erase(it);
    assert(vec21.size() == 2);
    it = vec21.find("xyz");
    assert(it == vec21.end());
    vec21.erase("xyz");
    vec21.erase("abc");
    assert(vec21.size() == 1);

    vec21.clear();
    assert(vec21.size() == 0);
}

///////////////////////////////////////////////////////////////////////////////
// test_vect3
///////////////////////////////////////////////////////////////////////////////

void test_vect3()
{
    // To use string, you need:
    // 1) enable _STLP_USE_NEWALLOC in include\stl\_site_config.h
    // 2) either disable use of any dynamic RTL (menu Project | Options | Linker)
    // 3) or recompile STLPort DLL.
    // This all is related to bug in STLPort allocator.
    test_vect3_t vec31;
    C_Namespace::srand(111);
    // a stress test
    for (unsigned i = 0; i < 2 * 1000; ++i) {
        char buffer[17];
        C_Namespace::sprintf(buffer, "string%d", C_Namespace::rand());
        std::string s(buffer);
        vec31.insert(std::make_pair(s, s));
    }
}

///////////////////////////////////////////////////////////////////////////////
// test_vect4
///////////////////////////////////////////////////////////////////////////////

void test_vect4()
{
    test_vect4_t vec41;
    for (int i = 0; i < 100; ++i) {
        vec41.insert(std::make_pair(AVTestClass(i), AVTestClass(i)));
    }
    assert(vec41.size() == 100);

    vec41.insert(std::make_pair(AVTestClass(300), AVTestClass(300)));
    vec41.insert(std::make_pair(AVTestClass(200), AVTestClass(200)));
    assert(vec41.size() == 102);

    test_vect4_t::iterator it = vec41.find(AVTestClass(22));
    assert(it != vec41.end());
    assert(it->second.value_ == 22);

    test_vect4_t vec42;
    vec42.swap(vec41);
    assert(vec41.size() == 0);
    assert(vec42.size() == 102);

    vec42.erase(vec42.begin(), vec42.end());
    assert(vec42.size() == 0);
}

///////////////////////////////////////////////////////////////////////////////
// test_vect5
///////////////////////////////////////////////////////////////////////////////

void test_vect5()
{
    test_vect5_t vec51;
    vec51.insert(test_vect5_t::value_type(3, "XXX"));
    vec51.insert(std::make_pair(1, "X"));
    vec51.insert(std::make_pair(2, "XX"));

    test_vect5_t::const_iterator it = vec51.begin();
    std::string::size_type count=1;

    while (it != vec51.end()) {
        assert(std::string(it->second).length()==count);

        ++count;

        it++; // intentionally
    }
}

///////////////////////////////////////////////////////////////////////////////
// test_vect6
///////////////////////////////////////////////////////////////////////////////

void test_vect6()
{
    srand( static_cast<unsigned int>(time(NULL)) );
    typedef Loki::AssocVector<int, int> IntMap;

    const unsigned int numTests = 20;
    const unsigned int numSamples = 50;
    for( unsigned int i = 0; i < numTests; ++i )
    {
        IntMap testVec;
        for( unsigned int i = 0; i < numSamples; ++i )
        {
            std::pair<int, int> newValue(
                rand() % numSamples,
                rand() % numSamples );

            IntMap backupVec( testVec );

            // assuming the basic insert operator is correct:
            testVec.insert( newValue );

            // test the boundary cases:
            {
                IntMap newVec( backupVec );
                newVec.insert( newVec.begin(), newValue );
                assert( newVec == testVec );

                // check to make sure repeated inserts do not
                //   break anything
                newVec.insert( newVec.begin(), newValue );
                newVec.insert( newVec.end(), newValue );
                newVec.insert( newVec.begin() + newVec.size()/2, newValue );
                newVec.insert( newVec.begin() + rand() % newVec.size(), newValue );
                assert( newVec == testVec );
            }

            {
                IntMap newVec( backupVec );
                newVec.insert( newVec.end(), newValue );
                assert( newVec == testVec );
            }

            // try the middle:
            {
                IntMap newVec( backupVec );
                IntMap::iterator itr = newVec.begin();
                std::advance( itr, newVec.size()/2 );
                newVec.insert( itr, newValue );
                assert( newVec == testVec );
            }

            // try a random spot:
            if( testVec.size() >= 2 )
            {
                IntMap newVec( backupVec );
                IntMap::iterator itr = newVec.begin();
                std::advance( itr, rand() % (testVec.size()-1) + 1 );
                newVec.insert( itr, newValue );
                assert( newVec == testVec );
            }
        }
    }
}




class AssocVectorTest : public Test
{
public:
  AssocVectorTest() : Test("AssocVector.h") {}

  virtual void execute(TestResult &result)
    {
    printName(result);

    test_vect1();
    test_vect2();
    test_vect3();
    test_vect4();
    test_vect5();
    test_vect6();

    testAssert("AssocVector",true,result),

    std::cout << '\n';
    }
} assocVectorTest;

#endif
