#/*
# *  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: thread.pm 1720 2008-06-04 15:48:49Z hikarin $
#

package Zeromin2::App::thread;

use strict;
use Zeromin::Thread qw();
use Zeromin2::App qw();

BEGIN {
    Zeromin2::App->install_alias( __PACKAGE__,
        {   'freeze_file'        => [ 'upload', 'freeze_file' ],
            'release_file'       => [ 'upload', 'release_file' ],
            'deny_ip_local'      => [ 'filter', 'deny_ip_local' ],
            'deny_ip_global'     => [ 'filter', 'deny_ip_global' ],
            'deny_serial_local'  => [ 'filter', 'deny_serial_local' ],
            'deny_serial_global' => [ 'filter', 'deny_serial_global' ],
        }
    );
}

sub load {
    my ($zApp) = @_;
    $zApp->privilege(undef) or return $zApp->return_value(1);

    require Img0ch::App::Mock::Read;
    my $iRequest = $zApp->request();
    my $iBBS     = $zApp->bbs();
    my $key      = $zApp->key();
    my $option   = $iRequest->param('option');
    my $reader   = Img0ch::App::Mock::Read->new( $iBBS, $key, $option );
    my $template_parameters = $reader->run();

    require Img0ch::Thread::Revision;
    require Img0ch::Upload::Poll;
    require Zeromin2::Util;
    my $iLog       = $iBBS->get_log_instance($key);
    my $iTRevision = Img0ch::Thread::Revision->new( $iBBS, $key );
    my $iUPoll     = Img0ch::Upload::Poll->new( $iBBS, $key );
    my $privilege  = $zApp->privilege('can_view_thread_log');
    my $rewriter   = Zeromin2::Util::get_rewriter($zApp);
    my $rslv       = $iRequest->param('resolve');

    for my $res ( @{ $template_parameters->{Thread} } ) {
        my $resno = $res->{resno};
        if ($privilege) {
            my $info = $iLog->get($resno);
            my $ip   = $info->[1];
            $ip = $ip ? join '.', unpack( 'C*', $ip ) : '(null)';
            $res->{ip}     = $ip;
            $res->{host}   = $rslv ? Zeromin2::Util::ip2host($ip) : '(null)';
            $res->{serial} = $info->[2] || '(null)';
            $res->{agent}  = $info->[3] || '(null)';
        }
        else {
            $res->{ip}     = '(null)';
            $res->{host}   = '(null)';
            $res->{serial} = '(null)';
            $res->{agent}  = '(null)';
        }
        $res->{revision} = $iTRevision->get_latest_id();
        $res->{poll_bad_count} = $iUPoll->count( $resno, 'BAD' );
        $rewriter->( \$res->{text} );
    }

    $rewriter->( \$template_parameters->{HEAD} );
    $rewriter->( \$template_parameters->{FOOT} );
    $rewriter->( \$template_parameters->{Banner} );
    $rewriter->( \$template_parameters->{SubBanner} );

    $zApp->add_template_param(
        {   %$template_parameters,
            BBSName        => $iBBS->get_name(),
            IsTypeTemplate => (
                $iBBS->get_setting_instance()->get('BBS_TEMPLATE')
                    =~ /\Atype/xms ? 1 : 0
            ),
            Option => $option,
        }
    );
    $zApp->encoding('Shift_JIS');
    return $zApp->return_value(0);
}

sub download_log {
    my ($zApp) = @_;
    $zApp->prevent_saving_session();
    $zApp->privilege(undef) or return $zApp->return_value(1);

    my $bbs = $zApp->bbs()->get_name();
    my $key = $zApp->key();
    $zApp->content_type('text/x-csv');
    $zApp->request()
        ->set_header( 'content-disposition',
        'attachment; filename="' . $bbs . '-' . $key . '.csv"' );
    return load($zApp);
}

sub lookup {
    my ($zApp) = @_;
    $zApp->privilege( 'can_view_thread_log', 1 )
        or return $zApp->return_value( 1, [] );

    require Data::Page;
    require Zeromin2::Util;

    my $ret      = [];
    my $target   = $zApp->request()->param('target');
    my $iKernel  = $zApp->kernel();
    my $encoding = $iKernel->get_encoding(1);
    my $iLog     = $zApp->bbs()->get_log_instance(0);
    my $result   = $iLog->search($target);
    my $page     = Data::Page->new( scalar @$result, $zApp->page_param() );

    for my $log ( $page->splice($result) ) {
        push @$ret,
            {
            key    => $log->[0],
            resno  => $log->[1],
            date   => Zeromin2::Util::format_date( $log->[2] ),
            ip     => $log->[3],
            serial => $log->[4],
            agent  => $log->[5],
            subject =>
                $iKernel->get_encoded_str( $log->[6], 'utf8', $encoding ),
            };
    }

    $zApp->add_template_param( { Target => $target } );
    return $zApp->return_value( 0, $ret, $page );
}

sub edit_res {
    my ($zApp) = @_;
    $zApp->check_csrf() or return $zApp->return_value(1);
    $zApp->privilege( 'can_edit_res', 1 ) or return $zApp->return_value(1);

    my $iBBS     = $zApp->bbs();
    my $iKernel  = $zApp->kernel();
    my $key      = $zApp->key();
    my $iRequest = $zApp->request();
    my $resno    = $iRequest->param_int('resno') || 1;
    my ( $unijp, $encoding ) = $zApp->unijp();

    my $zThread = Zeromin::Thread->new( $iBBS, $key );
    my $count = $zThread->load() or return $zApp->return_value(2);
    my $from
        = $iKernel->get_config()->get('EnableEditName')
        ? $iKernel->get_encoded_str( $iRequest->param( 'FROM', 1 ),
        undef, 'utf8' )
        : undef;
    defined $from and $from =~ tr/\r\t\n//d;
    my $mail = $iKernel->get_encoded_str( $iRequest->param('mail'), undef,
        'utf8' );
    $mail =~ tr/\r\t\n//d;
    my $message = $iKernel->get_encoded_str( $iRequest->param( 'MESSAGE', 1 ),
        undef, 'utf8' );
    $message =~ tr/\t//d;
    $message =~ s/\r\n/\n/gxms;
    $message =~ s/\n/<br>/gxms;
    my $subject
        = ( $resno == 1 )
        ? $iKernel->get_encoded_str( $iRequest->param( 'subject', 1 ),
        undef, 'utf8' )
        : undef;

    if ($zThread->edit(
            $resno, [ $from, $mail, undef, undef, $message, $subject ]
        )
        )
    {
        $zThread->save();
        $zApp->logger(
            1,
            'EDITED RES: %d at %s/dat/%s.dat',
            [ $resno, $iBBS->get_name(), $key ],
        );
        if ($subject) {
            my $iSubject = $iBBS->get_subject_instance();
            $iSubject->sage( $key, $count, $subject );
            $iSubject->save();
        }
        return $zApp->return_value(0);
    }
    else {
        $zApp->logger(
            0,
            'TRIED EDITTING INEXISTING RES: %d at %s/dat/%s.dat',
            [ $resno, $iBBS->get_name(), $key ]
        );
        return $zApp->return_value(2);
    }
}

sub edit_res_form {
    my ($zApp) = @_;
    $zApp->privilege( 'can_edit_res', 1 )
        or return $zApp->return_value(1);

    my $iKernel  = $zApp->kernel();
    my $iBBS     = $zApp->bbs();
    my $key      = $zApp->key();
    my $resno    = $zApp->request()->param_int('resno') || 1;
    my $iSetting = $iBBS->get_setting_instance();
    my $iThread  = $iBBS->get_thread_instance($key);

    my $res = $iThread->get($resno);
    my ( $unijp, $encoding ) = $zApp->unijp();
    my $text = $res->[4];
    $text =~ s/<[bB][rR]>/\n/gxms;
    $text =~ s/\s+\n\s+/\n/gxms;
    $text =~ s/\A\s+//xms;
    $text =~ s/\s+\z//xms;
    $text =~ s/\n+\z//xms;

    $zApp->add_template_param(
        {   BBS_TITLE => $iKernel->get_encoded_str(
                $iSetting->get('BBS_TITLE'),
                'utf8', $encoding
            ),
            KEY     => $key,
            Resno   => $resno,
            Subject => $iKernel->get_encoded_str(
                $iThread->get_subject(), 'utf8', $encoding
            ),
            IsFirstRes => ( $resno == 1 ? 1 : 0 ),
            IsNameEditable =>
                $zApp->kernel()->get_config()->get('EnableEditName'),
            IsTypeTemplate =>
                ( $iSetting->get('BBS_TEMPLATE') =~ /\Atype/xms ? 1 : 0 ),
            Name => $iKernel->get_encoded_str( $res->[0], 'utf8', $encoding ),
            Mail => $iKernel->get_encoded_str( $res->[1], 'utf8', $encoding ),
            Date => $iKernel->get_encoded_str(
                "$res->[2]&nbsp;$res->[3]", 'utf8', $encoding
            ),
            Message => $unijp->set( $text, $encoding )->get(),
        }
    );
    return $zApp->return_value(0);
}

sub do_import {
    my ($zApp) = @_;
    $zApp->check_csrf() or return $zApp->return_value(1);
    $zApp->privilege( 'can_recreate_subject', 1 )
        or return $zApp->return_value(1);

    my $iRequest = $zApp->request();
    $iRequest->is_uploadable('file') or return $zApp->return_value(2);

    my $zThread = Zeromin::Thread->new( $zApp->bbs() );
    my $param   = $iRequest->filename('file');
    my $ret     = $zThread->import_from( $iRequest->tempfile(), $param );
    return $zApp->return_value( $ret ? 0 : 3 );
}

sub create_special {
    my ($zApp) = @_;
    $zApp->check_csrf() or return $zApp->return_value(1);
    $zApp->privilege( 'can_edit_head', 1 ) or return $zApp->return_value(1);

    require Img0ch::App::BBS;
    require Zeromin::Thread::Special;
    my $iBBS = $zApp->bbs();
    my $iApp = Img0ch::App::BBS->new( $iBBS->get_kernel(),
        $zApp->request()->request() );
    my $zThread = Zeromin::Thread->new($iBBS);
    my $zTS     = Zeromin::Thread::Special->new($iBBS);
    my $dir     = $iBBS->get_name();
    my $tp      = { BBSName => $dir };
    my $return  = 0;

    $iApp->init();
    if ( $iApp->validate() ) {
        $iApp->get_date();
        $zTS->load();
        my $key = $zApp->request()->param('key');
        $zThread->set_key($key);
        my $count = $zThread->count();
        if ( !$count ) {
            $key = $zTS->get_latest_key();
            $zThread->set_key($key);
            $count = $zThread->count();
        }
        my $type = $count ? 'ADDED RES TO' : 'CREATED';
        my $iKernel = $zApp->kernel();
        my $subject
            = $iKernel->get_encoded_str( $iApp->request()->param('subject'),
            $iKernel->get_encoding(1), 'utf8' );
        $zThread->set(
            [   $iApp->get_name(),             $iApp->get_mail,
                @{ $iApp->get_date_and_id() }, $iApp->get_comment(),
                ( $count ? '' : $subject )
            ]
        );
        $zThread->save();
        my $iSubject = $iBBS->get_subject_instance();
        $iSubject->age( $key, $zThread->count(), $zThread->get_subject() );
        $iSubject->save();
        $tp->{ ( $count ? 'IsAdded' : 'IsCreated' ) } = 1;
        $zApp->logger(
            1,
            '%s SPECIAL THREAD: %s/dat/%s.dat',
            [ $type, $dir, ( ref($key) ? $key->bstr() : $key ) ]
        );
    }
    else {
        $tp->{Error} = $iApp->get_error();
        $return = 2;
    }

    $zApp->add_template_param($tp);
    return $zApp->return_value(0);
}

sub rewrite_all {
    my ($zApp) = @_;
    $zApp->check_csrf() or return $zApp->return_value(1);
    $zApp->privilege('can_create_bbs') or return $zApp->return_value(1);

    my ( $unijp, undef ) = $zApp->unijp();
    my $iRequest = $zApp->request();
    my $from     = $iRequest->param('from');
    my $to       = $iRequest->param('to');
    if ( !$from or !$to ) {
        return $zApp->return_value(2);
    }
    if (   Unicode::Japanese->getcode($from) ne 'ascii'
        or Unicode::Japanese->getcode($to) ne 'ascii' )
    {
        return $zApp->return_value(3);
    }
    my $from_re = qr/$from/xms;

    my $iBBS     = $zApp->bbs();
    my $iSubject = $iBBS->get_subject_instance();
    my $zThread  = Zeromin::Thread->new( $iBBS, 0 );
    $iSubject->load();

    for my $key ( @{ $iSubject->to_array() } ) {
        $zThread->set_key($key);
        my $count = $zThread->count();
        for ( my $i = 1; $i <= $count; $i++ ) {
            my $res     = $zThread->get($i);
            my $comment = $res->[4];
            $comment =~ s/$from_re/$to/xms;
            $res->[4] = $comment;
            $zThread->set( $res, $i );
        }
        $zThread->save();
    }

    return $zApp->return_value(0);
}

sub rewrite_form {
    my ($zApp) = @_;
    $zApp->privilege('can_create_bbs') or return $zApp->return_value(1);
    return $zApp->return_value(0);
}

sub load_revision {
    my ($zApp) = @_;
    $zApp->privilege( 'can_edit_res', 1 ) or return $zApp->return_value(1);

    my $iKernel  = $zApp->kernel();
    my $iRequest = $zApp->request();
    my $iBBS     = $zApp->bbs();
    my $key      = $zApp->key();
    my $resno    = $iRequest->param_int('resno');

    require Img0ch::Thread::Revision;
    my $iSetting   = $iBBS->get_setting_instance();
    my $iThread    = $iBBS->get_thread_instance($key);
    my $iTRevision = Img0ch::Thread::Revision->new( $iBBS, $key );
    my $latest     = $iTRevision->get_latest_id($resno) + 1;
    my $revision   = $iRequest->param_int('revision') || $latest;
    my $revisions  = $iTRevision->get_revisions($resno);
    my $stack      = [];
    my $fallback   = $iThread->get($resno);
    my ( $fb_name, $fb_mail, $fb_date, $fb_id, $fb_message ) = @$fallback;

    for my $one (@$revisions) {
        my ( $rev, $content ) = @$one;
        unshift @$stack,
            {
            revision => $rev,
            name     => $content->[0],
            mail     => $content->[1],
            date     => $content->[2],
            id       => $content->[3],
            comment  => $content->[4],
            selected => $rev == $revision,
            };
    }

    unshift @$stack,
        {
        revision => $latest,
        name     => $fb_name,
        mail     => $fb_mail,
        date     => $fb_date,
        id       => $fb_id,
        comment  => $fb_message,
        selected => $latest == $revision,
        };

    my $encoding = $iKernel->get_encoding(1);
    my $ret = $iTRevision->get( $resno, $revision );
    $zApp->add_template_param(
        {   BBS_TITLE => $iKernel->get_encoded_str(
                $iSetting->get('BBS_TITLE'),
                'utf8', $encoding
            ),
            CurrentId => $revision,
            Date      => $iKernel->get_encoded_str(
                ( $ret->[2] || $fb_date ),
                'utf8', $encoding
            ),
            Id => $iKernel->get_encoded_str(
                ( $ret->[3] || $fb_id ),
                'utf8', $encoding
            ),
            KEY      => $key,
            LatestId => $latest,
            Mail     => $iKernel->get_encoded_str(
                ( $ret->[1] || $fb_mail ),
                'utf8', $encoding
            ),
            Message => $iKernel->get_encoded_str(
                ( $ret->[4] || $fb_message ),
                'utf8', $encoding
            ),
            Name => $iKernel->get_encoded_str(
                ( $ret->[0] || $fb_name ),
                'utf8', $encoding
            ),
            Resno     => $resno,
            Revisions => $stack,
            Subject   => $iKernel->get_encoded_str(
                $iThread->get_subject(), 'utf8', $encoding
            ),
        }
    );

    return $zApp->return_value(0);
}

sub revert_res {
    my ($zApp) = @_;
    $zApp->check_csrf() or return $zApp->return_value(1);
    $zApp->privilege( 'can_edit_res', 1 ) or return $zApp->return_value(1);

    my $iBBS     = $zApp->bbs();
    my $key      = $zApp->key();
    my $iRequest = $zApp->request();
    my $resno    = $iRequest->param_int('resno');
    my $revision = $iRequest->param_int('revision');

    my $zThread = Zeromin::Thread->new( $iBBS, $key );
    $zThread->load();
    $zThread->revert( { $resno => $revision } );
    $zApp->logger(
        1,
        'REVERTED RES TO REVISION %d: %d at %s/dat/%s.dat',
        [ $revision, $resno, $iBBS->get_name(), $key ],
    );

    return $zApp->return_value(0);
}

sub remove {
    _task(
        shift, 'can_remove_thread',
        'REMOVED THREAD',
        'TRIED REMOVING THREAD', 'remove'
    );
}

sub stop {
    _task(
        shift, 'can_stop_thread',
        'STOPPED THREAD',
        'TRIED STOPPING THREAD', 'stop'
    );
}

sub restart {
    _task(
        shift, 'can_restart_thread',
        'RESTARTED THREAD',
        'TRIED RESTARTING THREAD', 'restart'
    );
}

sub repair_upload_file_table {
    _task(
        shift, 'can_stop_thread',
        'REPAIRED THREAD UPLOAD FILE TABLE',
        'TRIED REPAIRING THREAD UPLOAD FILE TABLE',
        'repair_upload_file_table'
    );
}

sub remove_res {
    _task2( shift, 'can_abone_res', 'REMOVED RES WITH ABONE', {} );
}

sub erase_res {
    _task2(
        shift, 'can_remove_res',
        'REMOVED RES WITH ERASE',
        { is_erase => 1 }
    );
}

sub remove_file {
    _task2(
        shift, 'can_abone_res',
        'REMOVED RES WITH FILE ONLY',
        { is_file_only => 1 }
    );
}

sub _task {
    my ( $zApp, $priv_meth, $success, $fail, $method ) = @_;
    $zApp->check_csrf() or return $zApp->return_value(1);
    $zApp->privilege( $priv_meth, 1 ) or return $zApp->return_value(1);

    my @param = $zApp->request()->param('thread');
    scalar @param or return $zApp->return_value(2);

    my $zBBS    = $zApp->bbs();
    my $bbs     = $zBBS->get_name();
    my $status  = {};
    my $zThread = Zeromin::Thread->new($zBBS);

    $zThread->load();
    for my $key (@param) {
        $zThread->set_key($key);
        if ( $zThread->$method ) {
            $zApp->logger( 1, ( $success . ': %s/%s' ), [ $bbs, $key ] );
            $status->{$key} = 1;
        }
        else {
            $zApp->logger( 0, ( $fail . ': %s/%s' ), [ $bbs, $key ] );
            $status->{$key} = 0;
        }
    }

    return $zApp->return_value( 0, $status );
}

sub _task2 {
    my ( $zApp, $priv_meth, $success, $argument ) = @_;
    $zApp->check_csrf() or return $zApp->return_value(1);
    $zApp->privilege( $priv_meth, 1 ) or return $zApp->return_value(1);

    my $key = $zApp->key() || return $zApp->return_value(3);
    my @param = $zApp->request()->param('resno');
    scalar @param or return $zApp->return_value(2);

    my $zBBS    = $zApp->bbs();
    my $bbs     = $zBBS->get_name();
    my $status  = {};
    my $zThread = Zeromin::Thread->new( $zBBS, $key );

    $zThread->load();
    $zThread->remove( [@param], $argument );
    for my $resno (@param) {
        Img0ch::Kernel::intval($resno) or next;
        $zApp->logger(
            1,
            ( $success . ' %d at %s/dat/%s.dat' ),
            [ $resno, $bbs, $key ]
        );
        $status->{$resno} = 1;
    }

    return $zApp->return_value( 0, $status );
}

1;
__END__
