/**
 * chanfix.cc
 * 
 * Copyright (C) 2003	Reed Loden <reed@reedloden.com>
 *			Matthias Crauwels <ultimate_@wol.be>
 *			Jimmy Lipham <music0m@alltel.net>
 *			Neil Spierling <sirvulcan@gmail.com>
 *
 * Automatically and manually fix opless and taken over channels
 *
 * 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: chanfix.cc,v 1.16 2010/03/04 04:24:11 hidden1 Exp $
 */

#include	<csignal>
#include	<cstdarg>
#include	<ctime>
#include	<iomanip>
#include	<iostream>
#include	<map>
#include	<new>
#include	<sstream>
#include	<string>
#include	<utility>
#include	<vector>

#include	<sys/resource.h>

#include	"dbHandle.h"

#include	"gnuworld_config.h"
#include	"client.h"
#include	"EConfig.h"
#include	"Network.h"
#include	"server.h"
#include	"StringTokenizer.h"

#include	"chanfix.h"
#include	"defs.h"
#include	"chanfix_misc.h"
#include	"chanfixCommands.h"
#include	"responses.h"
#include	"sqlChanOp.h"
#include	"sqlChannel.h"
#include	"sqlcfUser.h"

#ifdef CHANFIX_HAVE_BOOST_THREAD
#include	<boost/thread/thread.hpp>
#endif /* CHANFIX_HAVE_BOOST_THREAD */

RCSTAG("$Id: chanfix.cc,v 1.16 2010/03/04 04:24:11 hidden1 Exp $");

namespace gnuworld
{

namespace cf
{

short currentDay;

/*
 *  Exported function used by moduleLoader to gain an
 *  instance of this module.
 */

extern "C"
{
  xClient* _gnuwinit(const std::string& args)
  { 
    return new chanfix( args );
  }

} 

/**
 * This constructor calls the base class constructor.  The xClient
 * constructor will open the configuration file given and retrieve
 * basic client info (nick/user/host/etc).
 * Any additional processing must be done here.
 */
chanfix::chanfix( const std::string& configFileName )
 : xClient( configFileName )
{
/* Load the config file */
readConfigFile(configFileName);

/* Initial state */
currentState = INIT;

/* Initial finding of the channel service */
chanServLinked = false;

/* Initial update status */
updateInProgress = false;

std::string dbString = "host=" + sqlHost + " dbname=" + sqlDB
  + " port=" + sqlPort + " user=" + sqlcfUsername + " password=" + sqlPass;

theManager = sqlManager::getInstance(dbString);

/* Open our logfiles for writing */
adminLog.open(adminLogFile.c_str(), std::ios::out | std::ios::app);
debugLog.open(debugLogFile.c_str(), std::ios::out | std::ios::app);

/* Register the commands we want to use */
RegisterCommand(new ADDFLAGCommand(this, "ADDFLAG",
	"<username> <flag>",
	3,
	sqlcfUser::F_USERMANAGER | sqlcfUser::F_SERVERADMIN
	));
RegisterCommand(new ADDHOSTCommand(this, "ADDHOST",
	"<username> <nick!user@host>",
	3,
	sqlcfUser::F_USERMANAGER | sqlcfUser::F_SERVERADMIN
	));
RegisterCommand(new ADDNOTECommand(this, "ADDNOTE",
	"<#channel> <reason>",
	3,
	sqlcfUser::F_COMMENT
	));
RegisterCommand(new ADDUSERCommand(this, "ADDUSER",
	"<username> [host]",
	2,
	sqlcfUser::F_USERMANAGER | sqlcfUser::F_SERVERADMIN
	));
RegisterCommand(new ALERTCommand(this, "ALERT",
	"<#channel> <reason>",
	3,
	sqlcfUser::F_COMMENT
	));
RegisterCommand(new BLOCKCommand(this, "BLOCK",
	"<#channel> <reason>",
	3,
	sqlcfUser::F_BLOCK
	));
RegisterCommand(new CANFIXCommand(this, "CANFIX",
	"<#channel>",
	2,
	0
	));
RegisterCommand(new CHANFIXCommand(this, "CHANFIX",
	"<#channel> [override] [contact]",
	2,
	0 /* Set to 0 to allow all opers to access it, otherwise this should be sqlcfUser::F_CHANFIX */
	));
RegisterCommand(new CHECKCommand(this, "CHECK",
	"<#channel>",
	2,
	0
	));
#ifdef CHANFIX_DEBUG
RegisterCommand(new DEBUGCommand(this, "DEBUG",
	"<ROTATE|UPDATE>",
	2,
	sqlcfUser::F_OWNER
	));
#endif /* CHANFIX_DEBUG */
RegisterCommand(new DELFLAGCommand(this, "DELFLAG",
	"<username> <flag>",
	3,
	sqlcfUser::F_USERMANAGER | sqlcfUser::F_SERVERADMIN
	));
RegisterCommand(new DELHOSTCommand(this, "DELHOST",
	"<username> <nick!user@host>",
	3,
	sqlcfUser::F_USERMANAGER | sqlcfUser::F_SERVERADMIN
	));
RegisterCommand(new DELNOTECommand(this, "DELNOTE",
	"<#channel> <note_id>",
	3,
	sqlcfUser::F_COMMENT
	));
RegisterCommand(new DELUSERCommand(this, "DELUSER",
	"<username>",
	2,
	sqlcfUser::F_USERMANAGER | sqlcfUser::F_SERVERADMIN
	));
RegisterCommand(new HELPCommand(this, "HELP",
	"[command]",
	1,
	0
	));
RegisterCommand(new HISTORYCommand(this, "HISTORY",
	"<#channel>",
	2,
	0
	));
RegisterCommand(new INFOCommand(this, "INFO",
	"<#channel>",
	2,
	0
	));
RegisterCommand(new INVITECommand(this, "INVITE",
	"",
	1,
	sqlcfUser::F_OWNER
	));
RegisterCommand(new LASTCOMCommand(this, "LASTCOM",
	"[amount of commands] [days from]",
	1,
	sqlcfUser::F_OWNER
	));
RegisterCommand(new LISTBLOCKEDCommand(this, "LISTBLOCKED",
	"",
	1,
	sqlcfUser::F_BLOCK
	));
RegisterCommand(new LISTHOSTSCommand(this, "LISTHOSTS",
	"[username]",
	1,
	sqlcfUser::F_LOGGEDIN
	));
RegisterCommand(new OPLISTCommand(this, "OPLIST",
	"<#channel> [-all] [-days]",
	2,
	0
	));
RegisterCommand(new OPNICKSCommand(this, "OPNICKS",
	"<#channel>",
	2,
	0
	));
#ifdef ENABLE_QUOTE
RegisterCommand(new QUOTECommand(this, "QUOTE",
	"<text>",
	2,
	sqlcfUser::F_OWNER
	));
#endif /* ENABLE_QUOTE */
RegisterCommand(new REHASHCommand(this, "REHASH",
	"",
	1,
	sqlcfUser::F_OWNER
	));
RegisterCommand(new RELOADCommand(this, "RELOAD",
	"[reason]",
	1,
	sqlcfUser::F_OWNER
	));
RegisterCommand(new REQUESTOPCommand(this, "REQUESTOP",
	isAllowingTopOpAlert() ? "<#channel> [contact]" : "<#channel>",
	2,
	0
	));
RegisterCommand(new SCORECommand(this, "SCORE",
	"<#channel> [account|=nick]",
	2,
	0
	));
RegisterCommand(new SCORECommand(this, "CSCORE",
	"<#channel> [account|=nick]",
	2,
	0
	));
RegisterCommand(new SAYCommand(this, "SAY",
	"<#channel> <text>",
	3,
	sqlcfUser::F_OWNER
	));
RegisterCommand(new SETCommand(this, "SET",
	"<option> <value>",
	3,
	sqlcfUser::F_OWNER
	));
#ifdef USERADMIN
RegisterCommand(new SETGROUPCommand(this, "SETGROUP",
	"<username> <group>",
	3,
	sqlcfUser::F_USERMANAGER
	));
#endif
RegisterCommand(new SIMULATECommand(this, "SIMULATE",
	"<#channel> <auto/manual>",
	3,
	0 /* Set to 0 to allow all opers to access it, otherwise this should be sqlcfUser::F_CHANFIX */
	));
RegisterCommand(new SHUTDOWNCommand(this, "SHUTDOWN",
	"[reason]",
	1,
	sqlcfUser::F_OWNER
	));
RegisterCommand(new STATUSCommand(this, "STATUS",
	"",
	1,
	0
	));
#ifdef USERADMIN
RegisterCommand(new SUSPENDCommand(this, "SUSPEND",
	"<username>",
	2,
	sqlcfUser::F_USERMANAGER | sqlcfUser::F_SERVERADMIN
	));
#endif
RegisterCommand(new UNALERTCommand(this, "UNALERT",
	"<#channel>",
	2,
	sqlcfUser::F_COMMENT
	));
RegisterCommand(new UNBLOCKCommand(this, "UNBLOCK",
	"<#channel>",
	2,
	sqlcfUser::F_BLOCK
	));
#ifdef USERADMIN
RegisterCommand(new UNSUSPENDCommand(this, "UNSUSPEND",
	"<username>",
	2,
	sqlcfUser::F_USERMANAGER | sqlcfUser::F_SERVERADMIN
	));
#endif
RegisterCommand(new USERSCORESCommand(this, "USERSCORES",
	"<account>",
	2,
	sqlcfUser::F_LOGGEDIN
	));
RegisterCommand(new USETCommand(this, "USET",
	"[username] <option> <value>",
	3,
	sqlcfUser::F_LOGGEDIN
	));
RegisterCommand(new WHOFLAGCommand(this, "WHOFLAG",
	"<flag>",
	2,
	sqlcfUser::F_LOGGEDIN
	));
#ifdef USERADMIN
RegisterCommand(new WHOGROUPCommand(this, "WHOGROUP",
	"[group]",
	1,
	sqlcfUser::F_USERMANAGER | sqlcfUser::F_SERVERADMIN
	));
#endif
RegisterCommand(new WHOISCommand(this, "WHOIS",
	"<username|=nick|*> [-modif]",
	2,
	sqlcfUser::F_LOGGEDIN
	));

/* Our global DB handler */
localDBHandle = theManager->getConnection(); 

/* Set our current day. */
setCurrentDay();

/* Precache our chanOps. */
precacheChanOps();

/* Precache our channels. */
precacheChannels();

/* Precache our staff. */
precacheUsers();

/* Load help messages. */
loadHelpTable();

/* Load our translation tables. */
loadTranslationTable();

/* Set up our timer. */
theTimer = new Timer();

}

chanfix::~chanfix()
{
}

/* Load (or reload) the configuration file */
void chanfix::readConfigFile(const std::string& configFileName)
{
chanfixConfig = new (std::nothrow) EConfig(configFileName);
assert(chanfixConfig != 0);

/* Config file processing */
consoleChan = chanfixConfig->Require("consoleChan")->second ;
consoleChanModes = chanfixConfig->Require("consoleChanModes")->second ;
sendConsoleNotices = atob(chanfixConfig->Require("sendConsoleNotices")->second) ;
joinChanModes = chanfixConfig->Require("joinChanModes")->second ;
enableAutoFix = atob(chanfixConfig->Require("enableAutoFix")->second) ;
enableChanFix = atob(chanfixConfig->Require("enableChanFix")->second) ;
enableChannelBlocking = atob(chanfixConfig->Require("enableChannelBlocking")->second) ;
autoFixNotice = atob(chanfixConfig->Require("autoFixNotice")->second) ;
manualFixNotice = atob(chanfixConfig->Require("manualFixNotice")->second) ; 
joinChannels = atob(chanfixConfig->Require("joinChannels")->second) ;
stopAutoFixOnOp = atob(chanfixConfig->Require("stopAutoFixOnOp")->second) ;
stopChanFixOnOp = atob(chanfixConfig->Require("stopChanFixOnOp")->second) ;
allowTopOpFix = atob(chanfixConfig->Require("allowTopOpFix")->second) ;
allowTopOpAlert = atob(chanfixConfig->Require("allowTopOpAlert")->second) ;
topOpPercent = atoi((chanfixConfig->Require("topOpPercent")->second).c_str()) ;
minFixScore = atoi((chanfixConfig->Require("minFixScore")->second).c_str()) ;
minCanFixScore = atoi((chanfixConfig->Require("minCanFixScore")->second).c_str()) ;
minRequestOpTime = atoi((chanfixConfig->Require("minRequestOpTime")->second).c_str()) ;
version = atoi((chanfixConfig->Require("version")->second).c_str()) ;
useBurstToFix = atob(chanfixConfig->Require("useBurstToFix")->second) ;
numServers = atoi((chanfixConfig->Require("numServers")->second).c_str()) ;
minServersPresent = atoi((chanfixConfig->Require("minServersPresent")->second).c_str()) ;
chanServName = chanfixConfig->Require("chanServName")->second ;
numTopScores = atoi((chanfixConfig->Require("numTopScores")->second).c_str()) ;
minClients = atoi((chanfixConfig->Require("minClients")->second).c_str()) ;
clientNeedsIdent = atob(chanfixConfig->Require("clientNeedsIdent")->second) ;
connectCheckFreq = atoi((chanfixConfig->Require("connectCheckFreq")->second).c_str()) ;
adminLogFile = chanfixConfig->Require("adminLogFile")->second ;
debugLogFile = chanfixConfig->Require("debugLogFile")->second ;

/* Set up the channels that chanfix should join */
EConfig::const_iterator ptr = chanfixConfig->Find("joinChan");
while (ptr != chanfixConfig->end() && ptr->first == "joinChan") {
  chansToJoin.push_back(ptr->second);
  ++ptr;
}

/* Make sure that the consoleChan is not a joinChan */
for (joinChansType::iterator ptr = chansToJoin.begin();
     ptr != chansToJoin.end(); ++ptr) {
  if (!strcasecmp((*ptr).c_str(), consoleChan.c_str())) {
    /* Found, remove it from the list of chansToJoin */
    chansToJoin.erase(ptr);
    break;
  }
}

/* Database processing */
sqlHost = chanfixConfig->Require("sqlHost")->second;
sqlPort = chanfixConfig->Require("sqlPort")->second;
sqlDB = chanfixConfig->Require("sqlDB")->second;
sqlcfUsername = chanfixConfig->Require("sqlcfUser")->second;
sqlPass = chanfixConfig->Require("sqlPass")->second;

elog	<< "chanfix::readConfigFile> Configuration loaded!"
	<< std::endl;
}


/* Register a new command */
bool chanfix::RegisterCommand( Command *theCommand )
{
UnRegisterCommand( theCommand->getName() ) ;
return commandMap.insert( commandMapType::value_type(theCommand->getName(), theCommand)).second;
}

/* UnRegister a command */
bool chanfix::UnRegisterCommand( const std::string& commName )
{
commandMapType::iterator ptr = commandMap.find( commName ) ;
if ( ptr == commandMap.end() ) {
  return false ;
}
delete ptr->second ;
commandMap.erase( ptr ) ;
return true ;
}

/* OnShutdown */
void chanfix::OnShutdown(const std::string& reason)
{
MyUplink->UnloadClient(this, reason);
}

/* OnSignal */
void chanfix::OnSignal(int sig)
{
if (sig == SIGHUP) {
  /* Close/reopen log files */
  logAdminMessage("---------- End of log file. Rotating... ----------");
  if (adminLog.is_open())
    adminLog.close();

  adminLog.open(adminLogFile.c_str(), std::ios::out | std::ios::app);
}

xClient::OnSignal(sig);
}

/* OnAttach */
void chanfix::OnAttach()
{
/**
 * Don't send END_OF_BURST (EB) so we can stay in burst mode 
 * indefinitely in order to be able to do TS-1.
 */
if (useBurstToFix && version < 12) /* Not needed for u2.10.12+ */
  MyUplink->setSendEB(false);

/**
 * Set our uplink as our main server for our commands.
 */
for (commandMapType::iterator ptr = commandMap.begin(); ptr != commandMap.end(); ++ptr) {
  ptr->second->setServer(MyUplink);
}

/**
 * Register for global network events
 */
MyUplink->RegisterEvent( EVT_ACCOUNT, this );
MyUplink->RegisterEvent( EVT_KILL, this );
MyUplink->RegisterEvent( EVT_QUIT, this );
MyUplink->RegisterEvent( EVT_BURST_CMPLT, this );
MyUplink->RegisterEvent( EVT_NETJOIN, this );
MyUplink->RegisterEvent( EVT_NETBREAK, this );
MyUplink->RegisterEvent( EVT_NICK, this );
if (!MyUplink->RegisterEvent( EVT_SERVERMODE , this ))
	elog << "FAILED to register chanfix.EVT_SERVERMODE" << std::endl;
else elog << "SUCCESSFULLY registered EVT_SERVERMODE" << std::endl;

/**
 * Register for all channel events
 */
MyUplink->RegisterChannelEvent( xServer::CHANNEL_ALL, this );

xClient::OnAttach() ;
}

/**
 * Thread class only used for score updates, updates on reload and shutdown are not threaded
 */
#ifdef CHANFIX_HAVE_BOOST_THREAD
class ClassUpdateDB {
  public:
    ClassUpdateDB(chanfix& cf) : cf_(cf) {}
    void operator()() {
      cf_.updateDB();
      return;
    }

private:
        chanfix& cf_;
};
#endif /* CHANFIX_HAVE_BOOST_THREAD */

/* OnTimer */
void chanfix::OnTimer(const xServer::timerID& theTimer, void*)
{
time_t theTime;
if (theTimer == tidGivePoints) {
  /* 5 min timer, loop through channels and give all ops a point! */
  giveAllOpsPoints();

  /* Refresh Timer */
  theTime = time(NULL) + POINTS_UPDATE_TIME;
  tidGivePoints = MyUplink->RegisterTimer(theTime, this, NULL);
  }
else if (theTimer == tidAutoFix) {
  autoFix();

  /* Refresh Timer */
  theTime = time(NULL) + CHECK_CHANS_TIME;
  tidAutoFix = MyUplink->RegisterTimer(theTime, this, NULL);
  }
else if (theTimer == tidCheckDB) {
  /*checkDBConnection();*/

  /* Refresh Timer */
  theTime = time(NULL) + connectCheckFreq;
  tidCheckDB = MyUplink->RegisterTimer(theTime, this, NULL);
  }
else if (theTimer == tidFixQ) {
  processQueue();

  /* Refresh Timer */
  theTime = time(NULL) + PROCESS_QUEUE_TIME;
  tidFixQ = MyUplink->RegisterTimer(theTime, this, NULL);
  setNextFix(currentTime() + PROCESS_QUEUE_TIME);
}
else if (theTimer == tidRotateDB) {
  /* Clean-up the database if its 00 GMT */
  rotateDB();

  /* Refresh Timer */
  theTime = time(NULL) + getSecsTilMidnight();
  tidRotateDB = MyUplink->RegisterTimer(theTime, this, NULL);
}
else if (theTimer == tidUpdateDB) {
  /* Prepare to synchronize the database in a thread */
  prepareUpdate(true);

  /* Refresh Timer */
  theTime = time(NULL) + SQL_UPDATE_TIME;
  tidUpdateDB = MyUplink->RegisterTimer(theTime, this, NULL);
}
else if (theTimer == tidTempBlocks) {
  /* Remove expired temporary blocks */
  expireTempBlocks();
	
  /* Refresh Timer */
  theTime = time(NULL) + TEMPBLOCKS_CHECK_TIME;
  tidTempBlocks = MyUplink->RegisterTimer(theTime, this, NULL);
}
}

/* OnDetach */
void chanfix::OnDetach( const std::string& reason )
{
/* Save our database */
prepareUpdate(false);

/* Delete our config */
delete chanfixConfig; chanfixConfig = 0;

/* Delete our timer */
delete theTimer; theTimer = 0;

/* Delete our commands */
for (commandMapType::iterator ptr = commandMap.begin();
     ptr != commandMap.end(); ++ptr) {
  delete ptr->second;
}
commandMap.clear();

/* Delete our sqlManager */
theManager->removeManager();

/* Finally, close our logs */
if (adminLog.is_open())
  adminLog.close();
if (debugLog.is_open())
  debugLog.close();

xClient::OnDetach( reason ) ;
}

/* OnConnect */
void chanfix::OnConnect()
{
/* If we have just reloaded, we won't be in BURST. */
if (currentState == INIT) {
  findChannelService();
  changeState(RUN);
}

xClient::OnConnect() ;
}

/* OnDisconnect */
void chanfix::OnDisconnect()
{
xClient::OnDisconnect() ;
}

void chanfix::OnPrivateMessage( iClient* theClient,
	const std::string& Message, bool)
{
sqlcfUser* theUser = isAuthed(theClient->getAccount());

if (currentState == BURST) {
  if (theUser)
    SendTo(theClient,
           getResponse(theUser,
                       language::no_commands_during_burst,
                       std::string("Sorry, I do not accept commands during a burst.")).c_str());
  else
    SendTo(theClient,
           getResponse(theUser,
                       language::no_commands_during_burst_noper,
                       std::string("Sorry, I'm too busy at the moment. Please try again soon.")).c_str());
  return;
}

StringTokenizer st(Message);
if (st.empty())
  return;

const std::string Command = string_upper(st[0]);

if (!theClient->isOper()) {
  if (Command != "CANFIX" && Command != "HELP" && Command != "REQUESTOP") {
    if (!theUser || theUser->getNeedOper())
      return;
  }
}

commandMapType::iterator commHandler = commandMap.find(Command);
if (commHandler == commandMap.end()) {
  SendTo(theClient,
         getResponse(theUser,
                     language::unknown_command,
                     std::string("Unknown command.")).c_str());
  return;
}

if (st.size() < commHandler->second->getNumParams()) {
  commHandler->second->Usage(theClient);
  return;
}

/* If you change this code, remember to change it in HELPCommand.cc */
sqlcfUser::flagType requiredFlags = commHandler->second->getRequiredFlags();
if (requiredFlags) {
  if (!theUser) {
    SendTo(theClient,
           getResponse(theUser,
                       language::need_to_auth,
                       std::string("You need to authenticate to use this command.")).c_str());
    return;
  }

  if (theUser->getIsSuspended()) {
    SendTo(theClient,
           getResponse(theUser,
                       language::access_suspended,
                       std::string("Your access to this service is suspended.")).c_str());
    return;
  }
#ifdef ENABLE_HOST_CHECKING
  if (!theUser->matchHost(theClient->getRealNickUserHost().c_str())) {
    SendTo(theClient,
           getResponse(theUser,
                       language::host_not_matching,
                       std::string("Your current host does not match any registered hosts for your username.")).c_str());
    return;
  }
#endif

  if (requiredFlags != sqlcfUser::F_LOGGEDIN &&
      !theUser->getFlag(requiredFlags)) {
    if (getFlagChar(requiredFlags) != ' ')
      SendTo(theClient,
             getResponse(theUser,
                         language::requires_flag,
                         std::string("This command requires flag '%c'.")).c_str(),
                                     getFlagChar(requiredFlags));
    else
      SendTo(theClient,
             getResponse(theUser,
                         language::requires_flags,
                         std::string("This command requires one of these flags: \"%s\".")).c_str(),
                                     getFlagsString(requiredFlags).c_str());
    return;
  }
}

if (theUser) {
  theUser->setLastSeen(currentTime());
  theUser->commit(localDBHandle);
}

commHandler->second->Exec(theClient, theUser ? theUser : NULL, Message);

xClient::OnPrivateMessage(theClient, Message);
}

void chanfix::OnCTCP( iClient* theClient, const std::string& CTCP,
		const std::string& Message, bool Secure )
{
StringTokenizer st(CTCP);

if (st.empty()) return;

const std::string Command = string_upper(st[0]);

if (Command == "DCC") {
  DoCTCP(theClient, CTCP, "REJECT");
} else if (Command == "PING" || Command == "ECHO") {
  DoCTCP(theClient, CTCP, Message);
} else if (Command == "VERSION") {
  DoCTCP(theClient, CTCP, "Evilnet Development -- mod.openchanfix v2.0 [compiled "__DATE__" "__TIME__"]");
} else if (Command == "WHODUNIT?") {
  DoCTCP(theClient, CTCP, "reed, ULtimaTe_, Compy, SiRVulcaN");
} else if (Command == "SUBVERSION") {
  DoCTCP(theClient, CTCP, rcsId);
} else if (Command == "GENDER") {
  DoCTCP(theClient, CTCP, "Gender pending vote, but for now I'll be whatever you want!");
}

xClient::OnCTCP(theClient, CTCP, Message, Secure);
}

// Burst any channels.
void chanfix::BurstChannels()
{
xClient::BurstChannels();

MyUplink->JoinChannel(this, consoleChan, consoleChanModes);

for (joinChansType::size_type i = 0; i < chansToJoin.size(); i++) {
  /* Burst our channels */
  MyUplink->JoinChannel(this, chansToJoin[i], joinChanModes);
}

/* Start our timers */
startTimers();
}

/* OnChannelEvent */
void chanfix::OnChannelEvent( const channelEventType& whichEvent,
	Channel* theChan,
	void* data1, void* data2, void* data3, void* data4 )
{
iClient* theClient = 0;
iServer* theServer = 0;

/* If we are not running, we don't want to be giving points. */
if (currentState != RUN) return;

/* If this channel is too small, don't worry about it. */
if (theChan->size() < minClients)  return;

switch( whichEvent )
	{
	case EVT_JOIN:
		{
		/* If this is the operChan or supportChan, op opers on join */
		theClient = static_cast< iClient* >( data1 );
		if (theClient->isOper()) {
		  for (joinChansType::iterator ptr = chansToJoin.begin();
		       ptr != chansToJoin.end(); ++ptr) {
		    if (!strcasecmp((*ptr).c_str(), theChan->getName().c_str())) {
		      /* Found, op the user */
		      Op(theChan, theClient);
		      break;
		    }
		  }
		}
		break ;
		}
	case EVT_KICK:
	case EVT_PART:
		{
		theClient = static_cast< iClient* >( data1 );
		lostOp(theChan->getName(), theClient, NULL);
		break ;
		}
	case EVT_SERVERMODE:
		{
		theServer = static_cast< iServer* >( data1 );
                if (theServer && theServer != MyUplink->getUplink() && theServer->isService()) {
		  if (!isTempBlocked(theChan->getName()))
		    tempBlockList.insert(tempBlockType::value_type(theChan->getName(), currentTime()));
		}
		elog << "chanfix: GOT SERVER MODE EVENT!" << std::endl;
		break ;
		}
	default:
		break ;
	}

xClient::OnChannelEvent( whichEvent, theChan,
	data1, data2, data3, data4 ) ;
}

void chanfix::OnChannelModeO( Channel* theChan, ChannelUser* theUser,
			const xServer::opVectorType& theTargets)
{
/* if (currentState != RUN) return; */

if (theUser) {
  iServer* theServer = Network->findServer(theUser->getClient()->getIntYY());
  if (theServer && theServer != MyUplink->getUplink() && theServer->isService()) {
    if (!isTempBlocked(theChan->getName()))
      tempBlockList.insert(tempBlockType::value_type(theChan->getName(), currentTime()));
  }
}

	
if (theChan->size() < minClients)
  return;

if (!canScoreChan(theChan))
  return;

for (xServer::opVectorType::const_iterator ptr = theTargets.begin();
     ptr != theTargets.end(); ++ptr) {
  ChannelUser* tmpUser = ptr->second;

  if (tmpUser->getClient()->getAccount() == "")
    continue;

  bool polarity = ptr->first;
  if (polarity) {
    // Someone is opped
    gotOpped(theChan, tmpUser->getClient());

    // If the channel is being fixed and the op is done by a user,
    // cancel the fix, as there is an awake op
    if (theUser)
      stopFixingChan(theChan, false);
  } else {
    // Someone is deopped
    lostOp(theChan->getName(), tmpUser->getClient(), NULL);
  } // if
} // for
}

bool chanfix::logLastComMessage(iClient* theClient, const std::string& Message)
{
  StringTokenizer st(Message);
  if (st.empty())
    return false;

  const std::string Command = string_upper(st[0]);
  
  const std::string username = (theClient->getAccount() != "") ? theClient->getAccount() : theClient->getNickName();

  std::string log;
  dbHandle* cacheCon = getLocalDBHandle();

  static const char *Main = "INSERT into comlog (ts,user_name,command) VALUES (date_part('epoch', CURRENT_TIMESTAMP)::int,'";

  std::stringstream insertString;
  insertString	<< Main
		<< username
		<< "','"
		<< Command
		<< " ";

  unsigned int pos = 1;
  while (pos < st.size()) {
    insertString << st[pos]
		 << " ";
    pos++;
  }

  insertString	<< "')"
		<< std::ends;

  if (!cacheCon->Exec(insertString.str())) {
    elog << "sqlChannel::Insert> Something went wrong: "
	 << cacheCon->ErrorMessage()
	 << std::endl;
  }

  //theManager->removeConnection(cacheCon);

  return true;
}

/* msgTopOps */
bool chanfix::msgTopOps(Channel* netChan) {

  int opCount = 0;
  sqlChanOp* curOp = 0;
  bool inChan = true;

  chanOpsType myOps = getMyOps(netChan);

  for (chanOpsType::iterator opPtr = myOps.begin();
     opPtr != myOps.end() && (opCount < OPCOUNT); opPtr++) {
 
    curOp = *opPtr;
    opCount++;

    inChan = accountIsOnChan(netChan->getName(), curOp->getAccount());
    if (!inChan) {
      typedef std::list < iClient* > accountListType;
      const iClient* curAccount;
      accountListType accountList;
      authMapType::iterator cAuth = authMap.find(curOp->getAccount());
      if (cAuth == authMap.end())
	break;
      
      accountList = cAuth->second;

      if (accountList.empty())
        break;

      for (accountListType::iterator cptr = accountList.begin(); (cptr != accountList.end()); cptr++)
      {
        curAccount = *cptr;

        MyUplink->Write("%s P %s :%s channel modes have been removed to allow you to return. Please return so that I can op you during the channel fixing process\r\n",
                  getCharYYXXX().c_str(), curAccount->getCharYYXXX().c_str(), netChan->getName().c_str());

        MyUplink->Write("%s P %s :\002DO NOT REPLY TO THIS MESSAGE\002\r\n",
                  getCharYYXXX().c_str(), curAccount->getCharYYXXX().c_str());

      }
    }
  }

  return true;
}

/* OnEvent */
void chanfix::OnEvent( const eventType& whichEvent,
	void* data1, void* data2, void* data3, void* data4 )
{
switch(whichEvent)
	{
	case EVT_ACCOUNT:
		{
		iClient* tmpUser = static_cast< iClient* >( data1 ) ;
		authMapType::iterator ptr = authMap.find(tmpUser->getAccount());
		if (ptr != authMap.end()) {
			/* This user is already logged in, just add the iClient
			 * (if they aren't in the list already)
			 */
			authMapType::mapped_type::iterator listPtr = std::find(ptr->second.begin(),
					ptr->second.end(),
					tmpUser);
			if (listPtr == ptr->second.end())
			  ptr->second.push_back(tmpUser);
		} else {
			/* Add the map entry AND the initial list entry */
			authMapType::mapped_type theList;
			theList.push_back(tmpUser);
			authMap.insert(authMapType::value_type(tmpUser->getAccount(), theList));
		}
		break;
		}
	case EVT_BURST_CMPLT:
		{
		if (currentState != SPLIT)
		  changeState(RUN);
		break;
		}
	case EVT_NETJOIN:
	case EVT_NETBREAK:
		{
		iServer* theServer = static_cast< iServer* >(data1);
		checkChannelServiceLink(theServer, whichEvent);
		checkNetwork();
		break;
		}
	case EVT_KILL:
	case EVT_QUIT:
		{
		iClient* theClient = static_cast< iClient* >
			((whichEvent == EVT_QUIT) ?
				 data1 :
				 data2 );

		clientOpsType* myOps = findMyOps(theClient);
		if (!myOps->empty()) {
		  for (clientOpsType::iterator ptr = myOps->begin(); ptr != myOps->end();)
		    lostOp(*ptr++, theClient, myOps);
		} else {
		  delete myOps;
		}
		
		if (!theClient->getAccount().empty())
		{
			/* Now we need to remove this iClient from the auth map */
			authMapType::iterator ptr = authMap.find(theClient->getAccount());
			
			if (ptr != authMap.end()) {
			  ptr->second.erase(std::find(ptr->second.begin(), ptr->second.end(), theClient));

			  /* If the list is empty, remove the map entry */
			  if (ptr->second.empty())
				authMap.erase(theClient->getAccount());
			}
		}
		//Cleanup
		theClient->removeCustomData(this);
		break;
		}
	case EVT_NICK:
		{
		iClient* tmpUser = static_cast< iClient* >( data1 );
		if (tmpUser->isModeR()) {
		  /* Check to see if this current account is already mapped,
		   * Then check to see if this nick is already there
		   */
		  authMapType::iterator ptr = authMap.find(tmpUser->getAccount());
		  if (ptr != authMap.end()) {
		    /* Already have an entry, just add the iClient if not there already */
		    authMapType::mapped_type::iterator listPtr = std::find(ptr->second.begin(),
					ptr->second.end(),
					tmpUser);
		    if (listPtr == ptr->second.end())
		      ptr->second.push_back(tmpUser);
		    
		  } else {
		    authMapType::mapped_type theList;
		    theList.push_back(tmpUser);
		    authMap.insert(authMapType::value_type(tmpUser->getAccount(), theList));
		  }
		}
		break;
		}
	}

xClient::OnEvent( whichEvent, data1, data2, data3, data4 ) ;
}

bool chanfix::serverNotice( Channel* theChannel, const char* format, ... )
{
char buf[ 1024 ] = { 0 } ;
va_list _list ;
va_start( _list, format ) ;
vsnprintf( buf, 1024, format, _list ) ;
va_end( _list ) ;

std::stringstream s;
s	<< MyUplink->getCharYY()
	<< " O "
	<< theChannel->getName()
	<< " :"
	<< buf
	<< std::ends;

Write( s );

return false;
}

/**
 * Send a notice to a channel from the server.
 * TODO: Move this method to xServer.
 */
bool chanfix::serverNotice( Channel* theChannel, const std::string& Message)
{
std::stringstream s;
s	<< MyUplink->getCharYY()
	<< " O "
	<< theChannel->getName()
	<< " :"
	<< Message
	<< std::ends;

Write( s );
return false;
}

bool chanfix::logAdminMessage(const char* format, ... )
{
char buf[ 1024 ] = { 0 } ;
va_list _list ;
va_start( _list, format ) ;
vsnprintf( buf, 1024, format, _list ) ;
va_end( _list ) ;

// Try and locate the relay channel.
if (sendConsoleNotices) {
  Channel* tmpChan = Network->findChannel(consoleChan);
  if (!tmpChan)
    return false;

  std::string message = std::string( "[" ) + nickName + "] " + buf ;
  serverNotice(tmpChan, message);
}

/* Everything sent here is also logged to a file on disk */
if (adminLog.is_open()) {
  std::string theLog = std::string( "[" ) + tsToDateTime(currentTime(), true) + "] " + buf ;
  adminLog << theLog << std::endl;
}

return true;
}

bool chanfix::logDebugMessage(const char* format, ... )
{
char buf[ 1024 ] = { 0 } ;
va_list _list ;
va_start( _list, format ) ;
vsnprintf( buf, 1024, format, _list ) ;
va_end( _list ) ;

// Try and locate the relay channel.
Channel* tmpChan = Network->findChannel(consoleChan);
if (!tmpChan)
  return false;

std::string message = std::string( "[" ) + nickName + "] " + buf ;
serverNotice(tmpChan, message);

/* Everything sent here is also logged to a file on disk */
if (debugLog.is_open()) {
  std::string theLog = std::string( "[" ) + tsToDateTime(currentTime(), true) + "] " + buf ;
  debugLog << theLog << std::endl;
}

return true;
}

void chanfix::SendTo(iClient* theClient, const std::string& theMessage)
{
sqlcfUser* theUser = isAuthed(theClient->getAccount());

if (theUser && !theUser->getUseNotice())
  Message(theClient, theMessage);
else
  Notice(theClient, theMessage);
}

char *chanfix::convertToAscTime(time_t NOW)
{
  time_t *tNow = &NOW;
  struct tm* Now = gmtime(tNow);
  char *ATime = asctime(Now);
  ATime[strlen(ATime)-1] = '\0';
  return ATime;
}

void chanfix::SendFmtTo(iClient* theClient, const std::string& theMessage)
{
char buffer[512] = { 0 };
char *b = buffer ;
const char *m = 0 ;

sqlcfUser* theUser = isAuthed(theClient->getAccount());

for (m = theMessage.c_str(); *m != 0; m++) {
  if (*m == '\n' || *m == '\r') {
    *b='\0';
    if (theUser) {
      if (!theUser->getUseNotice())
	MyUplink->Write("%s P %s :%s\r\n",
		getCharYYXXX().c_str(),
		theClient->getCharYYXXX().c_str(),
		buffer);
      else
	MyUplink->Write("%s O %s :%s\r\n",
		getCharYYXXX().c_str(),
		theClient->getCharYYXXX().c_str(),
		buffer);
    }
    b=buffer;
  }
  else if (b < buffer + 509)
    *(b++)=*m;
}

*b='\0'; // What's this for? 'b' isn't used anymore.

if (theUser) {
  if (!theUser->getUseNotice())
    MyUplink->Write("%s P %s :%s\r\n",
		    getCharYYXXX().c_str(),
		    theClient->getCharYYXXX().c_str(),
		    buffer);
  else
    MyUplink->Write("%s O %s :%s\r\n",
		    getCharYYXXX().c_str(),
		    theClient->getCharYYXXX().c_str(),
		    buffer);
}

return;
}

void chanfix::SendTo(iClient* theClient, const char *Msg, ...)
{
char buffer[ 1024 ] = { 0 } ;
va_list list;

va_start( list, Msg ) ;
vsprintf( buffer, Msg, list ) ;
va_end( list ) ;

sqlcfUser* theUser = isAuthed(theClient->getAccount());

if (theUser && !theUser->getUseNotice())
  Message(theClient, "%s", buffer);
else
  Notice(theClient, "%s", buffer);
}

void chanfix::doSqlError(const std::string& theQuery, const std::string& theError)
{
/* First, log it to error out */
elog	<< "SQL> Whilst executing: "
	<< theQuery
	<< std::endl;
elog	<< "SQL> "
	<< theError
	<< std::endl;
return;
}

void chanfix::precacheChanOps()
{
elog << "*** [chanfix::precacheChanOps] Precaching chanops." << std::endl;

/* Get a connection instance to our backend */
//dbHandle* cacheCon = theManager->getConnection();

/* Check for the backup table. If it exists, something went wrong. */
/* SELECT count(*) FROM pg_tables WHERE tablename = 'chanOpsBackup' */
/* SELECT chanOpsBackup FROM information_schema.tables */
if (!localDBHandle->Exec("SELECT count(*) FROM pg_tables WHERE tablename = 'chanOpsBackup'",true)) {
  elog	<< "*** [chanfix::precacheChanOps]: Error checking for backup table presence: " 
		<< localDBHandle->ErrorMessage()
		<< std::endl;
  return;
}

if (localDBHandle->Tuples() && atoi(localDBHandle->GetValue(0, 0))) {
  elog	<< "*** [chanfix::precacheChanOps]: Backup table still exists! "
	<< "Something must have gone wrong on the last update. Exiting..."
	<< std::endl;
  ::exit(0);
}

/* Retrieve the list of chanops */
std::stringstream theQuery;
std::string lastChan = "";
theQuery	<< "SELECT channel,account,last_seen_as,ts_firstopped,ts_lastopped,day0,day1,day2,day3,day4,day5,day6,day7,day8,day9,day10,day11,day12,day13 FROM chanOps ORDER BY channel ASC, ts_firstopped ASC"
		;

elog		<< "*** [chanfix::precacheChanOps]: Loading chanOps and their points ..." 
		<< std::endl;

if (localDBHandle->Exec(theQuery.str(),true)) {
  for (unsigned int i = 0 ; i < localDBHandle->Tuples(); i++) {
     sqlChanOp* newOp = new (std::nothrow) sqlChanOp(theManager);
     assert( newOp != 0 ) ;

     newOp->setAllMembers(localDBHandle, i);
     if (newOp->getChannel().c_str() != lastChan.c_str()) {
        lastChan = newOp->getChannel();
        newOp->setIsOldestOp(true);
     }
     sqlChanOpsType::iterator ptr = sqlChanOps.find(newOp->getChannel());
     if (ptr != sqlChanOps.end()) {
       ptr->second.insert(sqlChanOpsType::mapped_type::value_type(newOp->getAccount(),newOp));
     } else {
       sqlChanOpsType::mapped_type theMap;
       theMap.insert(sqlChanOpsType::mapped_type::value_type(newOp->getAccount(), newOp));
       sqlChanOps.insert(sqlChanOpsType::value_type(newOp->getChannel(), theMap));
     }
  }
} else {
  elog	<< "*** [chanfix::precacheChanOps] Error executing query: "
	<< localDBHandle->ErrorMessage()
	<< std::endl;
  ::exit(0);
}


elog	<< "*** [chanfix::precacheChanOps]: Done. Loaded "
	<< sqlChanOps.size()
	<< " chanops."
	<< std::endl;

/* Dispose of our connection instance */
//theManager->removeConnection(cacheCon);

return;
}

void chanfix::precacheChannels()
{
elog << "*** [chanfix::precacheChannels] Precaching channels." << std::endl;

/* Get a connection instance to our backend */
dbHandle* cacheCon = localDBHandle;

/* Retrieve the list of channels */
std::stringstream theQuery;
theQuery	<< "SELECT id, channel, flags FROM channels"
		;

elog		<< "*** [chanfix::precacheChannels]: Loading channels ..."
		<< std::endl;

if (cacheCon->Exec(theQuery.str(),true)) {
  for (unsigned int i = 0; i < cacheCon->Tuples(); i++) {
     sqlChannel* newChan = new (std::nothrow) sqlChannel(theManager);
     assert( newChan != 0 ) ;

     newChan->setAllMembers(cacheCon, i);
     sqlChanCache.insert(sqlChannelCacheType::value_type(newChan->getChannel(), newChan));
  }
} else {
  elog << "*** [chanfix::precacheChannels] Error executing query: "
    << cacheCon->ErrorMessage()
    << std::endl;
  ::exit(0);
}

elog	<< "*** [chanfix::precacheChannels]: Done. Loaded "
	<< sqlChanCache.size()
	<< " channels."
	<< std::endl;

/* Dispose of our connection instance */
//theManager->removeConnection(cacheCon);

return;
}

void chanfix::precacheUsers()
{
elog << "*** [chanfix::precacheUsers] Precaching users." << std::endl;

/* Get a connection instance to our backend */
dbHandle* cacheCon = localDBHandle;

if (!cacheCon)
	  elog  << "*** [chanfix::precacheUsers]: Error getting a new SQL connection through the manager."
                << std::endl;

/* Retrieve the list of chanops */
std::stringstream myQuery;


myQuery	<< "SELECT id, user_name, created, last_seen, last_updated, last_updated_by, language_id, faction, flags, issuspended, usenotice, needoper "
		<< "FROM users"
		;


if (cacheCon->Exec(myQuery.str(),true)) {

  /* First we need to clear the current cache. */
  for (usersMapType::iterator itr = usersMap.begin();
       itr != usersMap.end(); ++itr) {
    delete itr->second;
  }
  usersMap.clear();

  for (unsigned int i = 0; i < cacheCon->Tuples(); ++i) {
    sqlcfUser *newUser = new sqlcfUser(theManager);
    assert(newUser != 0);

    newUser->setAllMembers(cacheCon, i);
    usersMap.insert(usersMapType::value_type(newUser->getUserName(), newUser));
  }


} else {
  elog << "*** [chanfix::precacheUsers] Error executing query: "
    << cacheCon->ErrorMessage()
    << std::endl;
  ::exit(0);
}

/* Load up the host cache */
for (usersMapType::iterator itr = usersMap.begin();
     itr != usersMap.end(); ++itr) {
  itr->second->loadHostList(cacheCon);

}
	
elog	<< "chanfix::precacheUsers> Loaded "
	<< usersMap.size()
	<< " users."
	<< std::endl;

/* Dispose of our connection instance */
//theManager->removeConnection(cacheCon);

return;
}

void chanfix::changeState(STATE newState)
{
if (currentState == newState) return;

/* Start our timer. */
Timer stateTimer;
stateTimer.Start();

/* First, do what we need to exit our current state */
switch( currentState ) {
	case BURST:
	{
	elog	<< "chanfix::changeState> Exiting state BURST"
		<< std::endl;
	break;
	}
	case RUN:
	{
	elog	<< "chanfix::changeState> Exiting state RUN"
		<< std::endl;
	break;
	}
	case SPLIT:
	{
	elog	<< "chanfix::changeState> Exiting state SPLIT"
		<< std::endl;
	break;
	}
	case INIT:
	{
	elog	<< "chanfix::changeState> Exiting state INIT"
		<< std::endl;
	break;
	}
}

currentState = newState;

switch( currentState ) {
	case BURST:
	{
	elog	<< "chanfix::changeState> Entering state BURST"
		<< std::endl;
	break;
	}
	case RUN:
	{
	elog	<< "chanfix::changeState> Entering state RUN"
		<< std::endl;
	break;
	}
	case SPLIT:
	{
	elog	<< "chanfix::changeState> Entering state SPLIT"
		<< std::endl;
	break;
	}
	case INIT:
	{
	elog	<< "chanfix::changeState> Entering state INIT"
		<< std::endl;
	break;
	}

}

elog	<< "Changed state in: "
	<< stateTimer.stopTimeMS()
	<< "ms"
	<< std::endl;
}

sqlChanOp* chanfix::findChanOp(const std::string& channel, const std::string& account)
{
sqlChanOpsType::iterator ptr = sqlChanOps.find(channel);
if (ptr != sqlChanOps.end()) {
  sqlChanOpsType::mapped_type::iterator chanOp = ptr->second.find(account);
  if (chanOp != ptr->second.end())
    /* elog	<< "chanfix::findChanOp> DEBUG: We've got a winner: "
     *	<< chanOp->second->getAccount() << " on " << chanOp->second->getChannel() << "!!" << std::endl;
     */
    return chanOp->second ;
}

return 0;
}

sqlChanOp* chanfix::newChanOp(const std::string& channel, const std::string& account)
{
sqlChanOp* newOp = new (std::nothrow) sqlChanOp(theManager);
assert( newOp != 0 ) ;

/* elog << "chanfix::newChanOp> DEBUG: Added new operator: " << account << " on " << channel << "!!" << std::endl; */

newOp->setChannel(channel);
newOp->setAccount(account);
newOp->setTimeFirstOpped(currentTime());
newOp->setTimeLastOpped(currentTime());

sqlChanOpsType::iterator ptr = sqlChanOps.find(channel);
if (ptr != sqlChanOps.end()) {
  ptr->second.insert(sqlChanOpsType::mapped_type::value_type(account, newOp));
} else {
  sqlChanOpsType::mapped_type theMap;
  theMap.insert(sqlChanOpsType::mapped_type::value_type(account, newOp));
  sqlChanOps.insert(sqlChanOpsType::value_type(channel, theMap));
}

return newOp;
}

sqlChanOp* chanfix::findChanOp(Channel* theChan, iClient* theClient) 
{
return findChanOp(theChan->getName(), theClient->getAccount());
}

sqlChanOp* chanfix::newChanOp(Channel* theChan, iClient* theClient)
{
return newChanOp(theChan->getName(), theClient->getAccount());
}

chanfix::chanOpsType chanfix::getMyOps(const std::string &channel, bool newScore)
{
chanOpsType myOps;
time_t oldestTS = currentTime();

sqlChanOpsType::iterator ptr = sqlChanOps.find(channel);

if (ptr != sqlChanOps.end()) {

#ifdef ENABLE_NEWSCORES
  for (sqlChanOpsType::mapped_type::iterator chOp = ptr->second.begin();
       chOp != ptr->second.end(); chOp++) {
            if (chOp->second->getTimeFirstOpped() < oldestTS)
               oldestTS = chOp->second->getTimeFirstOpped();
  }
#endif
     
  for (sqlChanOpsType::mapped_type::iterator chanOp = ptr->second.begin();
       chanOp != ptr->second.end(); chanOp++) {
    /* elog	<< "chanfix::findChanOp> DEBUG: We've got a winner: "
     *	<< chanOp->second->getAccount() << " on " << chanOp->second->getChannel() << "!!" << std::endl;
     */
#ifdef ENABLE_NEWSCORES
    if (chanOp->second->getTimeFirstOpped() == oldestTS) {
       chanOp->second->setIsOldestOp(true);
    } else {
       chanOp->second->setIsOldestOp(false);
    }
    chanOp->second->setBonus(getNewScore(chanOp->second,oldestTS));
#endif
    
    myOps.push_back(chanOp->second);
  }
}
#ifdef ENABLE_NEWSCORES
if (newScore)
  myOps.sort(compare_points_new);
else
  myOps.sort(compare_points);
#else
myOps.sort(compare_points);
#endif

return myOps;
}

chanfix::chanOpsType chanfix::getMyOps(Channel* theChan)
{
return getMyOps(theChan->getName(), true);
}

chanfix::chanOpsType chanfix::getMyOps(const std::string &channel) {
    return getMyOps(channel, true);
}

size_t chanfix::countMyOps(const std::string& channel)
{
sqlChanOpsType::iterator ptr = sqlChanOps.find(channel);
if (ptr != sqlChanOps.end())
  return ptr->second.size();

return 0;
}

size_t chanfix::countMyOps(Channel* theChan)
{
return countMyOps(theChan->getName());
}

const std::string chanfix::getHostList( sqlcfUser* User)
{
/* Get a connection instance to our backend */
dbHandle* cacheCon = localDBHandle;

/* Grab the user's host list */
static const char* queryHeader
	= "SELECT host FROM hosts WHERE user_id = ";

std::stringstream theQuery;
theQuery	<< queryHeader 
		<< User->getID()
		;

if (!cacheCon->Exec(theQuery.str(),true)) {
  elog	<< "chanfix::getHostList> SQL Error: "
	<< cacheCon->ErrorMessage()
	<< std::endl;
  return 0;
}

// SQL Query succeeded
std::stringstream hostlist;
for (unsigned int i = 0 ; i < cacheCon->Tuples(); i++)
	{
	if (!i)
	  hostlist << cacheCon->GetValue(i, 0);
	else
	  hostlist << ", " << cacheCon->GetValue(i, 0);
	}
if (hostlist.str() == "") hostlist << "None.";

/* Dispose of our connection instance */
//theManager->removeConnection(cacheCon);

return hostlist.str();
}

const std::string escapeSQLChars(const std::string& theString)
{
std::string retMe ;

for( std::string::const_iterator ptr = theString.begin() ;
	ptr != theString.end() ; ++ptr )
	{
	if( *ptr == '\'' )
		{
		retMe += "\\\047" ;
		}
	else if ( *ptr == '\\' )
		{
		retMe += "\\\134" ;
		}
	else
		{
		retMe += *ptr ;
		}
	}
return retMe ;
}

bool atob( std::string str )
{
str = string_lower(str);
if (str == "y" || str == "true" || str == "yes")
  return true;
return false;
}

void chanfix::givePoints(Channel* theChan, iClient* theClient)
{
//No points for unidented clients
if (clientNeedsIdent && !hasIdent(theClient))
  return;

sqlChanOp* thisOp = findChanOp(theChan, theClient);
if (!thisOp) {
  size_t numMyOps = countMyOps(theChan);
  if ((numMyOps >= MAXOPCOUNT) || (!numMyOps && !chanServLinked))
    return; /* No room for new ops or channel service not linked */
  thisOp = newChanOp(theChan, theClient);
  gotOpped(theChan, theClient);
}

thisOp->addPoint();
thisOp->setTimeLastOpped(currentTime()); //Update the time they were last opped

/* elog	<< "chanfix::givePoints> DEBUG: Gave " << thisOp->getAccount()
 *	<< " on " << thisOp->getChannel() << " a point."
 *	<< std::endl;
 */
}

void chanfix::gotOpped(Channel* thisChan, iClient* thisClient)
{
//No tracking for unidented clients
if (clientNeedsIdent && !hasIdent(thisClient))
  return;

/* elog	<< "chanfix::gotOpped> DEBUG: " << thisClient->getAccount()
 *	<< " got opped on " << thisChan->getName()
 *	<< std::endl;
 */
sqlChanOp* thisOp = findChanOp(thisChan, thisClient);
if (!thisOp) {
  size_t numMyOps = countMyOps(thisChan);
  if ((numMyOps >= MAXOPCOUNT) || (!numMyOps && !chanServLinked))
    return; /* No room for new ops or channel service not linked */
  thisOp = newChanOp(thisChan, thisClient);
} else {
  thisOp->setTimeLastOpped(currentTime());
}

thisOp->setLastSeenAs(thisClient->getRealNickUserHost());

clientOpsType* myOps = findMyOps(thisClient);
if (myOps != NULL && !myOps->empty()) {
  clientOpsType::iterator ptr = myOps->find(thisChan->getName());
  if (ptr != myOps->end()) {
    return;
  }
}

myOps->insert(clientOpsType::value_type(thisChan->getName()));
thisClient->removeCustomData(this);
thisClient->setCustomData(this, static_cast< void*>(myOps));
return;
}

void chanfix::lostOp(const std::string& channel, iClient* theClient, clientOpsType* myOps)
{
if (myOps == NULL)
  myOps = findMyOps(theClient);

if (myOps->empty())
  return;

clientOpsType::iterator ptr = myOps->find(channel);
if (ptr != myOps->end()) {
  myOps->erase(ptr);
  theClient->removeCustomData(this);
  if (!myOps->empty())
    theClient->setCustomData(this, static_cast< void*>(myOps));
}
}

bool chanfix::hasIdent(iClient* theClient)
{
std::string userName = theClient->getUserName();
if (userName[0] == '~')
  return false;
return true;
}

void chanfix::checkNetwork()
{
if (100 * Network->serverList_size() < numServers * minServersPresent) {
  if (currentState != SPLIT) {
    elog << "chanfix::checkNetwork> DEBUG: Not enough servers linked! Going to SPLIT-state" << std::endl;
    changeState(SPLIT);
  }
  return;
}

if (currentState == SPLIT) {
  elog << "chanfix::checkNetwork> DEBUG: Enough servers linked! Going to BURST-state" << std::endl;
  changeState(BURST);
  return;
}
}

void chanfix::checkChannelServiceLink(iServer* theServer, const eventType& theEvent)
{
if (string_lower(theServer->getName()) == string_lower(chanServName)) {
  if (theEvent == EVT_NETJOIN)
    chanServLinked = true;
  else if (theEvent == EVT_NETBREAK)
    chanServLinked = false;
}
return;
}

void chanfix::JoinChan(Channel* theChan)
{
MyUplink->JoinChannel(this, theChan->getName());
}

void chanfix::PartChan(Channel* theChan)
{
MyUplink->PartChannel(this, theChan->getName(), "");
}

/* Check for the channel service server to see if it is linked. */
void chanfix::findChannelService()
{
iServer* curServer = 0;
for (xNetwork::serverIterator ptr = Network->servers_begin();
     ptr != Network->servers_end(); ptr++) {
   curServer = ptr->second;
   if ((curServer == NULL) || (Network->findFakeServer(curServer) != 0))
     continue;
   if (string_lower(curServer->getName()) == string_lower(chanServName)) {
     chanServLinked = true;
     break;
   }
}
return;
}

const int chanfix::getLastFix(sqlChannel* theChan)
{
/* Get a connection instance to our backend */
dbHandle* cacheCon = localDBHandle;

/* Grab the user's host list */
static const char* queryHeader
	= "SELECT ts,event FROM notes WHERE channelID = ";

std::stringstream theQuery;
theQuery	<< queryHeader 
		<< theChan->getID()
		;

if (!cacheCon->Exec(theQuery.str(),true)) {
  elog	<< "chanfix::getHostList> SQL Error: "
	<< cacheCon->ErrorMessage()
	<< std::endl;
  return 0;
}

int max_ts = 0;
int ts = 0;
int event;

// SQL Query succeeded
std::stringstream notes;
for (unsigned int i = 0 ; i < cacheCon->Tuples(); i++)
{
	ts = atoi(cacheCon->GetValue(i, 0));
	event = atoi(cacheCon->GetValue(i, 1));

	if ((event == sqlChannel::EV_CHANFIX) || (event == sqlChannel::EV_REQUESTOP)) {
		if (ts > max_ts)
			max_ts = ts;
	}
}

/* Dispose of our connection instance */
//theManager->removeConnection(cacheCon);

return max_ts;
}

void chanfix::autoFix()
{
/* If autofixing has been disabled, well, forget it. */
if (!enableAutoFix) {
  //elog << "chanfix::autoFix> DEBUG: AutoFix not enabled." << std::endl;
  return;
}

/* If there are too many servers split, don't autofix. */
if (currentState != RUN) {
  //elog << "chanfix::autoFix> DEBUG: currentState != RUN" << std::endl;
  return;
}

/* Start our timer. */
Timer autoFixTimer;
autoFixTimer.Start();

/* Now walk through all channels to find the opless ones. */
Channel* thisChan;
ChannelUser* curUser;
int numOpLess = 0;
tempBlockType::iterator tbPtr;
for (xNetwork::channelIterator ptr = Network->channels_begin(); ptr != Network->channels_end(); ptr++) {
   thisChan = ptr->second;
   bool opLess = true;
   bool hasService = false;

   // Do not autofix +R channels
   if (thisChan->getMode(Channel::MODE_REG))
     continue;

   if (thisChan->size() >= minClients && !isBeingFixed(thisChan)) {
     /* Don't autofix if the chan is temp blocked */
     tbPtr = tempBlockList.find(thisChan->getName());
     if (tbPtr != tempBlockList.end()) continue;

     /* Loop through the channel list to check users for op and umode +k */
     for (Channel::userIterator ptr = thisChan->userList_begin(); ptr != thisChan->userList_end(); ptr++) {
	curUser = ptr->second;
	if (curUser->getClient()->getMode(iClient::MODE_SERVICES)) {
	  hasService = true;
	  break;
	}
	if (curUser->isModeO())
	  opLess = false;
     }
     if (opLess && !hasService) {
       chanOpsType myOps = getMyOps(thisChan);
       if (myOps.empty())
	 continue;

       sqlChannel* sqlChan = getChannelRecord(thisChan);
       if (!sqlChan) sqlChan = newChannelRecord(thisChan);

       if (myOps.begin() != myOps.end())
	 sqlChan->setMaxScore((*myOps.begin())->getPoints() + (*myOps.begin())->getBonus());

       if ((sqlChan->getMaxScore() > 
	   static_cast<int>(static_cast<float>(FIX_MIN_ABS_SCORE_END)
	   * MAX_SCORE)) && !sqlChan->getFlag(sqlChannel::F_BLOCKED) &&
	   !isTempBlocked(thisChan->getName())) {
	 elog << "chanfix::autoFix> DEBUG: " << thisChan->getName() << " is opless, fixing." << std::endl;
	 autoFixQ.insert(fixQueueType::value_type(thisChan->getName(), currentTime()));
	 numOpLess++;

	 if (doJoinChannels() && shouldCJoin(sqlChan, true))
	   JoinChan(thisChan);
	 if (doAutoFixNotice() && shouldCJoin(sqlChan, true))
	   Message(thisChan, "Automatic channel fix in progress, please stand by.");
       }
     }
   }
}

//elog << "chanfix::autoFix> DEBUG: Found " << numOpLess << " of "
//	<< Network->channelList_size() << " channels in "
//	<< autoFixTimer.stopTimeMS() << " ms." << std::endl;
}

void chanfix::manualFix(Channel* thisChan)
{
/* If the channel doesn't exist (anymore), don't try to fix it. */
if (!thisChan) return;

elog << "chanfix::manualFix> DEBUG: Manual fix " << thisChan->getName() << "!" << std::endl;

if (useBurstToFix && thisChan->getCreationTime() > 1) {
  if (version >= 12) /* temporary fix until GNUWorld is fixed */
    MyUplink->setBursting(true);

  xServer::modeVectorType modeVector;
  if (thisChan->getMode(Channel::MODE_I))
    modeVector.push_back(std::make_pair(false, Channel::MODE_I));
  if (thisChan->getMode(Channel::MODE_K))
    MyUplink->OnChannelModeK(thisChan, false, 0, std::string());
  if (thisChan->getMode(Channel::MODE_L))
    MyUplink->OnChannelModeL(thisChan, false, 0, 0);
  if (thisChan->getMode(Channel::MODE_R))
    modeVector.push_back(std::make_pair(false, Channel::MODE_R));
  if (thisChan->getMode(Channel::MODE_D))
    modeVector.push_back(std::make_pair(false, Channel::MODE_D));
  /* Due to a bug in .11, we need to set at least one mode. */
  if (version < 12) {
    if (!thisChan->getMode(Channel::MODE_N))
      modeVector.push_back(std::make_pair(true, Channel::MODE_N));
    if (!thisChan->getMode(Channel::MODE_T))
      modeVector.push_back(std::make_pair(true, Channel::MODE_T));
  }
  if (!modeVector.empty())
    MyUplink->OnChannelMode(thisChan, 0, modeVector);

  MyUplink->BurstChannel(thisChan->getName(), thisChan->getModeString(),
			 thisChan->getCreationTime() - 1);

  if (version >= 12)
    MyUplink->setBursting(false);
} else {
  ClearMode(thisChan, "obiklrD", true);
}

sqlChannel* sqlChan = getChannelRecord(thisChan);
if (!sqlChan) sqlChan = newChannelRecord(thisChan);

if (doJoinChannels() && shouldCJoin(sqlChan, false))
  JoinChan(thisChan);
if (doManualFixNotice()  && shouldCJoin(sqlChan, false))
  Message(thisChan, "Channel fix in progress, please stand by.");

manFixQ.insert(fixQueueType::value_type(thisChan->getName(), currentTime() + CHANFIX_DELAY));
}


void chanfix::insertop(sqlChanOp* curOp, sqlChannel* sqlChan)
{
  simOppedStruct curStruct;

  curStruct.account = curOp->getAccount();
  curStruct.channel = sqlChan->getChannel();
      
  simMap.insert(SimMapType::value_type(sqlChan->getChannel(), curStruct));

  return;
}

bool chanfix::findop(sqlChanOp* curOp, sqlChannel* sqlChan)
{
  for (SimMapType::iterator ptr = simMap.begin();
       ptr != simMap.end(); ptr++) {

    if ((ptr->first == sqlChan->getChannel()) && (ptr->second.account == curOp->getAccount()))
      return true;
  }

  return false;
}

void chanfix::removechan(sqlChannel* sqlChan)
{
  simMap.erase(sqlChan->getChannel());
  return;
}

bool chanfix::simFix(sqlChannel* sqlChan, bool autofix, time_t c_Time, iClient* theClient, sqlcfUser* theUser)
{
if (sqlChan->getSimStart() == 0) sqlChan->setSimStart(c_Time);
sqlChan->setLastSimAttempt(c_Time);

Channel* netChan = Network->findChannel(sqlChan->getChannel());
if (!netChan) return true;

chanOpsType myOps = getMyOps(netChan);

if (myOps.begin() != myOps.end())
  sqlChan->setTMaxScore((*myOps.begin())->getPoints() + (*myOps.begin())->getBonus());

int maxScore = sqlChan->getTMaxScore();
if (maxScore <= FIX_MIN_ABS_SCORE_END * MAX_SCORE)
  return false;

unsigned int maxOpped = (autofix ? AUTOFIX_NUM_OPPED : CHANFIX_NUM_OPPED);

unsigned int currentOps = sqlChan->getAmountSimOpped();
if (currentOps >= maxOpped)
  return true;

int time_since_start;
if (autofix)
  time_since_start = c_Time - sqlChan->getSimStart();
else
  time_since_start = c_Time - (sqlChan->getSimStart() + CHANFIX_DELAY);

int max_time = (autofix ? AUTOFIX_MAXIMUM : CHANFIX_MAXIMUM);
int min_score_abs = static_cast<int>((MAX_SCORE *
		static_cast<float>(FIX_MIN_ABS_SCORE_BEGIN)) -
		static_cast<float>(time_since_start) /
		static_cast<float>(max_time) *
		(MAX_SCORE * static_cast<float>(FIX_MIN_ABS_SCORE_BEGIN)
		 - static_cast<float>(FIX_MIN_ABS_SCORE_END) * MAX_SCORE));

int min_score_rel = static_cast<int>((maxScore *
		static_cast<float>(FIX_MIN_REL_SCORE_BEGIN)) -
		static_cast<float>(time_since_start) /
		static_cast<float>(max_time) *
		(maxScore * static_cast<float>(FIX_MIN_REL_SCORE_BEGIN)
		 - static_cast<float>(FIX_MIN_REL_SCORE_END) * maxScore));

int min_score = min_score_abs;
if (min_score_rel > min_score)
  min_score = min_score_rel;

if (myOps.begin() != myOps.end())
  sqlChan->setTMaxScore((*myOps.begin())->getPoints() + (*myOps.begin())->getBonus());

if (sqlChan->getTMaxScore() < min_score)
  min_score = sqlChan->getTMaxScore();

unsigned int amtopped;
iClient* curClient = 0;
sqlChanOp* curOp = 0;
acctListType acctToOp;
std::string modes = "+";
std::string args;
unsigned int numClientsToOp = 0;
bool cntMaxedOut = false;
for (chanOpsType::iterator opPtr = myOps.begin(); opPtr != myOps.end();
     opPtr++) {
  curOp = *opPtr;
  if ((curOp->getPoints() + curOp->getBonus()) >= min_score) {
    acctToOp = findAccount(netChan, curOp->getAccount());
    std::vector< iClient* >::const_iterator acctPtr = acctToOp.begin(),
	end = acctToOp.end();
    while (acctPtr != end) {
      curClient = *acctPtr;
      if (curClient && !findop(curOp, sqlChan)) {
        amtopped = sqlChan->getAmountSimOpped();
        amtopped++;
        sqlChan->setAmountSimOpped(amtopped);

	insertop(curOp, sqlChan);
	if (!args.empty())
	  args += " ";
	args += curClient->getNickName();

	if ((++numClientsToOp + currentOps) >= maxOpped) {
	  cntMaxedOut = true;
	  break;
	}
      }
      ++acctPtr;
    }
    acctToOp.clear();
    if (cntMaxedOut)
      break;
  }
}

std::stringstream chanStatus;
std::stringstream chanModes;

if (netChan->getMode(Channel::MODE_I)) chanModes << "i";
if (netChan->getMode(Channel::MODE_K)) chanModes << "k";
if (netChan->getMode(Channel::MODE_L)) chanModes << "l";
if (netChan->getMode(Channel::MODE_R)) chanModes << "r";
if (netChan->getMode(Channel::MODE_D)) chanModes << "d";


chanStatus      << "* Channel Status for "
		<< netChan->getName() << ": "
		<< netChan->banList_size()
                << " bans -- Restrictive Modes: +"
		<< chanModes.str()
                ;

SendTo(theClient, chanStatus.str().c_str());

if ((!numClientsToOp || maxScore < min_score) &&
    (!autofix || !(numClientsToOp + currentOps))) {
  if (autofix && !sqlChan->getSimModesRemoved()) {

    if (netChan->banList_size() ||
        netChan->getMode(Channel::MODE_I) ||
        netChan->getMode(Channel::MODE_K) ||
        netChan->getMode(Channel::MODE_L) ||
        netChan->getMode(Channel::MODE_R) ||
        netChan->getMode(Channel::MODE_D)) {

      sqlChan->setSimModesRemoved(true);
      SendTo(theClient,
                getResponse(theUser,
                                 language::sim_modes_removed,
                                 std::string("(%s) Channel modes have been removed.")).c_str(),
                                 tsToDateTime(c_Time, true).c_str());
    }
  }
return false;
}


if (numClientsToOp) {
  SendTo(theClient,
            getResponse(theUser,
                             language::sim_opping,
                             std::string("(%s) Opping: %s (%d Clients)")).c_str(),
                             tsToDateTime(c_Time, true).c_str(), args.c_str(), numClientsToOp);
}

if (numClientsToOp + currentOps >= netChan->size() ||
    numClientsToOp + currentOps >= maxOpped)
  return true;

return false;
}

bool chanfix::simulateFix(sqlChannel* sqlChan, bool autofix, iClient* theClient, sqlcfUser* theUser)
{
  bool isFixed = false;
  time_t t = getNextFix();
  time_t end_fix = t + 86400; /* 1 Day */
  time_t next_fix = t + PROCESS_QUEUE_TIME;

  sqlChan->setSimStart(0);
  sqlChan->setLastSimAttempt(0);
  sqlChan->setAmountSimOpped(0);
  sqlChan->setSimModesRemoved(false);

  /* Modes are always removed straight when a CHANFIX command is issued
     for a channel (manual fix). */
  if (autofix == false)
    SendTo(theClient,
              getResponse(theUser,
                               language::sim_modes_removed,
                               std::string("(%s) Channel modes have been removed.")).c_str(),
                               tsToDateTime(currentTime(), true).c_str());

  while (t) {
    if (next_fix == t) {
      if (autofix) {
        if (t - sqlChan->getSimStart() > AUTOFIX_MAXIMUM) {
          isFixed = simFix(sqlChan, autofix, t, theClient, theUser);
        }
      } else {
        if (t - sqlChan->getSimStart() > CHANFIX_MAXIMUM) {
          isFixed = simFix(sqlChan, autofix, t, theClient, theUser);
        }
      }

      next_fix = t + PROCESS_QUEUE_TIME;
    }

    if (isFixed || (t == end_fix))
      break;
    else
      t++;
  }

  /* Cleanup */
  removechan(sqlChan);

  sqlChan->setSimStart(0);
  sqlChan->setLastSimAttempt(0);
  sqlChan->setAmountSimOpped(0);
  sqlChan->setSimModesRemoved(false);

  return isFixed;
}



bool chanfix::shouldCJoin(sqlChannel* sqlChan, bool autofix)
{
bool joinchan = false;
int maxScore;
time_t lastattempt;
time_t fixstart;

/* coder notes (mostly so i dont forget -sirv)
 * if we use this function for simulation then fix start is the both
 * these times will need to be set as temp vars in sqlChan
 */
fixstart = currentTime();
lastattempt = currentTime();

Channel* netChan = Network->findChannel(sqlChan->getChannel());
if (!netChan) return joinchan;

chanOpsType myOps = getMyOps(netChan);

if (myOps.begin() != myOps.end())
  sqlChan->setTMaxScore((*myOps.begin())->getPoints() + (*myOps.begin())->getBonus());

maxScore = sqlChan->getTMaxScore();

if (maxScore <= FIX_MIN_ABS_SCORE_END * MAX_SCORE)
  return joinchan;

unsigned int maxOpped = (autofix ? AUTOFIX_NUM_OPPED : CHANFIX_NUM_OPPED);
unsigned int currentOps = countChanOps(netChan);

if (currentOps >= maxOpped) {
  return joinchan;
} 

int time_since_start;
if (autofix)
  time_since_start = currentTime() - fixstart;
else
  time_since_start = currentTime() - (fixstart + CHANFIX_DELAY);

int max_time = (autofix ? AUTOFIX_MAXIMUM : CHANFIX_MAXIMUM);

int min_score_abs = static_cast<int>((MAX_SCORE *
		static_cast<float>(FIX_MIN_ABS_SCORE_BEGIN)) -
		static_cast<float>(time_since_start) /
		static_cast<float>(max_time) *
		(MAX_SCORE * static_cast<float>(FIX_MIN_ABS_SCORE_BEGIN)
		 - static_cast<float>(FIX_MIN_ABS_SCORE_END) * MAX_SCORE));

int min_score_rel = static_cast<int>((maxScore *
		static_cast<float>(FIX_MIN_REL_SCORE_BEGIN)) -
		static_cast<float>(time_since_start) /
		static_cast<float>(max_time) *
		(maxScore * static_cast<float>(FIX_MIN_REL_SCORE_BEGIN)
		 - static_cast<float>(FIX_MIN_REL_SCORE_END) * maxScore));

int min_score = min_score_abs;
if (min_score_rel > min_score)
  min_score = min_score_rel;

if (myOps.begin() != myOps.end())
  sqlChan->setTMaxScore((*myOps.begin())->getPoints() + (*myOps.begin())->getBonus());

if (sqlChan->getTMaxScore() < min_score)
  min_score = sqlChan->getTMaxScore();

iClient* curClient = 0;
sqlChanOp* curOp = 0;
acctListType acctToOp;
unsigned int numClientsToOp = 0;
bool cntMaxedOut = false;
for (chanOpsType::iterator opPtr = myOps.begin(); opPtr != myOps.end();
     opPtr++) {
  curOp = *opPtr;
  if ((curOp->getPoints() + curOp->getBonus()) >= min_score) {
    acctToOp = findAccount(netChan, curOp->getAccount());
    std::vector< iClient* >::const_iterator acctPtr = acctToOp.begin(),
	end = acctToOp.end();
    while (acctPtr != end) {
      curClient = *acctPtr;
      if (curClient && !netChan->findUser(curClient)->isModeO()) {
	if ((++numClientsToOp + currentOps) >= maxOpped) {
          joinchan = true;
	  cntMaxedOut = true;
	  break;
	}
      }
      ++acctPtr;
    }
    acctToOp.clear();
    if (cntMaxedOut)
      break;
  }
}

return joinchan;
}

bool chanfix::fixChan(sqlChannel* sqlChan, bool autofix)
{
/* First update the time of the previous attempt to now. */
if (sqlChan->getFixStart() == 0) sqlChan->setFixStart(currentTime());
sqlChan->setLastAttempt(currentTime());

/* If the channel doesn't exist (anymore), the fix is successful. */
Channel* netChan = Network->findChannel(sqlChan->getChannel());
if (!netChan) return true;

chanOpsType myOps = getMyOps(netChan);

if (myOps.begin() != myOps.end())
  sqlChan->setMaxScore((*myOps.begin())->getPoints() + (*myOps.begin())->getBonus());

int maxScore = sqlChan->getMaxScore();

/* If the max score of the channel is lower than the absolute minimum
 * score required, don't even bother trying. */
if (maxScore <= FIX_MIN_ABS_SCORE_END * MAX_SCORE)
  return false;

/* Get the number of clients that should have ops */
unsigned int maxOpped = (autofix ? AUTOFIX_NUM_OPPED : CHANFIX_NUM_OPPED);

/* If the channel has enough ops, abort & return. */
unsigned int currentOps = countChanOps(netChan);
if (currentOps >= maxOpped) {
  elog << "chanfix::fixChan> DEBUG: Enough clients opped on " << netChan->getName() << std::endl;
  return true;
} 

int time_since_start;
if (autofix)
  time_since_start = currentTime() - sqlChan->getFixStart();
else
  time_since_start = currentTime() - (sqlChan->getFixStart() + CHANFIX_DELAY);

int max_time = (autofix ? AUTOFIX_MAXIMUM : CHANFIX_MAXIMUM);

/* Determine minimum score required for this time. */

/* Linear interpolation of (0, fraction_abs_max * max_score) ->
 * (max_time, fraction_abs_min * max_score)
 * at time t between 0 and max_time. */
int min_score_abs = static_cast<int>((MAX_SCORE *
		static_cast<float>(FIX_MIN_ABS_SCORE_BEGIN)) -
		static_cast<float>(time_since_start) /
		static_cast<float>(max_time) *
		(MAX_SCORE * static_cast<float>(FIX_MIN_ABS_SCORE_BEGIN)
		 - static_cast<float>(FIX_MIN_ABS_SCORE_END) * MAX_SCORE));

elog << "chanfix::fixChan> [" << netChan->getName() << "] max "
	<< MAX_SCORE << ", begin " << FIX_MIN_ABS_SCORE_BEGIN
	<< ", end " << FIX_MIN_ABS_SCORE_END << ", time "
	<< time_since_start << ", maxtime " << max_time << "."
	<< std::endl;

/* Linear interpolation of (0, fraction_rel_max * max_score_channel) ->
 * (max_time, fraction_rel_min * max_score_channel)
 * at time t between 0 and max_time. */
int min_score_rel = static_cast<int>((maxScore *
		static_cast<float>(FIX_MIN_REL_SCORE_BEGIN)) -
		static_cast<float>(time_since_start) /
		static_cast<float>(max_time) *
		(maxScore * static_cast<float>(FIX_MIN_REL_SCORE_BEGIN)
		 - static_cast<float>(FIX_MIN_REL_SCORE_END) * maxScore));

/* The minimum score needed for ops is the HIGHER of these two
 * scores. */
int min_score = min_score_abs;
if (min_score_rel > min_score)
  min_score = min_score_rel;

/* We need to check to see if the highest scoring ops score for the chan
 * is less than min_score, if so, adjust min_score so we wont have
 * to wait forever for the first op! */

if (myOps.begin() != myOps.end())
  sqlChan->setMaxScore((*myOps.begin())->getPoints() + (*myOps.begin())->getBonus());

if (sqlChan->getMaxScore() < min_score)
  min_score = sqlChan->getMaxScore();

elog << "chanfix::fixChan> [" << netChan->getName() << "] start "
	<< sqlChan->getFixStart() << ", delta " << time_since_start
	<< ", max " << maxScore << ", minabs " << min_score_abs
	<< ", minrel " << min_score_rel << "." << std::endl;

/**
 * Get the scores of the accounts of the non-opped clients.
 * Find out which clients need to be opped.
 */
iClient* curClient = 0;
sqlChanOp* curOp = 0;
acctListType acctToOp;
std::string modes = "+";
std::string args;
unsigned int numClientsToOp = 0;
bool cntMaxedOut = false;
for (chanOpsType::iterator opPtr = myOps.begin(); opPtr != myOps.end();
     opPtr++) {
  curOp = *opPtr;
// elog	<< "chanfix::fixChan> DEBUG: "
//	<< curOp->getPoints() << " >= " << min_score << std::endl;
  if ((curOp->getPoints() + curOp->getBonus()) >= min_score) {
    acctToOp = findAccount(netChan, curOp->getAccount());
    std::vector< iClient* >::const_iterator acctPtr = acctToOp.begin(),
	end = acctToOp.end();
    while (acctPtr != end) {
      curClient = *acctPtr;
      if (curClient && !netChan->findUser(curClient)->isModeO()) {
	elog	<< "chanfix::fixChan> DEBUG: Decided to op: "
		<< curClient->getNickName() << " on "
		<< netChan->getName() << ". Client has "
		<< (curOp->getPoints() + curOp->getBonus()) << " points. ABS_MIN = "
		<< min_score_abs << " and REL_MIN = " << min_score_rel
		<< std::endl;
	modes += "o";
	if (!args.empty())
	  args += " ";
	args += curClient->getNickName();
	if ((++numClientsToOp + currentOps) >= maxOpped) {
	  elog	<< "chanfix::fixChan> DEBUG: Enough clients are to be "
		<< "opped (" << numClientsToOp << "); breaking the loop."
		<< std::endl;
	  cntMaxedOut = true;
	  break;
	}
      }
      ++acctPtr;
    }
    acctToOp.clear();
    if (cntMaxedOut)
      break;
  }
}

/* If no scores are high enough, return. */
if ((!numClientsToOp || maxScore < min_score) &&
    (!autofix || !(numClientsToOp + currentOps))) {
  if (autofix && !sqlChan->getModesRemoved() &&
      needsModesRemoved(netChan)) {
    ClearMode(netChan, "biklrD", true);
    sqlChan->setModesRemoved(true);
    Message(netChan, "Channel modes have been removed.");
  }
return false;
}

/* If we need to op at least one client, op him/her. */
if (numClientsToOp) {
  Mode(netChan, modes, args, true);

  if (numClientsToOp == 1)
    Message(netChan, "1 client should have been opped.");
  else
    Message(netChan, "%d clients should have been opped.",
	    numClientsToOp);
}

/* Now see if there are enough ops; if so, the fix is complete. */
if (numClientsToOp + currentOps >= netChan->size() ||
    numClientsToOp + currentOps >= maxOpped)
  return true;

return false;
}

void chanfix::stopFixingChan(Channel* theChan, bool force)
{
/* If the channel doesn't exist (anymore), don't try to end the fix. */
if (!theChan) return;

bool inFix = false;
bool isAutoFix = false; /* false = manual - true = auto */

if ((stopAutoFixOnOp || force) && isBeingAutoFixed(theChan)) {
  isAutoFix = true;
  inFix = true;
  removeFromAutoQ(theChan);
}
if ((stopChanFixOnOp || force) && isBeingChanFixed(theChan)) {
  inFix = true;
  removeFromManQ(theChan);
}

if (inFix) {
  sqlChannel* sqlChan = getChannelRecord(theChan);
  if (sqlChan) {
    sqlChan->setFixStart(0);
    sqlChan->setLastAttempt(0);
  }
}

if (doJoinChannels())
  PartChan(theChan);

return;
}

chanfix::acctListType chanfix::findAccount(Channel* theChan, const std::string& account)
{
acctListType chanAccts;
for (Channel::userIterator ptr = theChan->userList_begin();
     ptr != theChan->userList_end(); ptr++) {
  if (account == ptr->second->getClient()->getAccount())
    chanAccts.push_back(ptr->second->getClient());
}

return chanAccts;
}

bool chanfix::accountIsOnChan(const std::string& channel, const std::string& account)
{
Channel* tmpChan = Network->findChannel(channel);
if (!tmpChan) return false;

for (Channel::userIterator ptr = tmpChan->userList_begin();
     ptr != tmpChan->userList_end(); ptr++) {
  if (account == ptr->second->getClient()->getAccount())
    return true;
}
return false;
}

const std::string chanfix::getChanNickName(const std::string& channel, const std::string& account)
{
std::string theNick = "";
Channel* tmpChan = Network->findChannel(channel);
if (!tmpChan) return "";

for (Channel::userIterator ptr = tmpChan->userList_begin();
     ptr != tmpChan->userList_end(); ptr++) {
  if (account == ptr->second->getClient()->getAccount()) {
    if (ptr->second->isModeO()) {
      theNick = "@";
      theNick += ptr->second->getClient()->getNickName();
      return theNick;
    }
    else if (ptr->second->isModeV()) {
      theNick = "+";
      theNick += ptr->second->getClient()->getNickName();
      return theNick;
    }
    else {
      theNick = ptr->second->getClient()->getNickName();
      return theNick;
    }
  }
}
return theNick;
}

sqlChannel* chanfix::getChannelRecord(const std::string& Channel)
{
sqlChannelCacheType::iterator ptr = sqlChanCache.find(Channel);
if (ptr != sqlChanCache.end()) {
  //elog << "chanfix::getChannelRecord> DEBUG: cached channel " << Channel << " found" << std::endl;
  return ptr->second;
}
return 0;
}

sqlChannel* chanfix::getChannelRecord(Channel* theChan)
{
return getChannelRecord(theChan->getName());
}

sqlChannel* chanfix::newChannelRecord(const std::string& Channel)
{
sqlChannel* newChan = new (std::nothrow) sqlChannel(theManager);
assert( newChan != 0 ) ;

newChan->setChannel(Channel);
newChan->setFixStart(0);
newChan->setLastAttempt(0);

sqlChanCache.insert(sqlChannelCacheType::value_type(Channel, newChan));
elog << "chanfix::newChannelRecord> DEBUG: Added new channel: " << Channel << std::endl;

return newChan;
}

sqlChannel* chanfix::newChannelRecord(Channel* theChan)
{
return newChannelRecord(theChan->getName());
}

bool chanfix::deleteChannelRecord(sqlChannel* sqlChan)
{
if (sqlChan->useSQL() && sqlChan->Delete(localDBHandle))
  return false;
sqlChanCache.erase(sqlChan->getChannel());
delete sqlChan; sqlChan = 0;

return !sqlChan;
}

size_t chanfix::countChanOps(const Channel* theChan)
{
if (!theChan) {
  /* Don't try this on a null channel. */
  return 0;
}

size_t chanOps = 0;

for (Channel::const_userIterator ptr = theChan->userList_begin();
     ptr != theChan->userList_end(); ++ptr)
   if (ptr->second->isModeO())
     chanOps++;

return chanOps;
}

bool chanfix::needsModesRemoved(Channel* theChan)
{
/* Modes need to be removed if +b/i/k/l/r/D is set.
 * This check should actually be more specific (checking each ban
 * to see if it matches a high scored hostmask) but this will do
 * for now.
 */
if (theChan->banList_size() ||
    theChan->getMode(Channel::MODE_I) ||
    theChan->getMode(Channel::MODE_K) ||
    theChan->getMode(Channel::MODE_L) ||
    theChan->getMode(Channel::MODE_R) ||
    theChan->getMode(Channel::MODE_D))
  return true;

return false;
}

bool chanfix::canScoreChan(Channel* theChan)
{
  // Lets leave this and just return 'true' since we're allowing +R channels to be scored
  /*
   * for (Channel::const_userIterator ptr = theChan->userList_begin();
   *  ptr != theChan->userList_end(); ++ptr)
   *if (ptr->second->getClient()->getMode(iClient::MODE_SERVICES))
   *  return false;
   */
   
return true;
}

void chanfix::processQueue()
{
/* If there are too many servers split, don't process queue. */
if (currentState != RUN) {
  //elog << "chanfix::processQueue> DEBUG: currentState != RUN" << std::endl;
  return;
}

for (fixQueueType::iterator ptr = autoFixQ.begin(); ptr != autoFixQ.end(); ) {
   //elog << "chanfix::processQueue> DEBUG: Processing " << ptr->first << " in autoFixQ ..." << std::endl;
   if (ptr->second <= currentTime()) {
     sqlChannel* sqlChan = getChannelRecord(ptr->first);
     if (!sqlChan) sqlChan = newChannelRecord(ptr->first);
     bool isFixed = false;

     if (currentTime() - sqlChan->getLastAttempt() >= AUTOFIX_INTERVAL)
       isFixed = fixChan(sqlChan, true);

     /**
      * If the channel has been fixed, or the fixing time window
      * has passed, remove it from the list
      */
     if (isFixed || currentTime() - sqlChan->getFixStart() > AUTOFIX_MAXIMUM) {
       Channel* theChan = Network->findChannel(sqlChan->getChannel());
	
       if (!theChan) {
	 //Object no longer exists
	 autoFixQ.erase(ptr++);
	 continue;
       }

       if (doJoinChannels())
         PartChan(theChan);
       autoFixQ.erase(ptr++);
       sqlChan->setFixStart(0);
       sqlChan->setLastAttempt(0);
       elog << "chanfix::processQueue> DEBUG: Channel " << sqlChan->getChannel() << " done!" << std::endl;
     } else {
       ptr->second = currentTime() + AUTOFIX_INTERVAL;
       ptr++;
       elog << "chanfix::processQueue> DEBUG: Channel " << sqlChan->getChannel() << " not done yet ..." << std::endl;
     }
   } else ptr++;
}

for (fixQueueType::iterator ptr = manFixQ.begin(); ptr != manFixQ.end(); ) {
   //elog << "chanfix::processQueue> DEBUG: Processing " << ptr->first << " in manFixQ ..." << std::endl;
   if (ptr->second <= currentTime()) {
     sqlChannel* sqlChan = getChannelRecord(ptr->first);
     if (!sqlChan) sqlChan = newChannelRecord(ptr->first);
     bool isFixed = false;

     if (currentTime() - sqlChan->getLastAttempt() >= CHANFIX_INTERVAL)
       isFixed = fixChan(sqlChan, false);

     /**
      * If the channel has been fixed, or the fixing time window
      * has passed, remove it from the list
      */
     if (isFixed || currentTime() - sqlChan->getFixStart() > CHANFIX_MAXIMUM + CHANFIX_DELAY) {
       Channel* theChan = Network->findChannel(sqlChan->getChannel());
	     
       if (!theChan) {
	 //Object no longer exists
	 manFixQ.erase(ptr++);
	 continue;
       }
	     
       if (doJoinChannels())
         PartChan(theChan);
       manFixQ.erase(ptr++);
       sqlChan->setFixStart(0);
       sqlChan->setLastAttempt(0);
       elog << "chanfix::processQueue> DEBUG: Channel " << sqlChan->getChannel() << " done!" << std::endl;
     } else {
       ptr->second = currentTime() + CHANFIX_INTERVAL;
       ptr++;
       elog << "chanfix::processQueue> DEBUG: Channel " << sqlChan->getChannel() << " not done yet ..." << std::endl;
     }
   } else ptr++;
}

return;
}

bool chanfix::isBeingFixed(Channel* theChan)
{
return (isBeingAutoFixed(theChan) || isBeingChanFixed(theChan));
}

bool chanfix::isBeingAutoFixed(Channel* theChan)
{
fixQueueType::iterator ptr = autoFixQ.find(theChan->getName());
if (ptr != autoFixQ.end())
  return true;

return false;
}

bool chanfix::isBeingChanFixed(Channel* theChan)
{
fixQueueType::iterator ptr = manFixQ.find(theChan->getName());
if (ptr != manFixQ.end())
  return true;

return false;
}

bool chanfix::removeFromAutoQ(Channel* theChan)
{
fixQueueType::iterator ptr = autoFixQ.find(theChan->getName());
if (ptr != autoFixQ.end()) {
  autoFixQ.erase(ptr);
  return true;
}

return false;
}

bool chanfix::removeFromManQ(Channel* theChan)
{
fixQueueType::iterator ptr = manFixQ.find(theChan->getName());
if (ptr != manFixQ.end()) {
  manFixQ.erase(ptr);
  return true;
}

return false;
}

const std::string chanfix::prettyDuration( int duration )
{

// Pretty format a 'duration' in seconds to
// x day(s), xx:xx:xx.

char tmpBuf[ 64 ] = {0};

int	res = currentTime() - duration,
	secs = res % 60,
	mins = (res / 60) % 60,
	hours = (res / 3600) % 24,
	days = (res / 86400) ;

sprintf(tmpBuf, "%i day%s, %02d:%02d:%02d",
	days,
	(days == 1 ? "" : "s"),
	hours,
	mins,
	secs );

return std::string( tmpBuf ) ;
}

const std::string chanfix::tsToDateTime(time_t timestamp, bool time)
{
char datetimestring[ 20 ] = {0};
struct tm *stm;

stm = localtime(&timestamp);
memset(datetimestring, 0, sizeof(datetimestring));

if (time)
  strftime(datetimestring, sizeof(datetimestring), "%Y-%m-%d %H:%M:%S", stm);
else
  strftime(datetimestring, sizeof(datetimestring), "%Y-%m-%d", stm);

return std::string(datetimestring);
}

const int chanfix::getCurrentGMTHour()
{
time_t rawtime;
tm * ptm;

time ( &rawtime );
ptm = gmtime ( &rawtime );

return ptm->tm_hour;
}

sqlcfUser* chanfix::isAuthed(const std::string Name)
{
//Name = escapeSQLChars(Name);
sqlcfUser* tempUser = usersMap[Name];
if (!tempUser)
  usersMap.erase(usersMap.find(Name));
return tempUser;
}

chanfix::clientOpsType* chanfix::findMyOps(iClient* theClient)
{
clientOpsType* myOps = static_cast< clientOpsType* >(theClient->getCustomData(this) );

if (myOps == NULL) myOps = new clientOpsType;

return myOps;
}

/*
 * Start timers
 */
void chanfix::startTimers()
{
time_t theTime;
theTime = time(NULL) + connectCheckFreq;
tidCheckDB = MyUplink->RegisterTimer(theTime, this, NULL);
theTime = time(NULL) + CHECK_CHANS_TIME;
tidAutoFix = MyUplink->RegisterTimer(theTime, this, NULL);
theTime = time(NULL) + PROCESS_QUEUE_TIME;
tidFixQ = MyUplink->RegisterTimer(theTime, this, NULL);
theTime = time(NULL) + POINTS_UPDATE_TIME;
tidGivePoints = MyUplink->RegisterTimer(theTime, this, NULL);
theTime = time(NULL) + getSecsTilMidnight();
tidRotateDB = MyUplink->RegisterTimer(theTime, this, NULL);
theTime = time(NULL) + SQL_UPDATE_TIME;
tidUpdateDB = MyUplink->RegisterTimer(theTime, this, NULL);
theTime = time(NULL) + TEMPBLOCKS_CHECK_TIME;
tidTempBlocks = MyUplink->RegisterTimer(theTime, this, NULL);
elog	<< "chanfix::startTimers> Started all timers."
	<< std::endl;
}

/**
 * prepareUpdate - Copies the sqlChanOp map to a temporary multimap
 */
#ifdef CHANFIX_HAVE_BOOST_THREAD
void chanfix::prepareUpdate(bool threaded)
#else
void chanfix::prepareUpdate(bool)
#endif /* CHANFIX_HAVE_BOOST_THREAD */ 
{
  if (updateInProgress) {
    elog	<< "*** [chanfix::prepareUpdate] Update already in progress; not starting."
		<< std::endl;
    return;	  
  }

  elog	<< "*** [chanfix::prepareUpdate] Updating the SQL database "
#ifdef CHANFIX_HAVE_BOOST_THREAD
	<< (threaded ? "(threaded)." : "(unthreaded).")
#else
	<< "(unthreaded [no boost])."
#endif /* CHANFIX_HAVE_BOOST_THREAD */
	<< std::endl;
  logDebugMessage("Starting to update the SQL database.");

  /**
   * Set updateInProgress boolean to true so that any other updates 
   * and/or requests for shutdown/reload will be denied.
   */
  updateInProgress = true;

  /* Start our timer */
  Timer snapShotTimer;
  snapShotTimer.Start();
  
  /* Clear the snapShot map */
  snapShot.clear();

  //snapShotStruct* curStruct = new (std::nothrow) snapShotStruct;
  snapShotStruct curStruct;

  sqlChanOp* curOp;
  std::string curChan;
  int i = 0;

  for (sqlChanOpsType::iterator ptr = sqlChanOps.begin();
       ptr != sqlChanOps.end(); ptr++) {
    curChan = ptr->first;
    for (sqlChanOpsType::mapped_type::iterator chanOp = ptr->second.begin();
	 chanOp != ptr->second.end(); chanOp++) {
      curOp = chanOp->second;
		 
      /* Fill the structures and maps */
      curStruct.account = curOp->getAccount();
      curStruct.lastSeenAs = curOp->getLastSeenAs();
      curStruct.firstOpped = curOp->getTimeFirstOpped();
      curStruct.lastOpped = curOp->getTimeLastOpped();
      
      for (i = 0; i < DAYSAMPLES; i++) {
        curStruct.day[i] = curOp->getDay(i);
      }
      
      snapShot.insert(DBMapType::value_type(curChan, curStruct));
    }
  }

  logDebugMessage("Created snapshot map in %u ms.",
		  snapShotTimer.stopTimeMS());

#ifdef CHANFIX_HAVE_BOOST_THREAD
  if (threaded) {
    ClassUpdateDB updateDB(*this);
    boost::thread pthrd(updateDB);
    pthrd.join();
  } else
#endif /* CHANFIX_HAVE_BOOST_THREAD */
    updateDB();
    printResourceStats();
  return;
}

/**
 * updateDB - Copies the contents of sqlChanOps to the SQL database
 * Note: Only threaded if called via the ClassUpdateDB function with
 * boost::thread
 */
void chanfix::updateDB()
{  
  /* Start our timer */
  Timer updateDBTimer;
  updateDBTimer.Start();

  /* Get a connection instance to our backend */
  dbHandle* cacheCon = localDBHandle;

  /* Check for the backup table. If it exists, something went wrong. */
  /* SELECT count(*) FROM pg_tables WHERE tablename = 'chanOpsBackup' */
  /* SELECT chanOpsBackup FROM information_schema.tables */
  if (!cacheCon->Exec("SELECT count(*) FROM pg_tables WHERE tablename = 'chanOpsBackup'",true)) {
    elog	<< "*** [chanfix::updateDB]: Error checking for backup table presence: " 
		<< cacheCon->ErrorMessage()
		<< std::endl;
    return;
  }

  if (cacheCon->Tuples() && atoi(cacheCon->GetValue(0, 0))) {
    /* Drop the backup table. */
    if (!cacheCon->Exec("DROP TABLE chanOpsBackup")) {
      elog	<< "*** [chanfix::updateDB]: Error dropping backup table: " 
		<< cacheCon->ErrorMessage()
		<< std::endl;
      return;
    }
  }

  /* Copy all data from the main table to the backup table. */
  if (!cacheCon->Exec("CREATE TABLE chanOpsBackup AS SELECT * FROM chanOps")) {
    elog	<< "*** [chanfix::updateDB]: Error creating backup table: " 
		<< cacheCon->ErrorMessage()
		<< std::endl;
    return;
  }

  /* Truncate the current chanOps table. */
  if (!cacheCon->Exec("TRUNCATE TABLE chanOps")) {
    elog	<< "*** [chanfix::updateDB]: Error truncating current chanOps table: " 
		<< cacheCon->ErrorMessage()
		<< std::endl;
    return;
  }

  /* Copy the current chanOps to SQL. */
  if( !cacheCon->Exec("COPY chanOps FROM stdin") )
   { 
    elog	<< "*** [chanfix::updateDB]: Error starting copy of chanOps table."
		<< cacheCon->ErrorMessage()
		<< std::endl;
    return;
  }

  std::stringstream theLine;
  int chanOpsProcessed = 0;
  int i = 0;
  
  for (DBMapType::iterator ptr = snapShot.begin();
       ptr != snapShot.end(); ptr++) {
    theLine.str("");
    theLine	<< escapeSQLChars(ptr->first) << "\t"
		<< escapeSQLChars(ptr->second.account) << "\t"
		<< escapeSQLChars(ptr->second.lastSeenAs) << "\t"
		<< ptr->second.firstOpped << "\t"
		<< ptr->second.lastOpped
		;

    for (i = 0; i < DAYSAMPLES; i++) {
      theLine	<< "\t" << ptr->second.day[i]
		;
    }

    theLine	<< "\n"
		;

    cacheCon->PutLine(theLine.str());
    chanOpsProcessed++;
  }
  
  /* Send completion string for the end of the data. */
  cacheCon->PutLine("\\.\n");

  /**
   * Synchronize with the backend.
   * Returns 0 on success, 1 on failure
   */
  if( !cacheCon->StopCopyIn() )
//  if (copyStatus != 0) {
	{
    elog	<< "*** [chanfix::updateDB] Error ending copy!"
//		<< copyStatus << " instead (should be 0 for success)."
		<< std::endl;
    return;
  }

  /* Count the rows to see if the cache == number of rows in table. */
  if (!cacheCon->Exec("SELECT count(*) FROM chanOps",true)) {
    elog	<< "*** [chanfix::updateDB]: Error counting rows in chanOps table: " 
		<< cacheCon->ErrorMessage()
		<< std::endl;
    return;
  }

  int actualChanOpsProcessed = atoi(cacheCon->GetValue(0, 0));
  if (actualChanOpsProcessed != chanOpsProcessed) {
    elog	<< "*** [chanfix::updateDB] Error updating chanOps! "
		<< "Only " << actualChanOpsProcessed << " of "
		<< chanOpsProcessed << " chanops were copied to the SQL database."
		<< std::endl;
    logDebugMessage("ERROR: Only %d of %d chanops were updated in %u ms.",
		    actualChanOpsProcessed, chanOpsProcessed,
		    updateDBTimer.stopTimeMS());
  } else {
    elog	<< "*** [chanfix::updateDB]: Done. Copied "
		<< actualChanOpsProcessed
		<< " chanops to the SQL database."
		<< std::endl;
    logDebugMessage("Synched %d members to the SQL database in %u ms.",
		    actualChanOpsProcessed, updateDBTimer.stopTimeMS());
  }

  /* Drop the backup table. */
  if (!cacheCon->Exec("DROP TABLE chanOpsBackup")) {
    elog	<< "*** [chanfix::updateDB]: Error dropping backup table (after completion): " 
		<< cacheCon->ErrorMessage()
		<< std::endl;
    return;
  }

  /* Dispose of our connection instance */
  //theManager->removeConnection(cacheCon);

  /* Clean-up after ourselves and allow new updates to be started */
  snapShot.clear();
  updateInProgress = false;

  return;
}

void chanfix::printResourceStats()
{
  int who = RUSAGE_SELF;
  struct rusage usage;
  int ret;
  ret = getrusage(who, &usage);
  logDebugMessage("Max. resident size used by chanfix (kB): %ld", usage.ru_maxrss);
}

bool chanfix::isTempBlocked(const std::string& theChan)
{
  tempBlockType::iterator ptr = tempBlockList.find(theChan);
  if (ptr == tempBlockList.end())
    return false;
  
  return true;
}

void chanfix::expireTempBlocks()
{
  time_t unixTime = currentTime();
  std::list<std::string> remList;
  std::list<std::string>::iterator remIterator;

  for (tempBlockType::iterator ptr = tempBlockList.begin();
       ptr != tempBlockList.end(); ptr++) {
    if ((unixTime - ptr->second) >= TEMPBLOCK_DURATION_TIME)
      remList.push_back(ptr->first);
  }
  
  for(remIterator = remList.begin();remIterator != remList.end();)
  {
    tempBlockList.erase(*remIterator);
    remIterator = remList.erase(remIterator);
  }
}

void chanfix::rotateDB()
{
/* 
 * CODER NOTES:
 * go through everybody and remove the oldest day (set it to 0)
 * then loop back through and pick out the users with 0 points total and check their ts_firstopped
 * if it is older than 1 day, delete the user
 * cache: acct,chan
 */

logDebugMessage("Beginning database rotation.");

/* Start our timer */
Timer rotateDBTimer;
rotateDBTimer.Start();

int deleteCount = 0;
int errorCount = 0;
	
short nextDay = currentDay;
setCurrentDay();
if (nextDay >= (DAYSAMPLES - 1))
  nextDay = 0;
else
  nextDay++;

/**
 * Basically, we need to make sure we do not delete ops that were
 * just added. To do this, we take the currentTime(), subtract
 * POINTS_UPDATE_TIME (plus 10 seconds to make sure), and then
 * check it against the time that he/she was first opped.
 */
time_t maxFirstOppedTS = currentTime() - (POINTS_UPDATE_TIME + 10);
time_t maxLastOppedTS = currentTime() - (DAYSAMPLES * 86400);
sqlChanOp* curOp;
std::string curChan;

for (sqlChanOpsType::iterator ptr = sqlChanOps.begin();
     ptr != sqlChanOps.end(); ptr++) {
  for (sqlChanOpsType::mapped_type::iterator chanOp = ptr->second.begin();
       chanOp != ptr->second.end();) {
    curOp = chanOp->second;
    curOp->setDay(nextDay, 0);
    curOp->calcTotalPoints();
    if (((curOp->getPoints() <= 0) &&
	 (maxFirstOppedTS > curOp->getTimeFirstOpped()))
	|| (maxLastOppedTS > curOp->getTimeLastOpped())) {
      ptr->second.erase(chanOp++);
      delete curOp; curOp = 0;
    } else {
      chanOp++;
    }
  }
  if (!ptr->second.size()) {
    /* Empty channel, start deleting info */
    curChan = ptr->first;
    sqlChannel* sqlChan = getChannelRecord(curChan);
    if (!sqlChan)
      continue;

#ifndef REMEMBER_CHANNELS_WITH_NOTES_OR_FLAGS
    if (!sqlChan->deleteAllNotes()) {
      elog	<< "chanfix::rotateDB> Error: could not delete all the notes of channel "
		<< curChan.c_str()
		<< std::endl;
    }

#else
    if (!sqlChan->getFlags()) {
#endif
      if (!deleteChannelRecord(sqlChan)) {
	elog	<< "chanfix::rotateDB> Error: could not delete channel "
		<< curChan.c_str()
		<< std::endl;
	errorCount++;
      } else {
	deleteCount++;
      }
#ifdef REMEMBER_CHANNELS_WITH_NOTES_OR_FLAGS
    }
#endif
  }
}

logDebugMessage("Completed database rotation in %u ms.",
		rotateDBTimer.stopTimeMS());

logDebugMessage("%i channels were deleted. %i channels errored out while deleting.",
		deleteCount,
		errorCount);

return;
}

void chanfix::giveAllOpsPoints()
{
typedef std::list <std::string> ScoredOpsListType;

Channel* thisChan;
ScoredOpsListType scoredOpsList;
ScoredOpsListType::iterator scOpIter;
for (xNetwork::channelIterator ptr = Network->channels_begin();
     ptr != Network->channels_end(); ptr++) {
  thisChan = ptr->second;
  if (!canScoreChan(thisChan))
    continue; // Exit the loop and go to the next chan
  if (thisChan->size() >= minClients && !isBeingFixed(thisChan)) {
    scoredOpsList.clear();
    for (Channel::userIterator ptr = thisChan->userList_begin();
	 ptr != thisChan->userList_end(); ptr++) {
      ChannelUser* curUser = ptr->second;
      if (curUser->isModeO() && curUser->getClient()->getAccount() != "") {
	//Ok hes an op
	//Grab an iClient for curUser
	scOpIter = std::find(scoredOpsList.begin(), scoredOpsList.end(), curUser->getClient()->getAccount());
	if (scOpIter == scoredOpsList.end()) {
	  givePoints(thisChan, curUser->getClient());
	  scoredOpsList.push_back(ScoredOpsListType::value_type(curUser->getClient()->getAccount()));
	}
      }
    }
  }
}

return;
} //giveAllOpsPoints

char chanfix::getFlagChar(const sqlcfUser::flagType& whichFlag)
{
 if (whichFlag == sqlcfUser::F_SERVERADMIN)
   return 'a';
 else if (whichFlag == sqlcfUser::F_BLOCK)
   return 'b';
 else if (whichFlag == sqlcfUser::F_COMMENT)
   return 'c';
 else if (whichFlag == sqlcfUser::F_CHANFIX)
   return 'f';
 else if (whichFlag == sqlcfUser::F_OWNER)
   return 'o';
 else if (whichFlag == sqlcfUser::F_USERMANAGER)
   return 'u';
 else if (whichFlag == sqlcfUser::F_PERMBLOCKER)
   return 'p';
 else
   return ' ';
}

const std::string chanfix::getFlagsString(const sqlcfUser::flagType& whichFlags)
{
 std::string flagstr;
 if (whichFlags & sqlcfUser::F_SERVERADMIN)
   flagstr += "a";
 if (whichFlags & sqlcfUser::F_BLOCK)
   flagstr += "b";
 if (whichFlags & sqlcfUser::F_COMMENT)
   flagstr += "c";
 if (whichFlags & sqlcfUser::F_CHANFIX)
   flagstr += "f";
 if (whichFlags & sqlcfUser::F_OWNER)
   flagstr += "o";
 if (whichFlags & sqlcfUser::F_USERMANAGER)
   flagstr += "u";
 if (whichFlags & sqlcfUser::F_PERMBLOCKER)
   flagstr += "p";
 
return flagstr;
}

sqlcfUser::flagType chanfix::getFlagType(const char whichChar)
{
switch (whichChar) {
  case 'a': return sqlcfUser::F_SERVERADMIN;
  case 'b': return sqlcfUser::F_BLOCK;
  case 'c': return sqlcfUser::F_COMMENT;
  case 'f': return sqlcfUser::F_CHANFIX;
  case 'o': return sqlcfUser::F_OWNER;
  case 'u': return sqlcfUser::F_USERMANAGER;
  case 'p': return sqlcfUser::F_PERMBLOCKER;
}
return 0;
}

const std::string chanfix::getEventName(const int whichEvent)
{
if (whichEvent == sqlChannel::EV_MISC)
  return "MISC";
else if (whichEvent == sqlChannel::EV_NOTE)
  return "NOTE";
else if (whichEvent == sqlChannel::EV_CHANFIX)
  return "CHANFIX";
else if (whichEvent == sqlChannel::EV_SIMULATE)
  return "SIMULATE";
else if (whichEvent == sqlChannel::EV_REQUESTOP)
  return "REQUESTOP";
else if (whichEvent == sqlChannel::EV_BLOCK)
  return "BLOCK";
else if (whichEvent == sqlChannel::EV_TEMPBLOCK)
  return "TEMPBLOCK";
else if (whichEvent == sqlChannel::EV_UNTEMPBLOCK)
  return "UNTEMPBLOCK";
else if (whichEvent == sqlChannel::EV_UNBLOCK)
  return "UNBLOCK";
else if (whichEvent == sqlChannel::EV_ALERT)
  return "ALERT";
else if (whichEvent == sqlChannel::EV_UNALERT)
  return "UNALERT";
else
  return "";
}

const std::string chanfix::getHelpMessage(sqlcfUser* theUser, std::string topic)
{
int lang_id = 1;

if (theUser)
  lang_id = theUser->getLanguageId();

std::pair <int, std::string> thePair(lang_id, topic);
helpTableType::iterator ptr = helpTable.find(thePair);
if (ptr != helpTable.end())
  return ptr->second;

if (lang_id != 1)
  return getHelpMessage(theUser, topic);

return std::string("");
}

void chanfix::loadHelpTable()
{
/* Get a connection instance to our backend */
//dbHandle* cacheCon = theManager->getConnection();

/* Grab the help table */
std::stringstream theQuery;
theQuery	<< "SELECT language_id,topic,contents FROM help"
		;
if (localDBHandle->Exec("SELECT language_id,topic,contents FROM help",true))
  for (unsigned int i = 0; i < localDBHandle->Tuples(); i++)
    helpTable.insert(helpTableType::value_type(
		     std::make_pair(atoi(localDBHandle->GetValue(i, 0)),
				    localDBHandle->GetValue(i, 1)),
		     localDBHandle->GetValue(i, 2)));

elog	<< "*** [chanfix::loadHelpTable]: Loaded "
	<< helpTable.size()
	<< " help messages."
	<< std::endl;

/* Dispose of our connection instance */
//theManager->removeConnection(cacheCon);

return;
}

const std::string chanfix::getResponse( sqlcfUser* theUser,
	int response_id, std::string msg )
{

// Language defaults to English
int lang_id = 1;

if (theUser)
  lang_id = theUser->getLanguageId();

std::pair<int, int> thePair( lang_id, response_id );

translationTableType::iterator ptr = translationTable.find(thePair);
if (ptr != translationTable.end()) {
  /* Found something! */
  return ptr->second ;
}

/*
 * Can't find this response Id within a valid language.
 * Realistically we should bomb here, however it might be wise
 * to 'fallback' to a lower language ID and try again, only bombing if we
 * can't find an english variant. (Carrying on here could corrupt
 * numerous varg lists, and will most likely segfault anyway).
 */
if (lang_id != 1) {
  std::pair<int, int> thePair( 1, response_id );
  translationTableType::iterator ptr = translationTable.find(thePair);
  if (ptr != translationTable.end())
    return ptr->second ;
}

if (!msg.empty())
  return msg;

return std::string( "Unable to retrieve response. Please contact a chanfix "
        "administrator." ) ;
}

void chanfix::loadTranslationTable()
{
/* Get a connection instance to our backend */
/* dbHandle* cacheCon = theManager->getConnection(); */

/* Grab the languages table */
std::stringstream langQuery;
langQuery	<< "SELECT id,code,name FROM languages"
		;

if (localDBHandle->Exec(langQuery.str(),true))
  for (unsigned int i = 0; i < localDBHandle->Tuples(); i++)
    languageTable.insert(languageTableType::value_type(localDBHandle->GetValue(i, 1),
			 std::make_pair(atoi(localDBHandle->GetValue(i, 0)),
					localDBHandle->GetValue(i, 2))));

elog	<< "*** [chanfix::loadTranslationTable]: Loaded "
	<< languageTable.size()
	<< " language" << ((languageTable.size() != 1) ? "s." : ".")
	<< std::endl;

/* Grab the translations table */
std::stringstream transQuery;
transQuery	<< "SELECT language_id,response_id,text FROM translations"
		;

if (localDBHandle->Exec(transQuery.str(),true)) {
  for (unsigned int i = 0 ; i < localDBHandle->Tuples(); i++) {
    /*
     *  Add to our translations table.
     */

    int lang_id = atoi(localDBHandle->GetValue( i, 0 ));
    int resp_id = atoi(localDBHandle->GetValue( i, 1 ));

    std::pair<int, int> thePair( lang_id, resp_id ) ;

    translationTable.insert(
			translationTableType::value_type(
			thePair, localDBHandle->GetValue( i, 2 )) );
  }
}

elog	<< "*** [chanfix::loadTranslationTable]: Loaded "
	<< translationTable.size()
	<< " translations."
	<< std::endl;

/* Dispose of our connection instance */
/* theManager->removeConnection(cacheCon); */

return;
}

void chanfix::updatePoints()
{
/*
sqlChanOp* curOp;

for(chanOpsType::iterator ptr = opList.begin(); ptr != opList.end(); ptr++) {
  curOp = *ptr;
  givePoints(curOp);
}
*/
}

/*void chanfix::checkDBConnection()
{
if (cacheCon->Status() == CONNECTION_BAD) { //Check if the connection has died
  delete(cacheCon);
  dbConnected = false;
  updateSQLDb(NULL);
  MsgChanLog("PANIC! - The Connection With The Db Was Lost\n");
  MsgChanLog("Attempting to reconnect, Attempt %d out of %d\n",
	     connectCount+1,connectRetry+1);
  std::string Query = "host=" + sqlHost + " dbname=" + sqlDB + " port=" + sqlPort;
  if (strcasecmp(sqlcfUser,"''"))
    Query += (" user=" + sqlcfUser);
  if (strcasecmp(sqlPass,"''"))
    Query += (" password=" + sqlPass);
  theManager = new (std::nothrow) cmDatabase(Query.c_str());
  assert(theManager != NULL);

  if (theManager->ConnectionBad()) {
    ++connectCount;
    if (connectCount > connectRetry) {
      MsgChanLog("Cant connect to the database, quiting\n");
      ::exit(1);
    } else {
      MsgChanLog("Attempt failed\n");
    }
  } else {
    dbConnected = true;
    MsgChanLog("The PANIC is over, db connection restored\n");
    updateSqldb(theManager);
    connectCount = 0;
  }
}

}

void chanfix::updateSQLDb(dbHandle* _SQLDb)
{
for(glineIterator ptr = glineList.begin();ptr != glineList.end();++ptr)
	{
	(ptr->second)->setSqldb(_SQLDb);
	}

for(glineIterator ptr = rnGlineList.begin();ptr != rnGlineList.end();++ptr)
	{
	(ptr->second)->setSqldb(_SQLDb);
	}

for(exceptionIterator ptr = exception_begin();ptr != exception_end();++ptr)
	{
	(*ptr)->setSqldb(_SQLDb);
	}

for(usersIterator ptr = usersMap.begin();ptr != usersMap.end();++ptr)
	{
	ptr->second->setSqldb(_SQLDb);
	}

for(serversIterator ptr = serversMap.begin();ptr != serversMap.end();++ptr)
	{
	ptr->second->setSqldb(_SQLDb);
	}
}*/

void Command::Usage( iClient* theClient )
{
sqlcfUser* theUser = bot->isAuthed(theClient->getAccount());
bot->SendTo(theClient,
            bot->getResponse(theUser,
                             language::syntax,
                             std::string("SYNTAX: ")).c_str() + getInfo() );
}

/* THIS IS FOR CALCULATING NEW SCORES */
int chanfix::getNewScore( sqlChanOp* chOp, time_t oldestTS )
{
    int daysSinceFirstOpOnChan = (currentTime() - oldestTS) / 86400;
    int daysSinceFirstOp = (currentTime() - chOp->getTimeFirstOpped()) / 86400;
    
    int x = 0;
    
    if (daysSinceFirstOpOnChan < 30)
       x = 100;
    else
       x = 3000 / daysSinceFirstOpOnChan;
    
    /* GET NEW SCORE */
    int newScore = (x * daysSinceFirstOp);
    
    elog	<< "chanfix::getNewScore> daysSinceFirstOpOnChan: "
    << daysSinceFirstOpOnChan <<
    " daysSinceFirstOp: "
    << daysSinceFirstOp
    << " x = "
    << x
    << " newScore = (x * daysSinceFirstOp) = "
    << newScore
    << " oldestTS = "
    << oldestTS
	<< std::endl;
    
    return newScore;
}

} // namespace cf

} // namespace gnuworld

