#/*
# *  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: VTA.pm 2022 2010-05-05 11:12:29Z hikarin $
#

package Img0ch::App::VTA;

use strict;
use Img0ch::BBS qw();
use Img0ch::Template qw();
use Img0ch::Thread qw();
use Img0ch::Thread::Virtual qw();

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

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

    my $iRequest = $iApp->{__request};
    my $user     = $iRequest->param('user');
    my $pass     = $iRequest->param('pass');
    my ( $result, $iTemplate, $ctype, $body );
    if ( $user and $pass ) {
        ( $result, $iTemplate, $ctype, $body )
            = $iApp->process( $user, $pass );
    }
    else {
        ( $result, $iTemplate, $ctype, $body ) = $iApp->render();
    }

    $result or return $iApp->redirect_error('remove');
    my $cs = $iApp->{__bbs} ? $iApp->{__bbs}->get_encoding() : undef;
    $iRequest->send_http_header( $ctype || 'text/html', $cs );
    $iTemplate ? $iTemplate->flush() : $iRequest->print($body);
    return 1;
}

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

    my $bbs    = $iRequest->bbs();
    my $key    = $iRequest->param_int('key');
    my $resno  = $iRequest->param_int('resno');
    my $option = $iRequest->param('option');
    if ( my $path_info = $iRequest->path_info() ) {
        my @path = split '/', $path_info;
        ( $path[1] || 'test' ) =~ /([\w\-]+)/xms;
        $bbs = $1;
        ( $path[2] || '00000000' ) =~ /(\d{9,10})/xms;
        $key = $1;
        ( $path[3] || '0' ) =~ /(\d+)/xms;
        $resno = $1;
        $option ||= $path[4] || '';
    }

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

    $iApp->{__bbs}     = $iBBS;
    $iApp->{__setting} = $iBBS->get_setting_instance();

    my $iThread = $iBBS->get_thread_instance($key);
    if ( !$iThread->count() ) {
        $iApp->{__error} = 'INVALID_POST';
        return 0;
    }
    $iApp->{__key}    = $key;
    $iApp->{__option} = $option;
    $iApp->{__thread} = $iThread;

    return 1;
}

sub process {
    my ( $iApp, $user, $pass ) = @_;
    my $iBBS           = $iApp->{__bbs};
    my $iRequest       = $iApp->{__request};
    my $iSetting       = $iApp->{__setting};
    my $key            = $iApp->{__key};
    my $template_param = {};

    my $iTVirtual = Img0ch::Thread::Virtual->new( $iBBS, $key );
    if ( $iTVirtual->is_valid( $user, $pass ) ) {
        %$template_param = (
            Validate => 1,
            User     => $user,
            Pass     => $pass,
        );
        my $page              = $iRequest->param('mode');
        my $priv              = $iTVirtual->get_privilege($user);
        my @setting_int_keys  = $iTVirtual->get_setting_integer_keys();
        my @setting_str_keys  = $iTVirtual->get_setting_string_keys();
        my @setting_bool_keys = $iTVirtual->get_setting_boolean_keys();
        my @all_keys
            = ( @setting_int_keys, @setting_str_keys, @setting_bool_keys );
        $priv & $iTVirtual->administrator()
            and $priv = $iTVirtual->all_privileges();

        if ( $page eq 'setting' ) {
            my $max_length = $iTVirtual->get_setting_string_max_length();
            if (    $iRequest->param('submit')
                and $priv & $iTVirtual->can_change_setting() )
            {
                for my $setting_key (@setting_int_keys) {
                    my $value   = $iRequest->param_int($setting_key);
                    my $default = $iSetting->get_int($setting_key);
                    $value > $default and $value = $default;
                    $iTVirtual->set_setting( $setting_key,
                        $value || $iTVirtual->get_setting_int($setting_key) );
                }
                for my $setting_key (@setting_str_keys) {
                    my $value = $iRequest->param($setting_key);
                    my $len   = length $value;
                    $iTVirtual->set_setting( $setting_key,
                        ( $len and $len <= $max_length )
                        ? $value
                        : $iTVirtual->get_setting($setting_key) );
                }
                for my $setting_key (@setting_bool_keys) {

                    # force skipping BBS_READONLY
                    $setting_key eq 'BBS_READONLY' and next;
                    my $value = $iRequest->param($setting_key);
                    $iTVirtual->set_setting( $setting_key, $value );
                }

                $iTVirtual->save();
                $template_param->{DoneSaveSetting} = 1;
            }
            %$template_param = (
                %$template_param,
                ( map { ( $_ => $iTVirtual->get_setting($_) ) } @all_keys ),
                SettingStringLengthMax => $max_length,
                ModeSetting            => 1,
            );
        }
        elsif ( $page eq 'ngword' ) {
            my $iConfig = $iApp->{__config};
            my $llimit  = $iConfig->get_int('vta.ngword_length_limit') || 50;
            my $climit  = $iConfig->get_int('vta.ngword_count_limit') || 100;
            if (    $iRequest->param('submit')
                and $priv & $iTVirtual->can_edit_ngword() )
            {
                my @ngwords = split "\n", $iRequest->param('ngword');
                if ( @ngwords <= $climit ) {
                    map { $iTVirtual->remove_ngword($_) }
                        @{ $iTVirtual->get_ngwords() };
                    map      { $iTVirtual->set_ngword($_) }
                        grep { length($_) <= $llimit }
                        map  { trim($_) } @ngwords;
                    $iTVirtual->save();
                    $template_param->{DoneSaveNGWord} = 1;
                }
                else {
                    $template_param->{TooManyNGWordError} = 1;
                }
            }
            %$template_param = (
                %$template_param,
                NGWords => join( "\n", @{ $iTVirtual->get_ngwords() } ),
                NGWordLengthLimit => $llimit,
                NGWordCountLimit  => $climit,
                ModeNGword        => 1,
            );
        }
        elsif ( $page eq 'inherit' ) {
            my $iSubject      = $iBBS->get_subject_instance();
            my $inherit_key   = $iRequest->param('inherit_from');
            my $i             = 1;
            my $subject_stack = [];
            if (    $key ne $inherit_key
                and $iSubject->get($inherit_key)->[1] )
            {
                $template_param->{
                    $iTVirtual->inherit_from($inherit_key)
                    ? 'InheritedOK'
                    : 'InheritedNG'
                    }
                    = 1;
            }
            for my $subject_key ( @{ $iSubject->to_array() } ) {
                if ( $key ne $subject_key ) {
                    my $subj = $iSubject->get($subject_key);
                    push @{$subject_stack},
                        {
                        res     => $subj->[1],
                        key     => $subject_key,
                        subject => $subj->[0],
                        count   => $i,
                        index   => $i,
                        };
                }
                $i++;
            }
            %$template_param = (
                %$template_param,
                Threads     => $subject_stack,
                ModeInherit => 1,
            );
        }
        else {
            my $thread = sub {
                my ( $method, $arguments, $template_name, $iBBS, $key ) = @_;
                defined &{'Zeromin::Thread::new'} or require Zeromin::Thread;
                my $zThread = Zeromin::Thread->new( $iBBS, $key );
                $zThread->load();
                $zThread->$method(@$arguments);
                $zThread->save();
                $template_param->{"Done${template_name}"} = 1;
                return;
            };
            my $access = sub {
                my ( $method, $arguments, $template_name, $iBBS, $key ) = @_;
                my $iLog  = $iBBS->get_log_instance($key);
                my $stack = [];
                my $call  = $method . '_denial_remote_host';
                for my $i (@$arguments) {
                    $i = Img0ch::Kernel::intval($i) || next;
                    my $ip = $iLog->get($i)->[1] || next;
                    my $host = gethostbyaddr( $ip, 2 ) || unpack( 'C*', $ip );
                    $host and $iTVirtual->$call($host);
                }
                $iTVirtual->save();
                $template_param->{"Done${template_name}RemoteHost"} = 1;
                return;
            };
            if ( my $method = $iRequest->param('method') ) {
                map { $iSetting->set( $_, $iTVirtual->get_setting($_) ) }
                    @all_keys;
                if (    $method eq 'remove_res'
                    and $priv & $iTVirtual->can_remove_res() )
                {
                    my @resno = $iRequest->param('resno');
                    $thread->(
                        'remove', [ \@resno, { setting => $iSetting } ],
                        'RemoveRes', $iBBS, $key
                    );
                }
                elsif ( $method eq 'remove_file'
                    and $priv & $iTVirtual->can_remove_file() )
                {
                    my @resno = $iRequest->param('resno');
                    $thread->(
                        'remove', [ \@resno, { is_file_only => 1 } ],
                        'RemoveFile', $iBBS, $key
                    );
                }
                elsif ( $method eq 'stop'
                    and $priv & $iTVirtual->can_stop_thread() )
                {
                    $thread->( 'stop', [], 'StopThread', $iBBS, $key );
                }
                elsif ( $method eq 'restart'
                    and $priv & $iTVirtual->can_restart_thread() )
                {
                    $thread->( 'restart', [], 'RestartThread', $iBBS, $key );
                }
                elsif ( $method eq 'deny'
                    and $priv & $iTVirtual->can_control_access() )
                {
                    my @resno = $iRequest->param('resno');
                    $access->( 'set', \@resno, 'Deny', $iBBS, $key );
                }
                elsif ( $method eq 'allow'
                    and $priv & $iTVirtual->can_control_access() )
                {
                    my @resno = $iRequest->param('resno');
                    $access->( 'remove', \@resno, 'Allow', $iBBS, $key );
                }
            }
            defined &{'Img0ch::App::Mock::Read::new'}
                or require Img0ch::App::Mock::Read;
            my $reader = Img0ch::App::Mock::Read->new( $iBBS, $key,
                $iApp->{__option} );
            %$template_param
                = ( %$template_param, %{ $reader->run() }, View => 1, );
        }
    }
    else {
        $template_param->{AuthFailed} = 1;
    }

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

sub render {
    my ( $iApp, $template_param ) = @_;
    my $iBBS      = $iApp->{__bbs};
    my $iRequest  = $iApp->{__request};
    my $key_param = $iApp->{__key};
    my $validate  = $template_param->{Validate};

    my $iMeta     = $iBBS->get_metadata_instance();
    my $iTemplate = $iBBS->get_template_instance(
        {   file    => 'vta',
            request => $iRequest,
            setting => $iApp->{__setting},
        }
    );
    $iTemplate->param(
        {   %$template_param,
            Banner => $iMeta->main_banner(),
            KEY    => $key_param,
            META   => $iMeta->meta(),
            Option => $iApp->{__option},
        }
    );

    return ( 1, $iTemplate, 'text/html', undef, );
}

sub redirect_error {
    my ( $iApp, $reason ) = @_;
    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 $long = $iError->get( $iApp->{__error} || 'INVALID_POST' );

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

sub trim {
    my ($str) = @_;
    $str =~ tr/\n\r\t//d;
    return $str;
}

1;
__END__
