#!/usr/bin/perl
# keitairc
# $Id: keitairc,v 1.35 2008/01/09 18:42:42 morimoto Exp $
# $Source: /cvsroot/keitairc/keitairc,v $
#
# Copyright (c) 2003-2008 Jun Morimoto <morimoto@mrmt.net>
# This program is covered by the GNU General Public License 2
#
# Depends: libpoe-component-irc-perl,
#   liburi-perl, libwww-perl, libappconfig-perl, libproc-daemon-perl,
#   libhtml-template-perl

use lib qw(lib /usr/share/keitairc/lib);
use strict;
use Encode;
use POE;
use POE::Filter::HTTPD;
use POE::Component::IRC;
use POE::Component::Server::TCP;
use URI::Escape;
use HTML::Template;
use HTTP::Response;
use Proc::Daemon;
use Keitairc::Config;
use Keitairc::View;
use Keitairc::IrcBuffer;
use Keitairc::IrcCallback;
use Keitairc::ClientInfo;
use Keitairc::SessionManager;
use Keitairc::Plugins;

our $cf = new Keitairc::Config('2.0b1', @ARGV);
our $ib = new Keitairc::IrcBuffer({history => $cf->web_lines()});
our $sm = new Keitairc::SessionManager({default_ttl => $cf->session_ttl()});
our $pl = new Keitairc::Plugins({config => $cf});

# daemonize
if($cf->daemonize()){
	Proc::Daemon::Init;
	if(length $cf->pid_dir()){
		if (open(PID, '> ' . $cf->pid_dir() . '/keitairc.pid')) {
			print PID $$, "\n";
			close(PID);
		}
	}
}

# create irc component
our $irc = POE::Component::IRC->spawn(
	Alias => 'keitairc_irc',
	Nick => $cf->irc_nick(),
	Username => $cf->irc_username(),
	Ircname => $cf->irc_desc(),
	Server => $cf->irc_server(),
	Port => $cf->irc_port(),
	Password => $cf->irc_password());

# create POE session
POE::Session->create(
	heap => {
		seen_traffic => 0,
		disconnect_msg => 1,
		Config => $cf,
		Irc => $irc,
		IrcBuffer => $ib,
	},
	inline_states => {
		_start => \&Keitairc::IrcCallback::irc_start,
		autoping => \&Keitairc::IrcCallback::irc_autoping,
		connect => \&Keitairc::IrcCallback::irc_connect,
		irc_001 => \&Keitairc::IrcCallback::irc_001,
		irc_join => \&Keitairc::IrcCallback::irc_join,
		irc_part => \&Keitairc::IrcCallback::irc_part,
		irc_public => \&Keitairc::IrcCallback::irc_public,
		irc_notice => \&Keitairc::IrcCallback::irc_notice,
		irc_topic => \&Keitairc::IrcCallback::irc_topic,
		irc_332 => \&Keitairc::IrcCallback::irc_topicraw,
		irc_ctcp_action => \&Keitairc::IrcCallback::irc_ctcp_action,
		irc_disconnected => \&Keitairc::IrcCallback::irc_reconnect,
		irc_error => \&Keitairc::IrcCallback::irc_reconnect,
		irc_socketerr => \&Keitairc::IrcCallback::irc_reconnect,
	});

# create web server component
POE::Component::Server::TCP->new(
	Alias => 'keitairc',
	Port => $cf->web_port(),
	ClientFilter => 'POE::Filter::HTTPD',
	ClientInput => \&http_request);

# fire up main loop
$poe_kernel->run();
exit 0;

################################################################
sub http_request{
	my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0];

	# Filter::HTTPD sometimes generates HTTP::Response objects.
	# They indicate (and contain the response for) errors that occur
	# while parsing the client's HTTP request.  It's easiest to send
	# the responses as they are and finish up.
	if($request->isa('HTTP::Response')){
		$heap->{client}->put($request);
	}elsif(my $response = dispatch($request)){
		$heap->{client}->put($response);
	}

	$kernel->yield('shutdown');
}

################################################################
sub dispatch{
	my $request = shift;
	my $uri = $request->uri();
	my $ci = new Keitairc::ClientInfo($request);

	::log_debug("dispatch: $uri");

	if($uri eq '/'){
		return action_root($request);
	}

	if($uri eq '/login'){
		return action_login($request);
	}

	if($uri eq '/quicklogin'){
		return action_quicklogin($request);
	}

	for my $name ($pl->list_action_plugins()){
		if($uri =~ m|^/(S[a-zA-Z]{10})/$name/(.*)| ||
		   $uri =~ m|^/(S[a-zA-Z]{10})/$name$|){
			if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
				return add_cookie($pl->{plugins}->{$name}->{action_imprementation}($request, $name, $1, $2), $1);
			}
			return action_redirect_root($request);
		}
	}

	::log("dispatch: don't know how to dispatch uri[$uri]");
	return action_404($request);
}

################################################################
# adds session id cookie to http response object
sub add_cookie{
	my $response = shift;
	my $session_id = shift;

	my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time + $cf->cookie_ttl());
	my $expiration =
		sprintf('%.3s, %.2d-%.3s-%.4s %.2d:%.2d:%.2d',
			qw(Sun Mon Tue Wed Thu Fri Sat)[$wday],
			$mday,
			qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[$mon],
			$year + 1900,
			$hour,
			$min,
			$sec);
	my $content = sprintf("sid=%s; expires=%s; \n", $session_id, $expiration);
	$response->push_header('Set-Cookie', $content);
	$response;
}

################################################################
# 通常ログインのPOST先
# パスワードをチェックして
# 間違っていたら / へリンクして終わり
# 合っていたらセッションを発行し /{SESSION}/index へ
sub action_login{
	my $request = shift;
	my $ci = new Keitairc::ClientInfo($request);
	my $content = $request->decoded_content();
	my ($password) = ($content =~ /^password=(.*)/);

	::log_debug("password [$password]");
	::log_debug("web_password [" . $cf->web_password() . "]");

	if($cf->web_password() eq $password){
		my $s = $sm->add($ci->{header}->{user_agent}, $ci->serial_key());
		my $view = new Keitairc::View($cf, $ci, $s->{id});
		return $view->redirect("/$s->{id}/index");
	}

	# password mismatch
	my $view = new Keitairc::View($cf, $ci);
	return $view->redirect("/");
}

################################################################
sub action_404{
	my $request = shift;
	my $ci = new Keitairc::ClientInfo($request);
	my $view = new Keitairc::View($cf, $ci);
	return $view->render('404.html', { action => $request->uri() });
}

################################################################
# かんたんログインのPOST先
# DoCoMoだったらiccが来ているはずなので, icc + user_agent でチェック。
# 合っていたらセッション復帰して /{SESSION}/index へ
sub action_quicklogin{
	my $request = shift;
	my $ci = new Keitairc::ClientInfo($request);
	if($ci->is_docomo()){
		my $docomo_foma_icc = $ci->docomo_foma_icc();
		if(length $docomo_foma_icc){
			if(my $s = $sm->verify({serial_key => $docomo_foma_icc,
						user_agent => $ci->user_agent()})){
				::log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
				my $view = new Keitairc::View($cf, $ci, $s->{id});
				return $view->redirect("/$s->{id}/index");
			}

			if($docomo_foma_icc eq $cf->docomo_foma_icc()){
				my $s = $sm->add($ci->user_agent(), $docomo_foma_icc);
				::log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
				my $view = new Keitairc::View($cf, $ci, $s->{id});
				return $view->redirect("/$s->{id}/index");
			}
		}
	}

	my $view = new Keitairc::View($cf, $ci);
	return $view->render('root.html', { docomo => $ci->is_docomo() });
}

################################################################
sub action_root{
	my $request = shift;
	my $ci = new Keitairc::ClientInfo($request);

	if($ci->cookie_available()){
		my $session_id = $ci->{cookie}->{sid};
		if(length $session_id){
			if($sm->verify({session_id => $session_id,
					user_agent => $ci->user_agent()})){
				::log_debug("redirect to /$session_id/index from cookie");
				my $view = new Keitairc::View($cf, $ci, $session_id);
				return $view->redirect("/$session_id/index");
			}
		}
	}

	if($ci->is_ezweb()){
		my $subscriber_id = $ci->{header}->{x_up_subno};
		if(length $subscriber_id){
			if(my $s = $sm->verify({serial_key => $subscriber_id,
						user_agent => $ci->user_agent()})){
				::log_debug("redirect to /$s->{id}/index from subscriber_id");
				my $view = new Keitairc::View($cf, $ci, $s->{id});
				return $view->redirect("/$s->{id}/index");
			}

			if($subscriber_id eq $cf->au_subscriber_id()){
				my $s = $sm->add($ci->user_agent(), $subscriber_id);
				::log_debug("redirect to /$s->{id}/index from au_subscriber_id");
				my $view = new Keitairc::View($cf, $ci, $s->{id});
				return $view->redirect("/$s->{id}/index");
			}
		}
	}

	if($ci->is_softbank()){
		my $serial_key = $ci->softbank_serial();
		if(length $serial_key){
			if(my $s = $sm->verify({serial_key => $serial_key,
						user_agent => $ci->user_agent()})){
				::log_debug("redirect to /$s->{id}/index from softbank serial_key");
				my $view = new Keitairc::View($cf, $ci, $s->{id});
				return $view->redirect("/$s->{id}/index");
			}
		}
	}

	my $view = new Keitairc::View($cf, $ci);
	return $view->render('root.html', { docomo => $ci->is_docomo() });
}

################################################################
sub action_redirect_root{
	my $request = shift;
	my $ci = new Keitairc::ClientInfo($request);
	my $view = new Keitairc::View($cf, $ci);
	return $view->redirect('/');
}

################################################################
sub send_message{
	my $request = shift;
	my $channel = shift;

	my $message = $request->content();
	$message =~ s/^m=//;
	$message =~ s/\+/ /g;
	$message = uri_unescape($message);

	if(length($message)){
		my $jis = $message;
		my $euc = $message;
		Encode::from_to($jis, 'shiftjis', 'jis');
		Encode::from_to($euc, 'shiftjis', 'euc-jp');
		$irc->yield(privmsg => $channel => $jis);
		$ib->add_message($channel, $euc, $cf->irc_nick());
		$ib->message_added(1);
	}
}

################################################################
# 入力は euc-jp
sub render_line{
	local($_);
	my $in = shift;
	my $session_id = shift;
	my $buf;

	for ((reverse(split("\n", $in)))[0 .. $cf->web_lines()]){
		next unless defined;
		next unless length;

		$_ = $ib->simple_escape($_);

		for my $name ($pl->list_replace_plugins()){
			last if s/$pl->{plugins}->{$name}->{message_replace_regexp}/$pl->{plugins}->{$name}->{message_replace_imprementation}($session_id, $1, $2, $3, $4, $5, $6, $7, $8, $9)/eg;
		}

		s/\s+$//;
		s/\s+/ /g;
		$buf .= "$_<br />";
	}

	Encode::from_to($buf, 'euc-jp', 'shiftjis');
	$buf;
}

################################################################
sub log{
	my $m = shift;
	warn "keitairc: $m\n";
	# TODO
}

sub log_die{
	my $m = shift;
	die "keitairc: $m\n";
	# TODO
}

sub log_debug{
	my $m = shift;
	warn "keitairc: $m\n";
	# TODO
}

__END__
