package App::Mobirc::Web::Handler;
use strict;
use warnings;
use Mouse;
use Scalar::Util qw/blessed/;
use HTTP::Session;
use HTTP::Session::Store::OnMemory;
use HTTP::Session::State::Cookie;
use HTTP::Session::State::URI;
use Module::Find;
use URI::Escape;
use Plack::Response;
use Plack::Request;

use App::Mobirc;
use App::Mobirc::Util;
use App::Mobirc::Web::Router;
use App::Mobirc::Web::Context;
use App::Mobirc::Web::Tatsumaki;
useall 'App::Mobirc::Web::C';

my $session_store = HTTP::Session::Store::OnMemory->new(data => {});
if ($ENV{MOBIRC_DEBUG}) {
    require HTTP::Session::Store::DBM;
    $session_store = HTTP::Session::Store::DBM->new(
        file => '/tmp/hoge.dbm'
    );
}

our $CONTEXT;
sub web_context () { $CONTEXT } ## no critic

sub handler {
    my $env = shift;

    global_context->run_hook('env_filter', $env);

    my $req = Plack::Request->new($env);

    my $session = _create_session($req);

    local $CONTEXT = App::Mobirc::Web::Context->new(req => $req, session => $session);
    my $res = _handler($req, $session);
    global_context->run_hook('response_filter', $res);
    if (ref $res ne 'CODE') { # long poll cannot accept finalizer
        $session->response_filter( $res );
    }
    $session->finalize();
    
    # DEBUG sprintf("%03d: %s", $res->status, $req->uri->path);
    if (blessed($res)) {
        $res = $res->finalize();
    }
    return $res;
}

sub _handler {
    my ($req, $session) = @_;

    global_context->run_hook('request_filter', $req);

    if ($session->get('authorized')) {
        return process_request_authorized($req, $session);
    } else {
        return process_request_noauth($req, $session);
    }
}

sub _create_session {
    my $req = shift;
    my $conf = global_context->config->{global}->{session};
    my $ma = HTTP::MobileAttribute->new($req->headers);
    HTTP::Session->new(
        store   => $session_store,
        state   => sub {
            if ($ma->is_docomo && $ma->cache_size < 500) {
                # i-mode browser 1.0 does not supports cookie.
                # $ma->cache_size < 500 means 'i-mode browser 1.0'.
                HTTP::Session::State::URI->new(
                    session_id_name => 'sid',
                );
            } else {
                HTTP::Session::State::Cookie->new(
                    name    => 'mobirc_sid',
                    expires => '+1y',
                )
            }
        }->(),
        id      => 'HTTP::Session::ID::MD5',
        request => $req,
    );
}

sub authorize {
    my $req = shift;

    if (global_context->run_hook_first('authorize', $req)) {
        DEBUG "AUTHORIZATION SUCCEEDED";
        return 1; # authorization succeeded.
    } else {
        return 0; # authorization failed
    }
}

sub process_request_authorized {
    my ($req, $session) = @_;

    if ($req->path_info =~ m{^/tatsumaki/}) {
        return App::Mobirc::Web::Tatsumaki->handler->($req->env);
    }

    if (my $rule = App::Mobirc::Web::Router->match($req->path_info)) {
        return do_dispatch($rule, $req, $session);
    } else {
        # hook by plugins
        if (my $res = global_context->run_hook_first( 'httpd', $req )) {
            # XXX we should use html filter?
            return $res;
        }

        # doesn't match.
        return res_404($req);
    }
}

sub process_request_noauth {
    my ($req, $session) = @_;

    if (my $rule = App::Mobirc::Web::Router->match($req->path_info)) {
        if ($rule->{controller} eq 'Account' || $rule->{controller} eq 'Static') {
            return do_dispatch($rule, $req, $session);
        } else {
            return Plack::Response->new(
                302,
                [
                    Location => '/account/login?return=' . uri_escape($req->request_uri)
                ]
            );
        }
    } else {
        return res_404($req);
    }
}

sub do_dispatch {
    my ($rule, $req, $session) = @_;
    my $controller = "App::Mobirc::Web::C::$rule->{controller}";
    my $meth = $rule->{action};
    my $post_meth = "post_dispatch_$meth";
    my $get_meth  = "dispatch_$meth";
    my $args = $rule;
    $args->{session} = $session;
    $CONTEXT->action( $rule->{action} );
    $CONTEXT->controller( $rule->{controller} );
    if ( $req->method =~ /POST/i && $controller->can($post_meth)) {
        return $controller->$post_meth($req, $args);
    } else {
        return $controller->$get_meth($req, $args);
    }
}

sub res_404 {
    my ($req, ) = @_;

    my $uri = $req->path_info;
    warn "dan the 404 not found: $uri\n" if $uri ne '/favicon.ico';
    return Plack::Response->new(
        404,
        ['Content-Type' => 'text/plain'],
        "404 not found: $uri",
    );
}

no Mouse;__PACKAGE__->meta->make_immutable;
1;

