#/*
# *  Copyright 2007-2010 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: Text.pm 1901 2009-12-31 02:40:20Z hikarin $
#

package Img0ch::App::Search::Text;

use strict;
use Data::Page::Navigation qw();
use Img0ch::BBS qw();
use Img0ch::Template qw();

sub new {
    my ( $iClass, $iKernel, @args ) = @_;
    bless {
        __bbs     => undef,
        __config  => $iKernel->get_config(),
        __kernel  => $iKernel,
        __mobile  => 0,
        __resno   => 0,
        __request => Img0ch::Request->new(@args),
        __setting => undef,
    }, $iClass;
}

sub run {
    my ($iApp) = @_;
    $iApp->init() or return $iApp->redirect_error('init');

    my ( $iTemplate, $filter );
    if ( !$iApp->{__disable} and my $word = $iApp->{__word} ) {
        ( $iTemplate, $filter ) = $iApp->search($word);
    }
    else {
        ( $iTemplate, $filter ) = $iApp->render();
    }

    my $iRequest = $iApp->{__request};
    my $cs = $iApp->{__bbs} ? $iApp->{__bbs}->get_encoding() : undef;
    $iRequest->send_http_header( 'text/html', $cs );
    $iTemplate->flush($filter);
    return 1;
}

sub init {
    my ($iApp)   = @_;
    my $iKernel  = $iApp->{__kernel};
    my $iRequest = $iApp->{__request};
    $iRequest->init( $iApp->{__config} );

    my $bbs = $iRequest->bbs();
    if ( my $path_info = $iRequest->path_info() ) {
        $path_info =~ s{\A/}{}xms;
        my @path = split '/', $path_info, 2;
        ( $path[0] || 'test' ) =~ /([\w\-]+)/xms;
        $bbs = $1;
    }

    my $iBBS = Img0ch::BBS->new( $iKernel, { bbs => $bbs, } );
    if ( !$iBBS->get_id() ) {
        $iApp->{__error} = 'INVALID_POST';
        return 0;
    }

    my $encoding = $iKernel->get_encoding(1);
    $iApp->{__bbs}     = $iBBS;
    $iApp->{__disable} = $iKernel->get_config()->get('search.text_disabled');
    $iApp->{__setting} = $iBBS->get_setting_instance();
    $iApp->{__mobile}  = $iRequest->param('m') ? 1 : 0;
    $iApp->{__word} = $iKernel->get_encoded_str( $iRequest->param('query') )
        || $iKernel->get_encoded_str( $iRequest->param('word') );
    return 1;
}

sub search {
    my ( $iApp, $word ) = @_;
    my $iBBS          = $iApp->{__bbs};
    my $template_tags = {};
    my $type          = {
        'subject' => 0,
        'FROM'    => 1,
        'mail'    => 2,
        'date'    => 3,
        'id'      => 4,
        'MESSAGE' => 5,
    };

    my $iConfig  = $iApp->{__config};
    my $iKernel  = $iApp->{__kernel};
    my $iRequest = $iApp->{__request};
    my $iSubject = $iBBS->get_subject_instance();
    my $iThread  = $iBBS->get_thread_instance(0);
    my $aas_path = '';
    my $bbs      = $iBBS->get_name();
    my $cachedir = $iConfig->get('search.text_cache_dir');
    my $found    = 0;
    my $now      = $iRequest->now();
    my $s_type   = $iRequest->param('type');
    my $pos      = exists $type->{$s_type} ? $type->{$s_type} : 5;
    my $result   = [];
    my $mobile   = $iApp->{__mobile};
    my $digest   = Digest::MD5::md5_hex( $bbs, $word, $pos, $mobile );
    my $path     = join '/', $cachedir, $digest;

    if ($mobile) {
        my $server = $iConfig->get('Server');
        my ( $aas_base, $aas_dir ) = $server =~ m{http://(.+?)/(.*)?}xms;
        if ( ( $aas_dir || '' ) ne '' ) {
            $aas_dir =~ s{/\z}{}xms;
            $aas_dir =~ tr|/|+|;
            $aas_base .= '+' . $aas_dir;
        }
        $aas_path = $aas_base;
    }

    if ( $cachedir and -r $path ) {
        if ( defined $Storable::VERSION or eval 'use Storable qw(); 1' ) {
            @{$result} = @{ Storable::retrieve($path) }
                or $iKernel->throw_io_exception($path);
        }
        else {
            defined $FreezeThaw::VERSION or require FreezeThaw;
            my $fh = $iKernel->get_read_file_handle($path);
            @{$result} = FreezeThaw::thaw(
                do { local $/ = undef; <$fh> }
            );
            close $fh or $iKernel->throw_io_exception($path);
        }
        my $gc = $iConfig->get_int('search.text_cache_gc') || 128;
        if ( int( rand($gc) ) == 0 ) {
            my $expires = $iConfig->get_int('search.text_cache_expires')
                || 86400;
            opendir my $fh, $cachedir
                or $iKernel->throw_io_exception($cachedir);
            my @files = readdir $fh;
            closedir $fh or $iKernel->throw_io_exception($cachedir);
            for my $cache ( grep {/\A[0-9a-f]{32}\z/xms} @files ) {
                my $cache_path = join '/', $cachedir, $cache;
                my $modified = ( stat($cache_path) )[9];
                if ( $now > $modified + $expires ) {
                    unlink $cache_path
                        or $iKernel->throw_io_exception($cache_path);
                }
            }
        }
        $found = scalar @$result;
    }
    else {
        my $iPlugin = $iBBS->get_plugin_instance();
        my ( $param, $plugin );
        if ($mobile) {
            $param = {
                url => join( '/',
                    $iRequest->get_app_uri(),
                    ( $iConfig->get('MobileRenderer') || 'r.cgi' ), $bbs )
            };
            $plugin
                = $iPlugin->iterator( 'mobile.rewrite', sub { ${ $_[3] } } );
        }
        else {
            $param = {};
            $plugin = $iPlugin->iterator( 'bbs.rewrite', sub { ${ $_[3] } } );
            $iConfig->get('EnableXHTMLBreak')
                and $iThread->enable_xhtml_break();
        }
        $iSubject->load();
        my $result_hash = {};
        if ( $pos == 0 ) {
            my $ret = $iSubject->grep($word);
            for my $key ( sort keys %$ret ) {
                $iThread->set_key($key);
                my $count           = $iThread->count();
                my $subject         = $iThread->get_subject();
                my $result_internal = [];
                my $res             = $iThread->get(1);
                my $text            = $res->[4];
                my $id              = $res->[3];
                my $date = $id ? join( ' ', $res->[2], $id ) : $res->[2];
                $plugin->( $key, $count, 1, \$text, $param );
                unshift @$result,
                    {
                    key     => $key,
                    resno   => 1,
                    name    => $res->[0],
                    mail    => $res->[1],
                    date    => $date,
                    text    => $text,
                    subject => $subject,
                    count   => $count,
                    };
            }
        }
        else {
            for my $key ( @{ $iSubject->to_array() } ) {
                $iThread->set_key($key);
                my $count           = $iThread->count();
                my $subject         = $iThread->get_subject();
                my $result_internal = [];
                my $grepped_result  = $iThread->grep( $word, $pos );
                for my $resno ( sort keys %$grepped_result ) {
                    my $res  = $grepped_result->{$resno};
                    my $text = $res->[4];
                    my $id   = $res->[3];
                    my $date = $id ? join( ' ', $res->[2], $id ) : $res->[2];
                    $plugin->( $key, $count, $resno, \$text, $param );
                    push @$result_internal,
                        {
                        key     => $key,
                        resno   => $resno,
                        name    => $res->[0],
                        mail    => $res->[1],
                        date    => $date,
                        text    => $text,
                        subject => $subject,
                        count   => $count,
                        };
                }
                my $found_in_thread = scalar @$result_internal;
                map { $_->{found} = $found_in_thread }
                    @$result_internal;    ## no critic
                $result_hash->{$key} = $result_internal;
            }
            map { unshift @$result, @{ $result_hash->{ $_->[1] } } }
                sort { $a->[0] <=> $b->[0] }
                map { [ scalar @{ $result_hash->{$_} }, $_ ] }
                keys %$result_hash;
        }
        $found = scalar @$result;
        if ( $found and -r $cachedir and -w $cachedir and -x $cachedir ) {
            if ( defined $Storable::VERSION or eval 'use Storable qw(); 1' ) {
                Storable::nstore( $result, $path )
                    or $iKernel->throw_io_exception($path);
            }
            else {
                defined $FreezeThaw::VERSION or require FreezeThaw;
                my $fh = $iKernel->get_write_handle($path);
                binmode $fh;
                print ${fh} FreezeThaw::freeze($result);
                close $fh or $iKernel->throw_io_exception($path);
            }
        }
    }

    my $offset        = $iRequest->param_int('offset')  || 1;
    my $item_per_page = $iRequest->param_int('entries') || 5;
    my $page = Data::Page->new( $found, $item_per_page, $offset );
    my $pages = [];
    map { push @$pages, { num => $_ } }
        $page->pages_in_navigation(
        $iConfig->get_int('search.pages_in_navigation') || 15 );

    my $select_type = 'Selected' . ucfirst lc $s_type;
    %{$template_tags} = (
        AASBase         => $aas_path,
        Current         => $page->current_page(),
        Item            => $page->entries_per_page(),
        Next            => $page->next_page(),
        Pages           => $pages,
        Prev            => $page->previous_page(),
        Result          => [ $page->splice($result) ],
        Total           => $page->total_entries(),
        Query           => $word,
        QueryURLEscaped => $iRequest->escape_uri($word),
        SearchType      => $s_type,
        $select_type    => 1,
    );

    return $iApp->render($template_tags);
}

sub render {
    my ( $iApp, $template_tags ) = @_;
    my $iBBS     = $iApp->{__bbs};
    my $iRequest = $iApp->{__request};
    my $mobile   = $iApp->{__mobile};
    $template_tags ||= {};

    my $iMeta     = $iBBS->get_metadata_instance();
    my $iTemplate = $iBBS->get_template_instance(
        {   file => ( $mobile ? 'search_text_mobile' : 'search_text' ),
            request => $iRequest,
            setting => $iApp->{__setting},
        }
    );

    $iTemplate->param(
        {   Banner   => $iMeta->main_banner(),
            Disabled => $iApp->{__disable},
            Mobile   => $mobile,
            META     => $iMeta->meta(),
            %$template_tags,
        }
    );

    my $filter = $mobile ? sub { ${ $_[0] } =~ tr/\n//d; ${ $_[0] } } : undef;
    return ( $iTemplate, $filter );
}

sub redirect_error {
    my ($iApp)   = @_;
    my $iKernel  = $iApp->{__kernel};
    my $iRequest = $iApp->{__request};
    my $key      = $iRequest->key();
    my $ip       = $iRequest->ip_int();

    defined &{'Img0ch::Error::new'} or require Img0ch::Error;
    my $iError = Img0ch::Error->new( $iKernel, $key, $ip );
    my $error = $iApp->{__error} || 'INVALID_POST';
    my $long = $iError->get($error);

    if ( $iRequest->param('js') ) {
        $iRequest->send_http_header( 'text/javascript', 'UTF-8' );
        $iRequest->print(
            $iRequest->get_json( { ok => 0, error => $error } ) );
    }
    else {
        my $iTemplate = Img0ch::Template->new(
            ( $iApp->{__bbs} || $iKernel ),
            {   file    => ('error'),
                request => $iRequest,
                setting => $iApp->{__setting},
            }
        );
        $iTemplate->param(
            {   Banner       => '',
                Flag         => 0,
                LongErrorStr => $long,
            }
        );
        my $iConfig = $iApp->{__config};
        my $charset = $iConfig->get('DefaultCharset');
        $iRequest->send_http_header( 'text/html', $charset );
        $iTemplate->flush();
    }
    return 0;
}

1;
__END__
