#/*
# *  Copyright 2007 hkrn <hikarin@users.sourceforge.jp>
# *
# *  Licensed under the Apache License, Version 2.0 (the "License");
# *  you may not use this file except in compliance with the License.
# *  You may obtain a copy of the License at
# *
# *      http://www.apache.org/licenses/LICENSE-2.0
# *
# *  Unless required by applicable law or agreed to in writing, software
# *  distributed under the License is distributed on an "AS IS" BASIS,
# *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# *  See the License for the specific language governing permissions and
# *  limitations under the License.
# */
#
# $Id: App.pm 740 2007-06-01 13:40:42Z hikarin $
#

package Zeromin::App;

use strict;

BEGIN {
    my $pkg = __PACKAGE__;
    for my $mtd (qw(bbs key user kernel request)) {
        my $attr = '__' . $mtd;
        no strict 'refs';
        *{"${pkg}::${mtd}"} = sub { $_[0]->{$attr} }
    }
}

sub new {
    my ( $zClass, $iKernel, @args ) = @_;
    bless {
        __kernel  => $iKernel,
        __request => Img0ch::Request->new(@args),
    }, $zClass;
}

sub run {
    my ($zApp)   = @_;
    my $iConfig  = $zApp->kernel()->get_config();
    my $iRequest = $zApp->{__request};
    my $content;

    $zApp->proxy( \$content, $iRequest );
    if ( $iConfig->get('ZerominUseGzip') ) {
        my $compress = sub {
            require Compress::Zlib;
            my ($content_ref) = @_;
            $$content_ref = Compress::Zlib::memGzip($$content_ref);
            return;
        };
        my $accept = $iRequest->get_header('accept-encoding');
        $iRequest->enable_compress();
        $compress->( \$content );
    }

    my $charset = $zApp->encoding();
    $iRequest->send_http_header( $zApp->{__content_type}, $charset );
    $iRequest->print( \$content );
}

sub proxy {
    my ( $zApp, $content, $iRequest ) = @_;
    $iRequest ||= $zApp->{__request};
    my $class  = $iRequest->param('C');
    my $method = $iRequest->param('M');
    my @argv   = $iRequest->param('argv');
    my $Qclass;

    if ( $class and $method ) {
        $Qclass = 'Zeromin::App::' . lc $class;
        $method =~ /\A[\w_]+\z/xms or die 'Invalid method', "\n";
    }
    elsif ( $iRequest->param('uv') ) {
        $Qclass = 'Zeromin::App';
        $method = 'null';
    }
    else {
        $zApp->_html( $content, $iRequest );
        return 1;
    }

    eval {
        my $mtd = $zApp->load( $Qclass, $method )
            or die "${Qclass}::${method}() not found\n";
        $iRequest->init($zApp->kernel()->get_config());
        if ( my $bbs = $iRequest->bbs() ) {
            require Img0ch::BBS;
            $zApp->{__bbs}
                = Img0ch::BBS->new( $zApp->{__kernel}, { id => $bbs, } );
            $zApp->{__key} = $iRequest->key();
        }
        my $ok = $zApp->validate( ( $zApp->{__bbs} || $zApp->{__kernel} ),
            $iRequest );
        if ( $iRequest->param('js') ) {
            $zApp->{__content_type} = 'text/javascript';
            $$content = ${
                $iRequest->get_json(
                    {   ok      => 1,
                        loginOK => $ok,
                        data    => $mtd->( $zApp, @argv ),
                    }
                )
                };
        }
        else {
            my $result;
            if ($ok) {
                $result = $mtd->( $zApp, @argv );
                $result->{loginOK} = 1;
            }
            else {
                $result = { loginOK => 0 };
            }
            $zApp->render_as_html( $content, $result, $class, $method );
        }
        return 1;
    };
    if ( my $e = $@ ) {
        if ( $iRequest->param('js') ) {
            $zApp->{__content_type} = 'text/javascript';
            $$content = ${ $iRequest->get_json(
                    { ok => 0, loginOK => 0, data => $e } ) };
        }
        else {
            $zApp->{__content_type} = 'text/html';
            $zApp->_html( $content, $iRequest, { error => $e },
                undef, 'error' );
        }
        return 0;
    }
    else {
        return 1;
    }
}

sub null {undef}

sub logger {
    my ( $zApp, $success_or_fail, $reason, $params ) = @_;
    my $class = (caller)[0];
    my $zUser = $zApp->user();
    my $user  = $zUser ? $zUser->get_current_user->{id} : 0;
    my $ip    = $zApp->request()->ip_int();

    require Zeromin::Log::Action;
    my $zLogAct = Zeromin::Log::Action->new( $zApp->kernel() );
    $zLogAct->load();
    $zLogAct->add( $class, $user, $ip, $success_or_fail, $reason, $params );
    $zLogAct->save();
    1;
}

sub encoding {
    $_[1] and $_[0]->{__encoding} = $_[1];
    $_[0]->{__encoding} || 'UTF-8';
}

sub content_type {
    $_[1] and $_[0]->{__content_type} = $_[1];
    $_[0]->{__content_type} || 'text/html';
}

sub privilege {
    my ( $zApp, $privilege, $require_bbs_level ) = @_;
    my $zUser = $zApp->user() || return 0;
    if ($privilege) {
        $zUser->$privilege or return 0;
    }

    my $iBBS = $zApp->bbs();
    ( $require_bbs_level and !$iBBS ) and return 0;
    if ( my $range = $zUser->get_current_user()->{bbs} ) {
        $iBBS->get_id() == $range or return 0;
    }
    return 1;
}

sub paging_from_array {
    my ( $zApp, $entries, $item_per_page, $offset ) = @_;
    my $pages = [];

    require Data::Page;
    my $page = Data::Page->new( scalar @$entries, $item_per_page, $offset );
    map { push @$pages, { num => $_ } }
        ( $page->first_page() .. $page->last_page() );

    return {
        Entries => [ $page->splice($entries) ],
        Pages   => $pages,
        Total   => $page->total_entries(),
        Current => $page->current_page(),
        Prev    => $page->previous_page(),
        Next    => $page->next_page(),
        Item    => $page->entries_per_page()
    };
}

sub is_success {
    my ( $zApp, $data ) = @_;
    ( ref $data || '' ) eq 'HASH' or return 0;
    return $data->{Code} == 0 ? 1 : 0;
}

sub unijp {
    my ($zApp) = @_;
    require Unicode::Japanese;
    my $unijp    = Unicode::Japanese->new();
    my $encoding = $zApp->kernel()->get_encoding(1);
    return ( $unijp, $encoding );
}

sub _html {
    my ( $zApp, $content, $iRequest, $parameters, $t_dir, $t_file ) = @_;
    $parameters             ||= {};
    $zApp->{__content_type} ||= 'text/html';

    require Img0ch::Template;
    require Zeromin::User;
    my ( $dir, $file );
    my $zUser = Zeromin::User->new( $zApp->{__kernel} );
    $zUser->load();
    $zApp->{__user} = $zUser;

    if ( $zUser->is_initialized() ) {
        $dir  = $t_dir  || $zApp->template_directory();
        $file = $t_file || $zApp->template_file();
    }
    else {
        $dir  = $zApp->template_directory();
        $file = $zApp->template_installer();
    }

    my $iTemplate = Img0ch::Template->new(
        $zApp->{__kernel},
        {   dir     => $dir,
            file    => $file,
            request => $iRequest,
        }
    );
    my $iConfig = $zApp->kernel()->get_config();
    my $self    = $zApp->self($iConfig);
    $iTemplate->param( { %$parameters, ZerominScript => $self } );
    $$content = ${ $iTemplate->to_string() };
    return;
}

*render_as_html = \&_html;

sub self {
    my ( $zApp, $iConfig ) = @_;
    $iConfig->get('ZerominScript') || 'zeromin.cgi';
}

sub template_directory {'zeromin'}

sub template_file {'index'}

sub template_installer {'install'}

sub load {
    my ( $zApp, $class, $method ) = @_;
    my $file = $class . '.pm';
    $file =~ s{::}{/}g;
    require $file;
    index( $method, '_' ) == 0 and return 0;
    return $class->can($method);
}

sub validate {
    my ( $zApp, $iObject, $iRequest, $user, $pass ) = @_;
    $user ||= $iRequest->param('I');
    $pass ||= $iRequest->param('P');

    require Zeromin::User;
    my $zUser = $zApp->{__user} || Zeromin::User->new( $iObject, $user );
    $zUser->load();
    $zUser->is_initialized() or return 1;

    my $is_valid = $zUser->is_valid( $pass, $user );
    if ($is_valid) {
        $zApp->{__user} = $zUser;
        return 1;
    }

    $zApp->logger( 0, 'FAILED TO LOGIN FROM %s WITH %s', [ $user, $pass ] );
    return 0;
}

sub _request {
    my ($zApp)   = @_;
    my $iKernel  = $zApp->kernel();
    my $iRequest = $zApp->request();

    local ( $!, *FH );
    open *FH, '>dbg.txt'    ## no critic
        or $iKernel->throw_io_exception('dbg.txt');
    require Data::Dumper;
    print {*FH}
        Data::Dumper::Dumper( [ scalar( localtime( time() ) ), $iRequest ] );
    close *FH or $iKernel->throw_io_exception('dbg.txt');
    return;
}

1;
__END__
