// basisu_containers_impl.h
// Do not include directly

#ifdef _MSC_VER
#pragma warning (disable:4127) // warning C4127: conditional expression is constant
#endif

namespace basisu
{
   bool elemental_vector::increase_capacity(uint32_t min_new_capacity, bool grow_hint, uint32_t element_size, object_mover pMover, bool nofail)
   {
      assert(m_size <= m_capacity);

      if (sizeof(void *) == sizeof(uint64_t))
         assert(min_new_capacity < (0x400000000ULL / element_size));
      else
         assert(min_new_capacity < (0x7FFF0000U / element_size));

      if (m_capacity >= min_new_capacity)
         return true;

      size_t new_capacity = min_new_capacity;
      if ((grow_hint) && (!helpers::is_power_of_2((uint64_t)new_capacity)))
      {
         new_capacity = (size_t)helpers::next_pow2((uint64_t)new_capacity);

         assert(new_capacity && (new_capacity > m_capacity));

         if (new_capacity < min_new_capacity)
         {
            if (nofail)
               return false;
            fprintf(stderr, "vector too large\n");
            abort();
         }
      }
            
      const size_t desired_size = element_size * new_capacity;
      size_t actual_size = 0;
      if (!pMover)
      {
         void* new_p = realloc(m_p, desired_size);
         if (!new_p)
         {
            if (nofail)
               return false;

            char buf[256];
#ifdef _MSC_VER
            sprintf_s(buf, sizeof(buf), "vector: realloc() failed allocating %u bytes", (uint32_t)desired_size);
#else
            snprintf(buf, sizeof(buf), "vector: realloc() failed allocating %u bytes", (uint32_t)desired_size);
#endif
            fprintf(stderr, "%s", buf);
            abort();
         }

#if BASISU_VECTOR_DETERMINISTIC
         actual_size = desired_size;
#elif defined(_MSC_VER)
         actual_size = _msize(new_p);
#elif HAS_MALLOC_USABLE_SIZE
         actual_size = malloc_usable_size(new_p);
#else
         actual_size = desired_size;
#endif
         m_p = new_p;
      }
      else
      {
         void* new_p = malloc(desired_size);
         if (!new_p)
         {
            if (nofail)
               return false;

            char buf[256];
#ifdef _MSC_VER
            sprintf_s(buf, sizeof(buf), "vector: malloc() failed allocating %u bytes", (uint32_t)desired_size);
#else
            snprintf(buf, sizeof(buf), "vector: malloc() failed allocating %u bytes", (uint32_t)desired_size);
#endif
            fprintf(stderr, "%s", buf);
            abort();
         }

#if BASISU_VECTOR_DETERMINISTIC
         actual_size = desired_size;
#elif defined(_MSC_VER)
         actual_size = _msize(new_p);
#elif HAS_MALLOC_USABLE_SIZE
         actual_size = malloc_usable_size(new_p);
#else
         actual_size = desired_size;
#endif

         (*pMover)(new_p, m_p, m_size);

         if (m_p)
            free(m_p);
         
         m_p = new_p;
      }

      if (actual_size > desired_size)
         m_capacity = static_cast<uint32_t>(actual_size / element_size);
      else
         m_capacity = static_cast<uint32_t>(new_capacity);

      return true;
   }

#if BASISU_HASHMAP_TEST

#define HASHMAP_TEST_VERIFY(c) do { if (!(c)) handle_hashmap_test_verify_failure(__LINE__); } while(0)

   static void handle_hashmap_test_verify_failure(int line)
   {
      fprintf(stderr, "HASHMAP_TEST_VERIFY() faild on line %i\n", line);
      abort();
   }

   class counted_obj
   {
   public:
      counted_obj(uint32_t v = 0) :
         m_val(v)
      {
         m_count++;
      }

      counted_obj(const counted_obj& obj) :
         m_val(obj.m_val)
      {
         m_count++;
      }

      ~counted_obj()
      {
         assert(m_count > 0);
         m_count--;
      }

      static uint32_t m_count;

      uint32_t m_val;

      operator size_t() const { return m_val; }

      bool operator== (const counted_obj& rhs) const { return m_val == rhs.m_val; }
      bool operator== (const uint32_t rhs) const { return m_val == rhs; }

   };

   uint32_t counted_obj::m_count;

   static uint32_t urand32()
   {
      uint32_t a = rand();
      uint32_t b = rand() << 15;
      uint32_t c = rand() << (32 - 15);
      return a ^ b ^ c;
   }

   static int irand32(int l, int h)
   {
      assert(l < h);
      if (l >= h)
         return l;

      uint32_t range = static_cast<uint32_t>(h - l);

      uint32_t rnd = urand32();

      uint32_t rnd_range = static_cast<uint32_t>((((uint64_t)range) * ((uint64_t)rnd)) >> 32U);

      int result = l + rnd_range;
      assert((result >= l) && (result < h));
      return result;
   }

   void hash_map_test()
   {
      {
         basisu::hash_map<uint64_t, uint64_t> k;
         basisu::hash_map<uint64_t, uint64_t> l;
         std::swap(k, l);

         k.begin();
         k.end();
         k.clear();
         k.empty();
         k.erase(0);
         k.insert(0, 1);
         k.find(0);
         k.get_equals();
         k.get_hasher();
         k.get_table_size();
         k.reset();
         k.reserve(1);
         k = l;
         k.set_equals(l.get_equals());
         k.set_hasher(l.get_hasher());
         k.get_table_size();
      }

      uint32_t seed = 0;
      for (; ; )
      {
         seed++;

         typedef basisu::hash_map<counted_obj, counted_obj> my_hash_map;
         my_hash_map m;

         const uint32_t n = irand32(0, 100000);

         printf("%u\n", n);

         srand(seed); // r1.seed(seed);

         basisu::vector<int> q;

         uint32_t count = 0;
         for (uint32_t i = 0; i < n; i++)
         {
            uint32_t v = urand32() & 0x7FFFFFFF;
            my_hash_map::insert_result res = m.insert(counted_obj(v), counted_obj(v ^ 0xdeadbeef));
            if (res.second)
            {
               count++;
               q.push_back(v);
            }
         }

         HASHMAP_TEST_VERIFY(m.size() == count);

         srand(seed);

         my_hash_map cm(m);
         m.clear();
         m = cm;
         cm.reset();

         for (uint32_t i = 0; i < n; i++)
         {
            uint32_t v = urand32() & 0x7FFFFFFF;
            my_hash_map::const_iterator it = m.find(counted_obj(v));
            HASHMAP_TEST_VERIFY(it != m.end());
            HASHMAP_TEST_VERIFY(it->first == v);
            HASHMAP_TEST_VERIFY(it->second == (v ^ 0xdeadbeef));
         }

         for (uint32_t t = 0; t < 2; t++)
         {
            const uint32_t nd = irand32(1, q.size() + 1);
            for (uint32_t i = 0; i < nd; i++)
            {
               uint32_t p = irand32(0, q.size());

               int k = q[p];
               if (k >= 0)
               {
                  q[p] = -k - 1;

                  bool s = m.erase(counted_obj(k));
                  HASHMAP_TEST_VERIFY(s);
               }
            }

            typedef basisu::hash_map<uint32_t, empty_type> uint_hash_set;
            uint_hash_set s;

            for (uint32_t i = 0; i < q.size(); i++)
            {
               int v = q[i];

               if (v >= 0)
               {
                  my_hash_map::const_iterator it = m.find(counted_obj(v));
                  HASHMAP_TEST_VERIFY(it != m.end());
                  HASHMAP_TEST_VERIFY(it->first == (uint32_t)v);
                  HASHMAP_TEST_VERIFY(it->second == ((uint32_t)v ^ 0xdeadbeef));

                  s.insert(v);
               }
               else
               {
                  my_hash_map::const_iterator it = m.find(counted_obj(-v - 1));
                  HASHMAP_TEST_VERIFY(it == m.end());
               }
            }

            uint32_t found_count = 0;
            for (my_hash_map::const_iterator it = m.begin(); it != m.end(); ++it)
            {
               HASHMAP_TEST_VERIFY(it->second == ((uint32_t)it->first ^ 0xdeadbeef));

               uint_hash_set::const_iterator fit(s.find((uint32_t)it->first));
               HASHMAP_TEST_VERIFY(fit != s.end());

               HASHMAP_TEST_VERIFY(fit->first == it->first);

               found_count++;
            }

            HASHMAP_TEST_VERIFY(found_count == s.size());
         }

         HASHMAP_TEST_VERIFY(counted_obj::m_count == m.size() * 2);
      }
   }

#endif // BASISU_HASHMAP_TEST

} // namespace basisu
