// basisu_containers_impl.h
// Do not include directly

#include <ctype.h>

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

namespace basisu
{
	// A container operation has internally panicked in an unrecoverable way.
	// Either an allocation has failed, or a range or consistency check has failed.
#ifdef _MSC_VER
	__declspec(noreturn)
#else
	[[noreturn]] 
#endif
	void container_abort(const char* pMsg, ...)
	{
		assert(0);

		va_list args;
		va_start(args, pMsg);

		char buf[1024] = {};

#ifdef _MSC_VER
		vsprintf_s(buf, sizeof(buf), pMsg, args);
#else
		vsnprintf(buf, sizeof(buf), pMsg, args);
#endif
		va_end(args);

		fputs(buf, stderr);

		std::terminate();
	}

	bool elemental_vector::increase_capacity(size_t min_new_capacity, bool grow_hint, size_t element_size, object_mover pMover, bool nofail_flag)
	{
		assert(m_size <= m_capacity);
		assert(min_new_capacity >= m_size);
		assert(element_size);
		
		// Basic sanity check min_new_capacity
		if (!can_fit_into_size_t((uint64_t)min_new_capacity * element_size))
		{
			assert(0);
			
			if (nofail_flag)
				return false;

			container_abort("elemental_vector::increase_capacity: requesting too many elements\n");
		}

		// Check for sane library limits
		if (sizeof(void*) == sizeof(uint64_t))
		{
			// 16 GB
			assert(min_new_capacity < (0x400000000ULL / element_size));
		}
		else
		{
			// ~1.99 GB
			assert(min_new_capacity < (0x7FFF0000U / element_size));
		}

		// If vector is already large enough just return.
		if (m_capacity >= min_new_capacity)
			return true;

		uint64_t new_capacity_u64 = min_new_capacity;

		if ((grow_hint) && (!helpers::is_power_of_2(new_capacity_u64)))
		{
			new_capacity_u64 = helpers::next_pow2(new_capacity_u64);

			if (!can_fit_into_size_t(new_capacity_u64))
			{
				assert(0);

				if (nofail_flag)
					return false;

				container_abort("elemental_vector::increase_capacity: vector too large\n");
			}
		}

		const uint64_t desired_size_u64 = element_size * new_capacity_u64;

		if (!can_fit_into_size_t(desired_size_u64))
		{
			assert(0);

			if (nofail_flag)
				return false;

			container_abort("elemental_vector::increase_capacity: vector too large\n");
		}

		const size_t desired_size = static_cast<size_t>(desired_size_u64);
						
		size_t actual_size = 0;
		BASISU_NOTE_UNUSED(actual_size);

		if (!pMover)
		{
			void* new_p = realloc(m_p, desired_size);
			if (!new_p)
			{
				fprintf(stderr, "elemental_vector::increase_capacity: Allocation failed!\n");
				assert(0);

				if (nofail_flag)
					return false;

				container_abort("elemental_vector::increase_capacity: realloc() failed allocating %zu bytes", desired_size);
			}

#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)
			{
				fprintf(stderr, "elemental_vector::increase_capacity: Allocation failed!\n");
				assert(0);

				if (nofail_flag)
					return false;

				container_abort("elemental_vector::increase_capacity: malloc() failed allocating %zu bytes", desired_size);
			}

#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 BASISU_VECTOR_DETERMINISTIC
		m_capacity = static_cast<size_t>(new_capacity_u64);
#else
		if (actual_size > desired_size)
			m_capacity = static_cast<size_t>(actual_size / element_size);
		else
			m_capacity = static_cast<size_t>(new_capacity_u64);
#endif

		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)
	{
		container_abort("HASHMAP_TEST_VERIFY() faild on line %i\n", line);
	}

	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)
		{
			if (m_val != UINT64_MAX)
				m_count++;
		}

		counted_obj(counted_obj&& obj) :
			m_val(obj.m_val)
		{
			obj.m_val = UINT64_MAX;
		}

		counted_obj& operator= (counted_obj&& rhs)
		{
			if (this != &rhs)
			{
				m_val = rhs.m_val;
				rhs.m_val = UINT64_MAX;
			}
			return *this;
		}

		~counted_obj()
		{
			if (m_val != UINT64_MAX)
			{
				assert(m_count > 0);
				m_count--;
			}
		}

		static uint32_t m_count;

		uint64_t m_val;

		operator size_t() const { return (size_t)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<uint32_t> s;
			uint_vec k;

			for (uint32_t i = 0; i < 1000000; i++)
			{
				s.insert(i);
				k.push_back(i);
			}
						
			for (uint32_t i = 0; i < k.size(); i++)
			{
				uint32_t r = rand() ^ (rand() << 15);

				uint32_t j = i + (r % (k.size() - i));

				std::swap(k[i], k[j]);
			}

			basisu::hash_map<uint32_t> s1(s);

			for (uint32_t i = 0; i < 1000000; i++)
			{
				auto res = s.find(i);
				HASHMAP_TEST_VERIFY(res != s.end());
				HASHMAP_TEST_VERIFY(res->first == i);
				s.erase(i);
			}

			for (uint32_t it = 0; it < 1000000; it++)
			{
				uint32_t i = k[it];

				auto res = s1.find(i);
				HASHMAP_TEST_VERIFY(res != s.end());
				HASHMAP_TEST_VERIFY(res->first == i);
				s1.erase(i);
			}

			for (uint32_t i = 0; i < 1000000; i++)
			{
				auto res = s.find(i);
				HASHMAP_TEST_VERIFY(res == s.end());

				auto res1 = s1.find(i);
				HASHMAP_TEST_VERIFY(res1 == s1.end());
			}

			HASHMAP_TEST_VERIFY(s.empty());
			HASHMAP_TEST_VERIFY(s1.empty());
		}

		{
			typedef basisu::hash_map< uint32_t, basisu::vector<uint32_t> > hm;
			hm q;
			
			basisu::vector<uint32_t> a, b;
			a.push_back(1);
			b.push_back(2);
			b.push_back(3);

			basisu::vector<uint32_t> c(b);

			hm::insert_result ir;
			q.try_insert(ir, 1, std::move(a));
			q.try_insert(ir, 2, std::move(b));
			q.try_insert(ir, std::make_pair(3, c));
		}

		{
			typedef basisu::hash_map<counted_obj, counted_obj> my_hash_map;
			my_hash_map m;
			counted_obj a, b;
			m.insert(std::move(a), std::move(b));
		}

		{
			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(1, 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_u32() + 1);
				for (uint32_t i = 0; i < nd; i++)
				{
					uint32_t p = irand32(0, q.size_u32());

					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

	// String formatting

	bool fmt_variant::to_string(std::string& res, std::string& fmt) const
	{
		res.resize(0);

		// Scan for allowed formatting characters.
		for (size_t i = 0; i < fmt.size(); i++)
		{
			const char c = fmt[i];

			if (isdigit(c) || (c == '.') || (c == ' ') || (c == '#') || (c == '+') || (c == '-'))
				continue;

			if (isalpha(c))
			{
				if ((i + 1) == fmt.size())
					continue;
			}

			return false;
		}

		if (fmt.size() && (fmt.back() == 'c'))
		{
			if ((m_type == variant_type::cI32) || (m_type == variant_type::cU32))
			{
				if (m_u32 > 255)
					return false;

				// Explictly allowing caller to pass in a char of 0, which is ignored.
				if (m_u32)
					res.push_back((uint8_t)m_u32);
				return true;
			}
			else
				return false;
		}

		switch (m_type)
		{
		case variant_type::cInvalid:
		{
			return false;
		}
		case variant_type::cI32:
		{
			if (fmt.size())
			{
				int e = fmt.back();
				if (isalpha(e))
				{
					if ((e != 'x') && (e != 'X') && (e != 'i') && (e != 'd') && (e != 'u'))
						return false;
				}
				else
				{
					fmt += "i";
				}

				res = string_format((std::string("%") + fmt).c_str(), m_i32);
			}
			else
			{
				res = string_format("%i", m_i32);
			}
			break;
		}
		case variant_type::cU32:
		{
			if (fmt.size())
			{
				int e = fmt.back();
				if (isalpha(e))
				{
					if ((e != 'x') && (e != 'X') && (e != 'i') && (e != 'd') && (e != 'u'))
						return false;
				}
				else
				{
					fmt += "u";
				}

				res = string_format((std::string("%") + fmt).c_str(), m_u32);
			}
			else
			{
				res = string_format("%u", m_u32);
			}
			break;
		}
		case variant_type::cI64:
		{
			if (fmt.size())
			{
				int e = fmt.back();
				if (isalpha(e))
				{
					if (e == 'x')
					{
						fmt.pop_back();
						fmt += PRIx64;
					}
					else if (e == 'X')
					{
						fmt.pop_back();
						fmt += PRIX64;
					}
					else
						return false;
				}
				else
				{
					fmt += PRId64;
				}

				res = string_format((std::string("%") + fmt).c_str(), m_i64);
			}
			else
			{
				res = string_format("%" PRId64, m_i64);
			}
			break;
		}
		case variant_type::cU64:
		{
			if (fmt.size())
			{
				int e = fmt.back();
				if (isalpha(e))
				{
					if (e == 'x')
					{
						fmt.pop_back();
						fmt += PRIx64;
					}
					else if (e == 'X')
					{
						fmt.pop_back();
						fmt += PRIX64;
					}
					else
						return false;
				}
				else
				{
					fmt += PRIu64;
				}

				res = string_format((std::string("%") + fmt).c_str(), m_u64);
			}
			else
			{
				res = string_format("%" PRIu64, m_u64);
			}
			break;
		}
		case variant_type::cFlt:
		{
			if (fmt.size())
			{
				int e = fmt.back();
				if (isalpha(e))
				{
					if ((e != 'f') && (e != 'g') && (e != 'e') && (e != 'E'))
						return false;
				}
				else
				{
					fmt += "f";
				}

				res = string_format((std::string("%") + fmt).c_str(), m_flt);
			}
			else
			{
				res = string_format("%f", m_flt);
			}
			break;
		}
		case variant_type::cDbl:
		{
			if (fmt.size())
			{
				int e = fmt.back();
				if (isalpha(e))
				{
					if ((e != 'f') && (e != 'g') && (e != 'e') && (e != 'E'))
						return false;
				}
				else
				{
					fmt += "f";
				}

				res = string_format((std::string("%") + fmt).c_str(), m_dbl);
			}
			else
			{
				res = string_format("%f", m_dbl);
			}
			break;
		}
		case variant_type::cStrPtr:
		{
			if (fmt.size())
				return false;
			if (!m_pStr)
				return false;
			res = m_pStr;
			break;
		}
		case variant_type::cBool:
		{
			if (fmt.size())
				return false;
			res = m_bool ? "true" : "false";
			break;
		}
		case variant_type::cStdStr:
		{
			if (fmt.size())
				return false;
			res = m_str;
			break;
		}
		default:
		{
			return false;
		}
		}

		return true;
	}

	bool fmt_variants(std::string& res, const char* pFmt, const fmt_variant_vec& variants)
	{
		res.resize(0);

		// Must specify a format string
		if (!pFmt)
		{
			assert(0);
			return false;
		}

		// Check format string's length
		const size_t fmt_len = strlen(pFmt);
		if (!fmt_len)
		{
			if (variants.size())
			{
				assert(0);
				return false;
			}
			return true;
		}

		// Wildly estimate output length
		res.reserve(fmt_len + 32);

		std::string var_fmt;
		var_fmt.reserve(16);

		std::string tmp;
		tmp.reserve(16);

		size_t variant_index = 0;
		bool inside_brackets = false;
		const char* p = pFmt;

		while (*p)
		{
			const uint8_t c = *p++;

			if (inside_brackets)
			{
				if (c == '}')
				{
					inside_brackets = false;

					if (variant_index >= variants.size())
					{
						assert(0);
						return false;
					}

					if (!variants[variant_index].to_string(tmp, var_fmt))
					{
						assert(0);
						return false;
					}

					res += tmp;

					variant_index++;
				}
				else
				{
					// Check for forbidden formatting characters.
					if ((c == '*') || (c == 'n') || (c == '%'))
					{
						assert(0);
						return false;
					}

					var_fmt.push_back(c);
				}
			}
			else if (c == '{')
			{
				// Check for escaped '{'
				if (*p == '{')
				{
					res.push_back((char)c);
					p++;
				}
				else
				{
					inside_brackets = true;
					var_fmt.resize(0);
				}
			}
			else
			{
				res.push_back((char)c);
			}
		}

		if (inside_brackets)
		{
			assert(0);
			return false;
		}

		if (variant_index != variants.size())
		{
			assert(0);
			return false;
		}

		return true;
	}

} // namespace basisu
