/*
   Copyright (C) 2001-2006, William Joseph.
   All Rights Reserved.

   This file is part of GtkRadiant.

   GtkRadiant 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.

   GtkRadiant 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 GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#if !defined( INCLUDED_UNIQUENAMES_H )
#define INCLUDED_UNIQUENAMES_H

#include "debugging/debugging.h"
#include <map>
#include "string/string.h"
#include "generic/static.h"

#if 1
class Postfix
{
unsigned int m_value;
public:
Postfix( const char* postfix ) : m_value( atoi( postfix ) ){
}
unsigned int number() const {
	return m_value;
}
void write( char* buffer ) const {
	sprintf( buffer, "%u", m_value );
}
Postfix& operator++(){
	++m_value;
	return *this;
}
bool operator<( const Postfix& other ) const {
	return m_value < other.m_value;
}
bool operator==( const Postfix& other ) const {
	return m_value == other.m_value;
}
bool operator!=( const Postfix& other ) const {
	return !operator==( other );
}
};

#else
class Postfix
{
std::pair<unsigned int, unsigned int> m_value;
public:
Postfix( unsigned int number, unsigned int leading_zeros )
	: m_value( leading_zeros, number ){
}
Postfix( const char* postfix )
	: m_value( number_count_leading_zeros( postfix ), atoi( postfix ) ){
}
unsigned int number() const {
	return m_value.second;
}
unsigned int leading_zeros() const {
	return m_value.first;
}
void write( char* buffer ){
	for ( unsigned int count = 0; count < m_value.first; ++count, ++buffer )
		*buffer = '0';
	sprintf( buffer, "%u", m_value.second );
}
Postfix& operator++(){
	++m_value.second;
	if ( m_value.first != 0 && m_value.second % 10 == 0 ) {
		--m_value.first;
	}
	return *this;
}
bool operator<( const Postfix& other ) const {
	return m_value < other.m_value;
}
bool operator==( const Postfix& other ) const {
	return m_value == other.m_value;
}
bool operator!=( const Postfix& other ) const {
	return !operator==( other );
}
};

#endif

typedef std::pair<CopiedString, Postfix> name_t;

inline void name_write( char* buffer, name_t name ){
	strcpy( buffer, name.first.c_str() );
	name.second.write( buffer + strlen( name.first.c_str() ) );
}

inline name_t name_read( const char* name ){
	const char* end = name + strlen( name );

#if GDEF_OS_MACOS
	/* HACK: Apple shipped a clang built for macOS with an optimization enabled
	that is not available on macOS. This compiler error may then be faced:

	  ld: Undefined symbols:
	    _memrchr, referenced from:
	        name_read(char const*) in map.cpp.o

	This is a compiler error:

	> On Mac OSX (macOS version 12.4, sdk version 12.1) llvm can replace call
	> to strrchr() with call to memrchr() when string length is known at
	> compile time. This results in link error, because memrchr is not present
	> in libSystem.
	> -- https://github.com/llvm/llvm-project/issues/62254

	We workaround this by making the string length not known at build time
	on macOS to avoid triggering the unavailable compiler optimization. */

	const char* volatile numbers = "1234567890";
#else
	const char* numbers = "1234567890";
#endif

	for ( const char* p = end; end != name; --p )
	{
		if ( strrchr( numbers, *p ) == NULL ) {
			break;
		}
		end = p;
	}

	return name_t( CopiedString( StringRange( name, end ) ), Postfix( end ) );
}


class PostFixes
{
public:
typedef std::map<Postfix, unsigned int> postfixes_t;
postfixes_t m_postfixes;

private:
Postfix find_first_empty() const {
	Postfix postfix( "1" );
	for ( postfixes_t::const_iterator i = m_postfixes.find( postfix ); i != m_postfixes.end(); ++i, ++postfix )
	{
		if ( ( *i ).first != postfix ) {
			break;
		}
	}
	return postfix;
}

public:
Postfix make_unique( Postfix postfix ) const {
	postfixes_t::const_iterator i = m_postfixes.find( postfix );
	if ( i == m_postfixes.end() ) {
		return postfix;
	}
	else
	{
		return find_first_empty();
	}
}

void insert( Postfix postfix ){
	postfixes_t::iterator i = m_postfixes.find( postfix );
	if ( i == m_postfixes.end() ) {
		m_postfixes.insert( postfixes_t::value_type( postfix, 1 ) );
	}
	else
	{
		++( *i ).second;
	}
}

void erase( Postfix postfix ){
	postfixes_t::iterator i = m_postfixes.find( postfix );
	if ( i == m_postfixes.end() ) {
		// error
	}
	else
	{
		if ( --( *i ).second == 0 ) {
			m_postfixes.erase( i );
		}
	}
}

bool empty() const {
	return m_postfixes.empty();
}
};


class UniqueNames
{
typedef std::map<CopiedString, PostFixes> names_t;
names_t m_names;
public:
name_t make_unique( const name_t& name ) const {
	char buf[80];
	name_t r( "","" );
	name_write( buf, name );
	#ifdef _DEBUG
	globalErrorStream() << "find unique name for " << buf << "\n";
	globalErrorStream() << "> currently registered names:\n";
	#endif
	for ( names_t::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
	{
		#ifdef _DEBUG
		globalErrorStream() << ">> " << i->first.c_str() << ": ";
		#endif
		for ( PostFixes::postfixes_t::const_iterator j = i->second.m_postfixes.begin(); j != i->second.m_postfixes.end(); ++j )
		{
			j->first.write( buf );
			#ifdef _DEBUG
			globalErrorStream() << " '" << buf << "'";
			#endif
		}
		#ifdef _DEBUG
		globalErrorStream() << "\n";
		#endif
	}
	names_t::const_iterator i = m_names.find( name.first );
	if ( i == m_names.end() ) {
		r = name;
	}
	else
	{
		r = name_t( name.first, ( *i ).second.make_unique( name.second ) );
	}
	name_write( buf, r );
	#ifdef _DEBUG
	globalErrorStream() << "> unique name is " << buf << "\n";
	#endif
	return r;
}

void insert( const name_t& name ){
	m_names[name.first].insert( name.second );
}

void erase( const name_t& name ){
	names_t::iterator i = m_names.find( name.first );
	if ( i == m_names.end() ) {
		ASSERT_MESSAGE( true, "erase: name not found" );
	}
	else
	{
		( *i ).second.erase( name.second );
		if ( ( *i ).second.empty() ) {
			m_names.erase( i );
		}
	}
}

bool empty() const {
	return m_names.empty();
}
};



#if 0

#undef ERROR_MESSAGE
#define ERROR_MESSAGE( message )

class TestUniqueName
{
void name_check_equal( const name_t& name, const char* string, unsigned int postfix ){
	ASSERT_MESSAGE( strcmp( name.first.c_str(), string ) == 0
					&& name.second.number() == postfix,
					"test failed!" );
}
void test_refcount(){
	Names names;

	names.insert( name_t( "func_bleh_", "100" ) );
	names.insert( name_t( "func_bleh_", "100" ) );
	names.insert( name_t( "func_bleh_", "100" ) );


	names.erase( name_t( "func_bleh_", "100" ) );
	names.erase( name_t( "func_bleh_", "100" ) );
	names.erase( name_t( "func_bleh_", "100" ) );

	ASSERT_MESSAGE( names.empty(), "test failed!" );
}

void test_make_unique(){
	Names names;

	{
		name_t name( names.make_unique( name_t( "func_bleh_", "01" ) ) );
		name_check_equal( name, "func_bleh_", 1 );
		names.insert( name );
	}
	{
		name_t name( names.make_unique( name_t( "func_bleh_", "04" ) ) );
		name_check_equal( name, "func_bleh_", 4 );
		names.insert( name );
	}
	{
		name_t name( names.make_unique( name_t( "func_bleh_", "04" ) ) );
		name_check_equal( name, "func_bleh_", 2 );
		names.insert( name );
	}
	{
		name_t name( names.make_unique( name_t( "func_bleh_", "1" ) ) );
		name_check_equal( name, "func_bleh_", 3 );
		names.insert( name );
	}
	{
		name_t name( names.make_unique( name_t( "func_bleh_", "2" ) ) );
		name_check_equal( name, "func_bleh_", 5 );
		names.insert( name );
	}
	{
		name_t name( names.make_unique( name_t( "func_bleh_", "3" ) ) );
		name_check_equal( name, "func_bleh_", 6 );
		names.insert( name );
	}

	names.erase( name_t( "func_bleh_", "1" ) );
	names.erase( name_t( "func_bleh_", "2" ) );
	names.erase( name_t( "func_bleh_", "3" ) );
	names.erase( name_t( "func_bleh_", "4" ) );
	names.erase( name_t( "func_bleh_", "5" ) );
	names.erase( name_t( "func_bleh_", "6" ) );

	ASSERT_MESSAGE( names.empty(), "test failed!" );
}
public:
TestUniqueName(){
	test_refcount();
	test_make_unique();
}
};

const TestUniqueName g_testuniquename;

#endif


#endif
