#!/usr/bin/perl -w
# Tuxaator - A general purpose IRC bot/dictionary
# $Header: /cvsroot/tuxaator/tuxaator/tuxaator,v 1.92 2006/06/03 13:48:45 digg Exp $
use DBI;
use Data::Dumper;
use POSIX qw(:signal_h);
use IO::Handle;
use strict;
use Net::IRC;
use POSIX qw(strftime);
use Plugins::Output;
use Plugins::Log;
use BotCore;
#use Tuxaator::Plugin;
use File::Glob ':glob';
my ($irc,$whoami,$driver,$dbh,%last,%plugins,%msg_handlers,$DEBUG);
my $Output;
my $DB;
my $Kernel;
my $Log;
my $Plugin;
my $basedir = $ENV{'PWD'};


my $lk		= 0;
my %timers;
$DEBUG = 1;
my (%config,%messages,%users,%settings);
my $Core = new BotCore($basedir);

my $version = lc(qq$Revision: 1.92 $);
chop($version); #cut the crap 

my %comm_struct = ( "!mode"   	=>
					{"action" 	=> \&do_set_mode,
				 	 "restricted"	=> 1,	},
		    "!op"     	=>
		    			{"action"	=> \&do_op,
				 	 "restricted"   => 1,	},
		    "!topic"   	=>
		    			{"action"	=> \&do_topic,
				 	 "restricted"   => 1,
					 "level"	=> 9,	},
		    "!kick"   	=>
		    			{"action"	=> \&do_kick,
					 "restricted"   => 1,	},
		    "!reconfig"	=>
		    			{"action"	=> \&reconfig,
					 "restricted"	=> 1,
					 "level"	=> 9,},
		    "!tm"	=>
		    			{"action"	=> \&do_tm,
					 "restricted"	=> 0,},
		    "!language"	=>
		    			{"action"	=> \&change_language,
					 "restricted"	=> 1,
					 "level"	=> 9,},
		    
); # comm_struct ends
$irc 		= new Net::IRC;
my $conn;
&read_config();
open F,">tuxaator.pid";
print F $$;
close F;
$SIG{'HUP'} = 'signal_handler';

# and then overload the configured locale, so that when locale is missing
# some strings, we get english language strings instead of nothing.
&irc_connect();

# expose the connection object to the plugins
$Core->{'connection'} = $conn;

# expose the connection object..
$Core->{'conn'} = $conn;

$Core->{'version'} = "2.0pre2";
$Core->{'netircversion'} = $irc->VERSION;
my %tmp;

# load the required plugins
my ($_plugin,$_id);
my @plugins_to_load = bsd_glob('Plugins/*/Plugin.pm');

# well, dat is no good, I need to way to specify plugins in the ini file
foreach my $plugin (@plugins_to_load)
{
	my($pname) = $plugin =~ /Plugins\/(.*)\/Plugin.pm$/;
	print "Loading $pname... ";
	unless (eval "require Plugins::${pname}::Plugin")
	{
		print "\nerror occured::";
		print $@;
	};

	# create the plugin and pass it a reference to the core object
	# so it can access all the required methods
	unless (eval "\$_plugin = new $pname(\$Core)")
	{
		print "\nerror occured:";
		print $@;
	};

	if ($_plugin)
	{
		$_id = $_plugin->get_id();
		my $coderef = $_plugin->can("get_hooks");
		if ($coderef)
		{
			my $hooks = $_plugin->get_hooks();
			while(my($k1,$v1) = each(%{$hooks}))
			{
				$conn->add_handler($k1,$v1);
			};
		};
		
		$coderef = $_plugin->can("get_scheduled_handlers");
		if ($coderef)
		{
			my $handlers = $_plugin->get_scheduled_handlers();
			while(my($k1,$v1) = each(%{$handlers}))
			{
				$conn->schedule($k1,$v1,$conn);
			}
		};

		$plugins{"$_id"} = $_plugin;
		$Core->register_plugin($_id,$_plugin);
		print " OK\n";
	}
	else
	{
		print " FAILED\n";
	};
	
}

$conn->{'Core'} = $Core;

sub process_queue {
	my $self = shift;
	my $element = $Core->get_queue_element();
	if ($element->{"target"})
	{
		$Output->send($element->{"target"},$element->{"msg"});
	};
	$self->schedule(1,\&process_queue,$self);
};

# checks whether we are connected to a channel and tries to
# join if we are not
sub check_connection {
	my $self = shift;
	if ($Core->{'connection'}->connected)
	{
		unless ($Core->{'curchannel'})
		{
			print "trying to rejoin\n";
			my @params = ();
			push @params,$Core->{'cfg'}->{'channel'};
			my $event = "";
			$Core->execute_action("!join",$event,@params);
		};
		print "we are connected\n";
	}
	else
	{
		print "not connected\n";
		exit 0;
	};
	$self->schedule(60,\&check_connection,$self);
};

sub irc_connect {
	printf "Creating connection to IRC server %s:%d...\n",$config{'server'},$config{'port'};
	$conn = $irc->newconn(Server   => $config{'server'},
			 Port     => $config{'port'},
			 Nick     => $config{'nick'},
			 Ircname  => $config{'realname'},
			 Username => $config{'ident'})
    	or die "Can't connect to IRC server.\n";
	$Output = new Plugins::Output($conn);
        $Log = new Plugins::Log($basedir . "/log") if $config{'logfile'};
	#$Plugin = new Tuxaator::Plugin($conn,$config);

    	$settings{'nick'} = $config{'nick'};
	print "Installing handler routines..." if $DEBUG;

	$conn->add_handler('msg',      \&on_msg, 1);
	$conn->add_handler('public',   \&on_msg, 1);
	$conn->add_handler('notice',   \&on_action);
	$conn->add_handler('caction',  \&on_action);
	$conn->add_global_handler([ 251,252,253,254,302,255 ], \&on_init);
	$conn->add_global_handler('disconnect', \&on_disconnect);
	# $conn->add_global_handler(376, \&on_connect);

	# some servers (the one I know right now of, is nss.ircworld.org)
	# dont send the event with code 376 (connect), this is a somewhat
	# dumb workaround - we trigger the connect handler ourself.
	$conn->schedule(5,\&on_connect,$irc);
	# we start respoding to events after 3 seconds
	$conn->schedule(3,\&process_queue,$irc);
	$conn->schedule(60,\&check_connection,$irc);

	print " done.\n" if $DEBUG;

	print "starting...\n" if $DEBUG;
}

sub quote {
	my $arg = shift;
	$arg =~ s/'/\\'/g;
	return $arg;
}


sub read_config {
    open CFG, "<config/config.tux"
        or die sprintf($messages{'file_open_failed'}, "config/config.tux");
    while (<CFG>) {
        unless (/^#/) {
            if (m/^\s*(.+?)\s*=\s*(.+?)$/) {
                $config{$1} = $2;
            }
        }
    }
    close CFG;
}

# this probably doesnt work right now
sub do_set_mode {
	my ($self,$mode,$name) = @_;
	$self->mode($config{'channel'},$mode,$name);
	printf "* Modeset: Channel: %s mode: %s name: %s\n",$config{'channel'},$mode,$name if $DEBUG;
}

sub do_op {
	my ($self,$event) = @_;
	&do_set_mode($self,"+o",$event->nick)
}

sub do_topic {
	my ($self,$event,@arg) = @_;
	$self->topic($config{'channel'},join " " => @arg);
}

# What to do when the bot successfully connects.
sub on_connect {
	my $self = shift;
	if ($self->{'connected'})
	{
		return;
	};
	$self->{'connected'} = 1;
	my @channels = split /,/ => $config{'channel'};
	foreach (@channels)
	{
		$self->join($_);
		if ($config{'verbosity'} && ($config{'verbosity'} > 0))
		{
			$self->privmsg($config{'channel'},"Tuxaator [$version]");
			#my $db_announce = sprintf($messages{'db_announce'},&do_term_count());
			#$self->privmsg($config{'channel'},$db_announce);
			$self->privmsg($config{'channel'},"\x1fhttp://tuxaator.sourceforge.net\x00");
		};
	};
}

# Handles some messages you get when you connect
sub on_init {
	my ($self, $event) = @_;
	my (@args) = ($event->args);
	shift (@args);
	print "init: *** @args\n";
}

# this is a separate function to allow easier logging later on
sub send_message {
	my ($self, $event, $msg) = @_;
	if ($self->{'prepend_nick'})
	{
		$msg = sprintf("%s: %s\n",$event->nick,$msg);
	};
	$self->privmsg($self->{'target'},$msg);
}

# handles public messages
sub on_msg {
	my ($self, $event) = @_;
	my ($arg) = $event->args;
	my (undef,$min,$hour,undef) = localtime;
	# print the line to console
	printf "%02d:%02d <%s> %s\n",$hour,$min,$event->nick,$arg;
	$Core->{'userlist'}->{$event->nick}->{'lastmsg'} = $arg;
	if ($config{'logfile'})
	{
		$Log->_log($event->nick,$arg);
	};
	my @params;
	my $command;
	($command,@params) = split / /,$arg;
	my ($comm,$arguments) = $arg =~ /^(\S+)\s+(.+)$/;

	# command is simply the first word on the line
	# we verify it aganst all defined commands and if a match is found
	# proceed with access checks
	
	if ($Core->{'msg_handlers'}->{$command}) {
		#shortcut to command handler
		my $id = $Core->{'msg_handlers'}->{$command}->{'id'};
		my $thiscommand = $Core->{'msg_handlers'}->{$command};
		my $allow2execute = ($thiscommand->{restricted} > 0) ? $Core->can_execute($event->userhost,$thiscommand->{level}) : 1;
		if ($allow2execute)
		{
			$Core->execute_action($command,$event,@params);
		}
		else
		{
			@params = ();
			push @params,$event->nick;
			$Core->execute_action("!insult",$event,@params);
			print " access denied\n";
		};

	
	}
	else
	{
		&personality($self,$event);
	}
};

# well, not really a personality, but a function to make the bot to respond
# do some actions on the channel
sub personality {
	my ($self, $event) = @_;
	my ($arg) = $event->args;
	my ($comm,$arguments) = $arg =~ /^(\S+)\s+(.+)$/;

	if (lc($comm) eq lc($self->nick().":"))
	{
		my $res = $event->{nick};
		my @answers = ('Jah','Ei','Vib-olla','Vist kll..','Kindlasti','Selle peale vib mrki vtta.','Ah, mis sa sokid...','Maga vlja ennast');
		my $index = rand @answers;
		my $answer = $answers[$index];
		$res .= ": " . $answer;
		$Core->queue($event,$res);
	};
};


# executes the actual command from a plugin
sub execute {
	my ($command,$event,@params) = @_;
	my $id = $msg_handlers{$command}->{'id'};
	unless (eval "\$plugins{\$id}->$msg_handlers{$command}->{'action'}(\@params,\$event)")
	{
		print "eval returned an error: $@\n";
	};
}


# Display formatted CTCP ACTIONs.
sub on_action {
	my ($self, $event) = @_;
	my ($nick, @args) = ($event->nick, $event->args);
	my (undef,$min,$hour,undef) = localtime;
	printf "%02d:%02d %s %s\n",$hour,$min,$nick,join " " => @args;
}

# disconnect handler
sub on_disconnect {
	my ($self, $event) = @_;
	$Core->{curchannel} = ''; # Disconnect means full departure
	# try to reconnect if configured to do so
	unless ($config{'disable_reconnect'})
	{
		print "Disconnected from ", $event->from(), " (",
	     	 ($event->args())[0], "). Attempting to reconnect...\n";
		$self->connect();
	};
}

sub signal_handler {
	my ($sig) = @_;
	print "SIGNAL received!!!\n";
	my $event = "";
	my @params = ();
	$Core->execute_action("!rehash",$event,@params);
}


sub DESTROY {
	print "teardown!\n";
}
# start the circus
$irc->start;
