#!/usr/bin/perl
# -*- mode: perl; coding: utf-8 -*-
# keitairc
# $Id: keitairc,v 1.62 2008-07-18 15:17:49 morimoto Exp $
# $Source: /home/ishikawa/work/keitairc/tmp/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
#
# 00location_receiver plugin use XML::Simple, so if you want to use it
#    Depends: libxml-simple-perl

use lib qw(lib /usr/share/keitairc/lib);
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 Keitairc::Config;
use Keitairc::View;
use Keitairc::IrcBuffer;
use Keitairc::IrcCallback;
use Keitairc::ClientInfo;
use Keitairc::SessionManager;
use Keitairc::Plugins;
use strict;
use warnings;

our $cf = new Keitairc::Config('2.0b7', @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()){
	use Proc::Daemon;

	Proc::Daemon::Init;
	if(length $cf->pid_dir()){
		if (open(PID, '> ' . $cf->pid_dir() . '/' . $cf->pid_file())) {
			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_registered => \&Keitairc::IrcCallback::irc_registered,
		irc_001 => \&Keitairc::IrcCallback::irc_001,
		irc_join => \&Keitairc::IrcCallback::irc_join,
		irc_part => \&Keitairc::IrcCallback::irc_part,
		irc_quit => \&Keitairc::IrcCallback::irc_quit,
		irc_public => \&Keitairc::IrcCallback::irc_public,
		irc_notice => \&Keitairc::IrcCallback::irc_notice,
		irc_mode => \&Keitairc::IrcCallback::irc_mode,
		irc_nick => \&Keitairc::IrcCallback::irc_nick,
		irc_msg => \&Keitairc::IrcCallback::irc_msg,
		irc_topic => \&Keitairc::IrcCallback::irc_topic,
		irc_332 => \&Keitairc::IrcCallback::irc_topicraw,
		irc_352 => \&Keitairc::IrcCallback::irc_whoreply,
		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");

	{
		# chop off $cf->web_root()
		my $root = $cf->web_root();
		$uri =~ s|$root|/|;
	}

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

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

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

	if($uri eq '/login_imodeid?guid=ON'){
		return action_login_imodeid($request);
	}

	if($uri eq '/robots.txt'){
		return action_robots_txt($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() });
}

################################################################
sub action_robots_txt{
	my $request = shift;
	my $ci = new Keitairc::ClientInfo($request);
	my $view = new Keitairc::View($cf, $ci);
	return $view->render('robots.txt', { content_type => 'text/plain' });
}

################################################################
# かんたんログインのPOST先
# DoCoMoだったらiccが来ているはずなので, icc + user_agent でチェック。
# 合っていたらセッション復帰して /{SESSION}/index へ
sub action_login_icc{
	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('login_icc.html', { icc => $docomo_foma_icc });
		}
	}

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

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

			if($docomo_imodeid eq $cf->docomo_imodeid()){
				my $s = $sm->add($ci->user_agent(), $docomo_imodeid);
				::log_debug("redirect to /$s->{id}/index from docomo_imodeid");
				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('login_imodeid.html', { imodeid => $docomo_imodeid });
		}
	}

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

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

	if($ci->cookie_available()){
		my $session_id = $ci->{cookie}->{sid};
		if(defined($session_id) && 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");
			}
			if($serial_key eq $cf->softbank_serial_key()){
				my $s = $sm->add($ci->user_agent(), $serial_key);
				::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");
			}
		}
	}

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

			if($userid eq $cf->emobile_userid()){
				my $s = $sm->add($ci->user_agent(), $userid);
				::log_debug("redirect to /$s->{id}/index from emobile_userid");
				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_foma_icc => $cf->docomo_foma_icc(),
		docomo_imodeid => $cf->docomo_imodeid(),
			});
}

################################################################
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 $timestamp;

	my $message = $request->content();
        if(length($message)){
		($message, $timestamp) = split(/&/, $message);
		$timestamp =~ s/^stamp=//g; 
        }
	$message =~ s/^m=//;
	$message =~ s/\+/ /g;
	$message = uri_unescape($message);

	if(length($message)){
		my $jis = $message;
		Encode::from_to($jis, $cf->web_charset(), $cf->irc_charset());
		my $euc = Encode::decode($cf->web_charset(), $message);
		if($jis =~ s|^/||) {
			my ($params, $trailing) = split(/ :/, $jis, 2);
			my @postcmd = split(/ /, $params);
			push @postcmd, $trailing if defined $trailing;
			# This parser may be incomplete.
			if($postcmd[0] =~ /join/i) {
				if($postcmd[1] =~ /^\w/) {
					$ib->join($postcmd[1]);
					return;
				}
			} elsif($postcmd[0] =~ /part/i) {
				if($postcmd[1] =~ /^\w/) {
					$ib->part($ib->name2cid($postcmd[1]));
					return;
				}
			}
			$irc->yield(@postcmd);
		} elsif(length($channel)){
			if($ib->update_timestamp($timestamp)){
				$irc->yield(privmsg => $channel => $jis);
				my $cid = $ib->name2cid($channel);
				$ib->add_message($cid, $euc, $cf->irc_nick());
			}
			$ib->message_added(1);
		}
	}
}

################################################################
# 入力 charset は perl internal
sub render_line{
	my ($in, $session_id, $reverse) = @_;
	my @message;
	my $buf;
	local($_);

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

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

		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 />";
	}
	return Encode::encode($cf->web_charset(), $buf);
}

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

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

sub log_debug{
	my $m = shift;
	if($cf->debug()){
		warn "keitairc(debug): $m\n";
	}
}

__END__
