#/*
# *  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: Text.pm 850 2007-06-23 11:39:18Z hikarin $
#

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

use strict;

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};
    $iRequest->send_http_header('text/html');
    $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;
    }

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

    require Img0ch::Setting;
    my $iSetting = Img0ch::Setting->new($iBBS);
    $iSetting->load();

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

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

    require Data::Page::Navigation;
    require Digest::MD5;
    require Img0ch::Subject;
    require Img0ch::Thread;
    my $iConfig  = $iApp->{__config};
    my $iKernel  = $iApp->{__kernel};
    my $iRequest = $iApp->{__request};
    my $iSubject = Img0ch::Subject->new($iBBS);
    my $iThread  = Img0ch::Thread->new( $iBBS, 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} : 4;
    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 ( eval 'use Storable qw(); 1' ) {
            @{$result} = @{ Storable::retrieve($path) }
                or $iKernel->throw_io_exception($path);
        }
        else {
            require FreezeThaw;
            local ( *FH, $! );
            open *FH, "<${path}"    ## no critic
                or $iKernel->throw_io_exception($path);
            binmode *FH;
            @{$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 ) {
            local ( *DH, $! );
            my $expires = $iConfig->get_int('search.text_cache_expires')
                || 86400;
            opendir *DH, $cachedir
                or $iKernel->throw_io_exception($cachedir);
            my @files = readdir *DH;
            closedir *DH 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 {
        require Img0ch::Plugin;
        my $iPlugin = Img0ch::Plugin->new($iBBS);
        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 = {};
        for my $key ( @{ $iSubject->to_array() } ) {
            $iThread->set_key($key);
            my $count           = $iThread->count();
            my $result_internal = [];
            for ( my $i = 1; $i <= $count; $i++ ) {
                my $res = $iThread->get($i);
                if ( index( $res->[$pos], $word ) >= 0 ) {
                    my $subject = $iSubject->get($key);
                    my $text    = $res->[4];
                    my $id      = $res->[3];
                    my $date = $id ? join( ' ', $res->[2], $id ) : $res->[2];
                    $plugin->( $key, $count, $i, \$text, $param );
                    push @$result_internal,
                        {
                        key     => $key,
                        resno   => $i,
                        name    => $res->[0],
                        mail    => $res->[1],
                        date    => $date,
                        text    => $text,
                        subject => $subject->[0],
                        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 ( eval 'use Storable qw(); 1' ) {
                Storable::nstore( $result, $path )
                    or $iKernel->throw_io_exception($path);
            }
            else {
                require FreezeThaw;
                local ( *FH, $! );
                open *FH, ">${path}"    ## no critic
                    or $iKernel->throw_io_exception($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 );

    %{$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,
    );
    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 ||= {};

    require Img0ch::Metadata;
    require Img0ch::Template;
    my $iMeta     = Img0ch::Metadata->new($iBBS);
    my $iTemplate = Img0ch::Template->new(
        $iBBS,
        {   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();

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

    if ( $iRequest->param('js') ) {
        $iRequest->send_http_header( 'text/javascript', 'UTF-8' );
        $iRequest->print(
            $iRequest->get_json( { ok => 0, error => $error } ) );
    }
    else {
        require Img0ch::Template;
        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__
