/* Copyright (C) 2012 Wildfire Games.
 * This file is part of 0 A.D.
 *
 * 0 A.D. is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 0 A.D. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "precompiled.h"

#include "CStrIntern.h"

#include "lib/fnv_hash.h"
#include "ps/CLogger.h"

#include <boost/unordered_map.hpp>

class CStrInternInternals
{
public:
	CStrInternInternals(const char* str, size_t len)
		: data(str, str+len), hash(fnv_hash(str, len))
	{
// 		LOGWARNING(L"New interned string '%hs'", data.c_str());
	}

	bool operator==(const CStrInternInternals& b) const
	{
		// Compare hash first for quick rejection of inequal strings
		return (hash == b.hash && data == b.data);
	}

	const std::string data;
	const u32 hash; // fnv_hash of data

private:
	CStrInternInternals& operator=(const CStrInternInternals&);
};

// Interned strings are stored in a hash table, indexed by string:

typedef std::string StringsKey;

struct StringsKeyHash
{
	size_t operator()(const StringsKey& key) const
	{
		return fnv_hash(key.c_str(), key.length());
	}
};

// To avoid std::string memory allocations when GetString does lookups in the
// hash table of interned strings, we make use of boost::unordered_map's ability
// to do lookups with a functionally equivalent proxy object:

struct StringsKeyProxy
{
	const char* str;
	size_t len;
};

struct StringsKeyProxyHash
{
	size_t operator()(const StringsKeyProxy& key) const
	{
		return fnv_hash(key.str, key.len);
	}
};

struct StringsKeyProxyEq
{
	bool operator()(const StringsKeyProxy& proxy, const StringsKey& key) const
	{
		return (proxy.len == key.length() && memcmp(proxy.str, key.c_str(), proxy.len) == 0);
	}
};

static boost::unordered_map<StringsKey, shared_ptr<CStrInternInternals>, StringsKeyHash> g_Strings;


static CStrInternInternals* GetString(const char* str, size_t len)
{
	// g_Strings is not thread-safe, so complain if anyone is using this
	// type in non-main threads. (If that's desired, g_Strings should be changed
	// to be thread-safe, preferably without sacrificing performance.)
	ENSURE(ThreadUtil::IsMainThread());

#if BOOST_VERSION >= 104200
	StringsKeyProxy proxy = { str, len };
	boost::unordered_map<StringsKey, shared_ptr<CStrInternInternals> >::iterator it =
		g_Strings.find(proxy, StringsKeyProxyHash(), StringsKeyProxyEq());
#else
	// Boost <= 1.41 doesn't support the new find(), so do a slightly less efficient lookup
	boost::unordered_map<StringsKey, shared_ptr<CStrInternInternals> >::iterator it =
		g_Strings.find(str);
#endif

	if (it != g_Strings.end())
		return it->second.get();

	shared_ptr<CStrInternInternals> internals(new CStrInternInternals(str, len));
	g_Strings.insert(std::make_pair(internals->data, internals));
	return internals.get();
}

CStrIntern::CStrIntern()
{
	m = GetString("", 0);
}

CStrIntern::CStrIntern(const char* str)
{
	m = GetString(str, strlen(str));
}

CStrIntern::CStrIntern(const std::string& str)
{
	m = GetString(str.c_str(), str.length());
}

u32 CStrIntern::GetHash() const
{
	return m->hash;
}

const char* CStrIntern::c_str() const
{
	return m->data.c_str();
}

size_t CStrIntern::length() const
{
	return m->data.length();
}

const std::string& CStrIntern::string() const
{
	return m->data;
}
