/*
 * Channel.cc
 * Author: Daniel Karrels (dan@karrels.com)
 * Copyright (C) 2002 Daniel Karrels <dan@karrels.com>
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 *
 * $Id: Channel.cc,v 1.55 2008/04/16 20:29:37 danielaustin Exp $
 */

#include	<new>
#include	<map>
#include	<string>
#include	<iostream>
#include	<sstream>

#include	"Channel.h"
#include	"iClient.h"
#include	"ChannelUser.h"
#include	"Network.h"
#include	"xparameters.h"
#include	"StringTokenizer.h"
#include	"ELog.h"
#include	"match.h"
#include	"server.h"
#include	"ConnectionManager.h"

RCSTAG("$Id: Channel.cc,v 1.55 2008/04/16 20:29:37 danielaustin Exp $") ;

namespace gnuworld
{

using std::vector ;
using std::string ;
using std::endl ;
using std::stringstream ;

const Channel::modeType Channel::MODE_T = 0x01 ;
const Channel::modeType Channel::MODE_N = 0x02 ;
const Channel::modeType Channel::MODE_S = 0x04 ;
const Channel::modeType Channel::MODE_P = 0x08 ;
const Channel::modeType Channel::MODE_K = 0x10 ;
const Channel::modeType Channel::MODE_L = 0x20 ;
const Channel::modeType Channel::MODE_M = 0x40 ;
const Channel::modeType Channel::MODE_I = 0x80 ;
const Channel::modeType Channel::MODE_R = 0x100 ;
const Channel::modeType Channel::MODE_D = 0x200 ;
const Channel::modeType Channel::MODE_A = 0x400 ;
const Channel::modeType Channel::MODE_U = 0x800 ;
const Channel::modeType Channel::MODE_REG = 0x1000 ;

Channel::Channel( const string& _name,
	const time_t& _creationTime )
 : name( _name ),
   creationTime( _creationTime ),
   modes( 0 ),
   limit( 0 )
{}

Channel::~Channel()
{
// Deallocate all ChannelUser's that are left
userIterator currentPtr = userList_begin() ;
userIterator endPtr = userList_end() ;
for( ; currentPtr != endPtr ; ++currentPtr )
	{
	delete currentPtr->second ;
	}
userList.clear() ;
}

bool Channel::addUser( ChannelUser* newUser )
{
assert( newUser != 0 ) ;

//elog	<< "Channel::addUser> ("
//	<< getName()
//	<< ") Number of users: "
//	<< userList.size()
//	<< endl ;

if( !userList.insert(
	userListType::value_type( newUser->getIntYYXXX(), newUser ) ).second )
	{
	elog	<< "Channel::addUser> ("
		<< getName()
		<< "): "
		<< "Unable to add user: "
		<< *newUser
		<< endl ;
	return false ;
	}

//elog	<< "Channel::addUser> " << name << " added user: "
//	<< *newUser << endl ;

return true ;
}

bool Channel::addUser( iClient* theClient )
{
ChannelUser* addMe = new (std::nothrow) ChannelUser( theClient ) ;

// The signature of addUser() here will verify the pointer
return addUser( addMe ) ;
}

ChannelUser* Channel::removeUser( ChannelUser* theUser )
{
assert( theUser != 0 ) ;

return removeUser( theUser->getClient()->getIntYYXXX() ) ;
}

ChannelUser* Channel::removeUser( iClient* theClient )
{
// This method is public, so the pointer must be validated
assert( theClient != 0 ) ;

return removeUser( theClient->getIntYYXXX() ) ;
}

ChannelUser* Channel::removeUser( const unsigned int& intYYXXX )
{
ChannelUser* tmpUser;

// Attempt to find the user in question
userIterator ptr = userList.find( intYYXXX ) ;

// Was the user found?
if( ptr != userList.end() )
	{
	// Yup, go ahead and remove the user from the userList
	tmpUser = ptr->second;

	userList.erase( ptr ) ;

	// Return a pointer to the ChannelUser
	return tmpUser;
	}

// Otherwise, the user was NOT found
// Log the error
// Happens when not handling zombies
//elog	<< "Channel::removeUser> ("
//	<< getName() << ") "
//	<< "Unable to find user: "
//	<< intYYXXX
//	<< std::endl ;

// Return error state
return 0 ;
}

ChannelUser* Channel::findUser( const iClient* theClient ) const
{
assert( theClient != 0 ) ;

const_userIterator ptr = userList.find( theClient->getIntYYXXX() ) ;
if( ptr == userList_end() )
	{
	// User not found
	return 0 ;
	}
return ptr->second ;
}

bool Channel::removeUserMode( const ChannelUser::modeType& whichMode,
	iClient* theClient )
{
ChannelUser* theChanUser = findUser( theClient ) ;
if( NULL == theChanUser )
	{
	elog	<< "Channel::removeUserMode> ("
		<< getName()
		<< ") "
		<< "Unable to find user: "
		<< theClient->getCharYYXXX()
		<< endl ;
	return false ;
	}
theChanUser->removeMode( whichMode ) ;
return true ;
}

bool Channel::setUserMode( const ChannelUser::modeType& whichMode,
	iClient* theClient )
{
// findUser() is also public, and so will verify theClient's pointer
ChannelUser* theChanUser = findUser( theClient ) ;
if( NULL == theChanUser )
	{
	elog	<< "Channel::setUserMode> ("
		<< getName()
		<< ") "
		<< "Unable to find user: "
		<< theClient->getCharYYXXX()
		<< endl ;
	return false ;
	}
theChanUser->setMode( whichMode ) ;
return true ;
}

bool Channel::getUserMode( const ChannelUser::modeType& whichMode,
	iClient* theClient ) const
{
// findUser() is also public, and so will verify theClient's pointer
ChannelUser* theChanUser = findUser( theClient ) ;
if( NULL == theChanUser )
	{
	elog	<< "Channel::getUserMode> ("
		<< getName()
		<< ") "
		<< "Unable to find user: "
		<< theClient->getCharYYXXX()
		<< endl ;
	return false ;
	}
return theChanUser->getMode( whichMode ) ;
}

void Channel::setBan( const string& newBan )
{
// xServer will worry about removing conflicting bans
banList.push_front( newBan ) ;
}

bool Channel::removeBan( const string& banMask )
{
for( banIterator ptr = banList_begin(), end = banList_end() ;
	ptr != end ; ++ptr )
	{
	if( !strcasecmp( *ptr, banMask ) )
		{
		banList.erase( ptr ) ;
		// Ban found, return true
		return true ;
		}
	}
// Ban not found
return false ;
}

bool Channel::findBan( const string& banMask ) const
{
for( const_banIterator ptr = banList_begin(),
	end = banList_end() ; ptr != end ; ++ptr )
	{
	if( !strcasecmp( *ptr, banMask ) )
		{
		return true ;
		}
	}
return false ;
}

bool Channel::matchBan( const string& banMask ) const
{
for( const_banIterator ptr = banList_begin(),
	end = banList_end() ; ptr != end ; ++ptr )
	{
	if( !match( banMask, *ptr ) )
		{
		// Found a match
		return true ;
		}
	}
return false ;
}

bool Channel::getMatchingBan( const string& banMask,
	string& matchingBan ) const
{
for( const_banIterator ptr = banList_begin(),
	end = banList_end() ; ptr != end ; ++ptr )
	{
	if( !match( banMask, *ptr ) )
		{
		matchingBan = *ptr ;
		return true ;
		}
	}
return false ;
}

void Channel::onMode(
	const vector< std::pair< bool, Channel::modeType > >& modeVector )
{
typedef vector< std::pair< bool, Channel::modeType > > modeVectorType ;
for( modeVectorType::const_iterator mItr = modeVector.begin() ;
	mItr != modeVector.end() ; ++mItr )
	{
	bool polarity = (*mItr).first ;

	if( polarity )		setMode( (*mItr).second ) ;
	else			removeMode( (*mItr).second ) ;
	} // for()
}

void Channel::onModeL( bool polarity, const unsigned int& newLimit )
{
if( polarity )
	{
	setMode( MODE_L ) ;
	setLimit( newLimit ) ;
	}
else
	{
	removeMode( MODE_L ) ;
	setLimit( 0 ) ;
	}
}

void Channel::onModeK( bool polarity, const string& newKey )
{
if( polarity )
	{
	setMode( MODE_K ) ;
	setKey( newKey ) ;
	}
else
	{
	removeMode( MODE_K ) ;
	setKey( string() ) ;
	}
}

void Channel::onModeA( bool polarity, const string& newApass )
{
if( polarity )
	{
	setMode( MODE_A ) ;
	setApass( newApass ) ;
	}
else
	{
	removeMode( MODE_K ) ;
	setApass( string() ) ;
	}
}

void Channel::onModeU( bool polarity, const string& newUpass )
{
if( polarity )
	{
	setMode( MODE_U ) ;
	setUpass( newUpass ) ;
	}
else
	{
	removeMode( MODE_U ) ;
	setUpass( string() ) ;
	}
}

void Channel::onModeO( const vector< std::pair< bool, ChannelUser* > >&
	opVector )
{
typedef vector< std::pair< bool, ChannelUser* > > opVectorType ;
for( opVectorType::const_iterator ptr = opVector.begin() ;
	ptr != opVector.end() ; ++ptr )
	{
	if( ptr->first )
		{
		ptr->second->setModeO() ;
		}
	else
		{
		ptr->second->removeModeO() ;
		}
	}
}

void Channel::onModeH( const vector< std::pair< bool, ChannelUser* > >&
	opVector )
{
typedef vector< std::pair< bool, ChannelUser* > > opVectorType ;
for( opVectorType::const_iterator ptr = opVector.begin() ;
	ptr != opVector.end() ; ++ptr )
	{
	if( ptr->first )
		{
		ptr->second->setModeH() ;
		}
	else
		{
		ptr->second->removeModeH() ;
		}
	}
}

void Channel::onModeV( const vector< std::pair< bool, ChannelUser* > >&
	voiceVector )
{
typedef vector< std::pair< bool, ChannelUser* > > voiceVectorType ;
for( voiceVectorType::const_iterator ptr = voiceVector.begin() ;
	ptr != voiceVector.end() ; ++ptr )
	{
	if( ptr->first )
		{
		ptr->second->setModeV() ;
		}
	else
		{
		ptr->second->removeModeV() ;
		}
	}
}

/**
 * The banVector passed to this method will be updated to
 * include any bans that have been removed as a result
 * of overlapping bans being added.
 * The order of these additions will be as expected:
 *  an overlapping ban will be put into the banVector,
 *  followed by all bans that it overrides.
 */
void Channel::onModeB( xServer::banVectorType& banVector )
{
typedef xServer::banVectorType banVectorType ;

banVectorType origBans( banVector ) ;
banVector.clear() ;

// Walk through the list of bans being removed/added
for( banVectorType::const_iterator newBanPtr = origBans.begin() ;
	newBanPtr != origBans.end() ; ++newBanPtr )
	{
	banVector.push_back( *newBanPtr ) ;

	// Is the ban being set or removed?
	if( !newBanPtr->first )
		{
		// Removing a ban
		removeBan( newBanPtr->second ) ;
		continue ;
		}

	// Setting a ban
	// Need to check the list of current bans for overlaps
	// This is grossly inefficient, Im open to suggestions

	// Next, search for overlaps
	banIterator currentBanPtr = banList_begin() ;
	for( ; currentBanPtr != banList_end() ; )
		{
		if( !match( newBanPtr->second, *currentBanPtr ) )
			{
			// Add the removed ban to the banVector
			// so that the caller can notify the rest
			// of the system of the removal
			banVector.push_back( banVectorType::value_type(
				false, *currentBanPtr ) ) ;

//			elog	<< "Channel::onModeB> Removing "
//				<< "overlapping ban: "
//				<< *currentBanPtr
//				<< endl ;

			// Overlap, remove the old ban
			currentBanPtr = banList.erase( currentBanPtr ) ;

			}
		else
			{
			// Update the iterator
			++currentBanPtr ;
			}
		} // inner for()

	// Now set the new ban
	// Setting this ban will add the ban into
	// later comparisons, but not this comparison,
	// which is what we want.
	setBan( newBanPtr->second ) ;

	} // outer for()
}

const string Channel::getModeString() const
{
string modeString( "+" ) ;
string argString ;

if( modes & MODE_T )	modeString += 't' ;
if( modes & MODE_N )	modeString += 'n' ;
if( modes & MODE_S )	modeString += 's' ;
if( modes & MODE_P )	modeString += 'p' ;
if( modes & MODE_M )	modeString += 'm' ;
if( modes & MODE_I )	modeString += 'i' ;
if( modes & MODE_R )	modeString += 'r' ;
if( modes & MODE_REG )	modeString += 'R' ;
if( modes & MODE_D )	modeString += 'D' ;

if( modes & MODE_K )
	{
	modeString += 'k' ;
	argString += getKey() + ' ' ;
	}

if( modes & MODE_L )
	{
	modeString += 'l' ;

	// Can't put numerical variables into a string
	stringstream s ;
	s	<< getLimit() ;

	argString += s.str() ;
	}

if( modes & MODE_A )
	{
	modeString += 'A' ;
	argString += getApass() + ' ' ;
	}

if( modes & MODE_U )
	{
	modeString += 'U' ;
	argString += getUpass() + ' ' ;
	}


return (modeString + ' ' + argString) ;
}

string Channel::createBan( const iClient* theClient )
{
assert( theClient != 0 ) ;

string theBan = "*!*" ;

// If we're +x, don't bother with the user name either.
if(!theClient->isModeX())
	{
	// Don't include the '~'
	if( (theClient->getUserName().size() >= 2) &&
		('~' == theClient->getUserName()[ 0 ]) )
		{
		theBan += theClient->getUserName().c_str() + 1 ;
		}
	else if( !theClient->getUserName().empty() )
		{
		theBan += theClient->getUserName() ;
		}
	}

theBan += '@' ;

StringTokenizer st( theClient->getInsecureHost(), '.' ) ;
if( ConnectionManager::isIpAddress( theClient->getInsecureHost() ) )
	{
	theBan += st[ 0 ] + '.' ;
	theBan += st[ 1 ] + '.' ;
	theBan += st[ 2 ] + ".*" ;
	}
else
	{
	if( (2 == st.size()) || theClient->isModeX() )
		{
		theBan += theClient->getInsecureHost() ;
		}
	else
		{
		theBan += "*." + st.assemble( 1 ) ;
		}
	}

return theBan ;
}

string Channel::createUniformBan( const iClient* theClient )
{
	assert( theClient != 0 ) ;

	string theBan;
	if (theClient->isModeX())
	{
		if (theClient->isModeR())
			theBan = "*!*@" + theClient->getAccount() + theClient->getHiddenHostSuffix();
		else if ((!theClient->getCloakIP().empty()) || (!theClient->getCloakHost().empty()))
		{
			if (!theClient->getCloakIP().empty())
				theBan = "*!*@" + theClient->getCloakIP();
			if (!theClient->getCloakHost().empty())
				theBan = "*!*@" + theClient->getCloakHost();
		}
		else
			theBan = "*!*@" + theClient->getAccount() + theClient->getHiddenHostSuffix();
	}
	else
	{
		theBan = "*!*@" + theClient->getInsecureHost();
	}

	return theBan ;
}

void Channel::removeAllModes()
{
for( userIterator uItr = userList_begin() ; uItr != userList_end() ;
	++uItr )
	{
	ChannelUser* theUser = uItr->second ;
	assert( theUser != 0 ) ;

	theUser->removeModeO() ;
	theUser->removeModeV() ;
	}
modes = 0 ;
limit = 0 ;
#if __GNUC__ == 2
key = "";
Apass = "";
Upass = "";
#else
key.clear();
Apass.clear();
Upass.clear();
#endif
}

} // namespace gnuworld
