#/*
# *  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: bbs.pm 398 2007-03-21 13:57:43Z hikarin $
#

package Zeromin::App::bbs;

use strict;
use Unicode::Japanese qw();
use Zeromin::BBS qw();

sub load {
    my ($zApp) = @_;
    my $zUser     = $zApp->user() || return { code => 1 };
    my $bbs_range = $zUser->get_current_user->{bbs};
    my $unijp     = Unicode::Japanese->new();
    my $iKernel   = $zApp->kernel();
    my $encoding  = $iKernel->get_encoding(1);

    my $meta = sub {
        my ( $method, $iBBS, $zUser, $unijp, $encoding ) = @_;
        require Zeromin::Metadata;
        my $zMeta = Zeromin::Metadata->new($iBBS);
        $unijp->set( $zMeta->$method, $encoding )->get();
    };
    my $subject = sub {
        my ( $class, $file, $privilege, $iBBS, $zUser, $unijp, $encoding )
            = @_;
        if ($privilege) {
            $zUser->$privilege or return [];
        }
        require $file;
        my $ret      = [];
        my $zSubject = $class->new($iBBS);
        $zSubject->load();
        for my $key ( @{ $zSubject->to_array() } ) {
            my $data = $zSubject->get_subject($key);
            push @$ret,
                {
                count   => $data->[1],
                key     => $key,
                subject => $unijp->set( $data->[0], $encoding )->get(),
                };
        }
        $ret;
    };
    my $subs = {
        'archive' => sub {
            $subject->(
                'Zeromin::Archive', 'Zeromin/Archive.pm',
                'can_enter_archive_section', @_,
            );
        },
        'pool' => sub {
            $subject->(
                'Img0ch::Pool::Subject', 'Img0ch/Pool/Subject.pm',
                'can_enter_pool_section', @_,
            );
        },
        'subject' => sub {
            $subject->( 'Zeromin::Subject', 'Zeromin/Subject.pm', '', @_ );
        },
        'setting' => sub {
            my ( $iBBS, $zUser, $unijp, $encoding ) = @_;
            require Zeromin::Setting;
            my $zSetting = Zeromin::Setting->new($iBBS);
            my $ret      = {};
            $zSetting->load();
            for my $key ( @{ $zSetting->keyset(1) } ) {
                my $priv_meth = $zSetting->get_require_privilege($key);
                $ret->{$key} = {
                    type      => $zSetting->get_type($key),
                    value     => $zSetting->get_normalized( $key, $unijp ),
                    privilege => $zUser->$priv_meth,
                };
            }
            $ret;
        },
        'head'       => sub { $meta->( 'head',       @_ ) },
        'meta'       => sub { $meta->( 'meta',       @_ ) },
        'foot'       => sub { $meta->( 'foot',       @_ ) },
        'thread_end' => sub { $meta->( 'thread_end', @_ ) },
    };

    my $iRequest = $zApp->request();
    if ( my $iBBS = $zApp->bbs() ) {
        ( $bbs_range == 0 or $iBBS->get_id() == $bbs_range )
            or return { code => 1 };
        my $type = $iRequest->param('type');
        exists $subs->{$type}
            and return $subs->{$type}->( $iBBS, $zUser, $unijp, $encoding );
    }

    my $zBBS = Zeromin::BBS->new( $iKernel, { bbs => '' } );
    my $ret
        = $bbs_range ? $zBBS->get( $bbs_range, $unijp ) : $zBBS->all($unijp);
    if ( my $offset = $iRequest->param_int('offset') ) {
        my $item = $iRequest->param_int('item') || 10;
        $ret = $zApp->paging_from_array( $ret, $offset, $item );
    }
    return $ret;
}

sub save {
    my ($zApp) = @_;
    my $zUser     = $zApp->user() || return { code => 1 };
    my $bbs_range = $zUser->get_current_user()->{bbs};
    my $unijp     = Unicode::Japanese->new();
    my $iKernel   = $zApp->kernel();
    my $encoding  = $iKernel->get_encoding(1);

    my $setting = sub {
        my ( $spec_keys, $zApp, $iBBS, $zSetting, $zUser, $unijp, $encoding )
            = @_;
        my $iRequest = $zApp->request();
        my @keys     = @$spec_keys ? @$spec_keys : $iRequest->param('key');
        my $ok       = 0;
        for my $key (@keys) {
            my $priv_meth = $zSetting->get_require_privilege($key);
            if ( !$zUser->$priv_meth ) {
                $zApp->logger( 0,
                    'CANNOT CHANGE SETTING.TXT WITHOUT PRIVILEGE: %s', [$key],
                );
                next;
            }
            my $value = $iRequest->param($key);
            my $old   = $zSetting->get($key);
            $old eq $value and next;
            $zSetting->set( $key,
                $unijp->set( $value, 'utf8' )->$encoding() );
            $zApp->logger(
                1,
                'MODIFIED SETTING.TXT: from %s to %s on %s',
                [ $unijp->set( $old, $encoding )->get(), $value, $key ],
            );
            $ok++;
        }
        $ok and $zSetting->save(0);
        { code => ( $ok ? 0 : 1 ), changed => $ok };
    };
    my $meta = sub {
        my ( $method, $privilege, $zApp, $iBBS, $zSetting, $zUser, $unijp,
            $encoding )
            = @_;
        $zUser->$privilege or return { code => 1 };
        require Zeromin::Metadata;
        my $iRequest = $zApp->request();
        my $zMeta    = Zeromin::Metadata->new($iBBS);
        my $value    = $iRequest->param_multibyte( 'content', $encoding, 1 )
            || '';
        $zMeta->$method( \$value );
        $zApp->logger( 1, 'MODIFIED META DATA: %s', [$method] );
        { code => 0 };
    };
    my $subs = {
        'global' => sub {
            $setting->( [ 'BBS_MODE', 'BBS_SUBTITLE', 'BBS_TITLE' ], @_ );
        },
        'setting' => sub { $setting->( [], @_ ) },
        'head'       => sub { $meta->( 'head', 'can_edit_head', @_ ) },
        'meta'       => sub { $meta->( 'meta', 'can_edit_meta', @_ ) },
        'foot'       => sub { $meta->( 'foot', 'can_edit_foot', @_ ) },
        'thread_end' => sub {
            my ( $zApp, $iBBS, $zSetting, $zUser, $unijp, $encoding ) = @_;
            $zUser->can_edit_head() or return { code => 1 };
            my $iRequest = $zApp->request();
            my $date     = $iRequest->param('date');
            $date =~ /\AOver\s+\d+\s+Thread\z/xms or return { code => 2 };
            my $from = $iRequest->param_multibyte( 'FROM',    $encoding );
            my $mail = $iRequest->param_multibyte( 'mail',    $encoding );
            my $text = $iRequest->param_multibyte( 'MESSAGE', $encoding );
            $text =~ s/\r\n/\n/gxms;
            $text =~ s/\n/<br>/gxms;
            my $content = join '<>', $from, $mail, $date, $text, '';
            require Zeromin::Metadata;
            Zeromin::Metadata->new($iBBS)->thread_end( \$content );
            $zApp->logger( 1, 'MODIFIED META DATA: %s', ['thread_end'] );
            return { code => 0 };
        },
    };

    if ( my $iBBS = $zApp->bbs() ) {
        ( $bbs_range == 0 or $iBBS->get_id() == $bbs_range )
            or return { code => 1 };
        my $type = $zApp->request()->param('type');
        require Zeromin::Setting;
        my $zSetting = Zeromin::Setting->new($iBBS);
        $zSetting->load();
        exists $subs->{$type}
            and return $subs->{$type}
            ->( $zApp, $iBBS, $zSetting, $zUser, $unijp, $encoding );
    }
    return { code => 1 };
}

sub create {
    my ($zApp) = @_;
    my $zUser    = $zApp->user() || return { code => 1 };
    my $iRequest = $zApp->request();
    my $unijp    = Unicode::Japanese->new();
    my $iKernel  = $zApp->kernel();
    my $encoding = $iKernel->get_encoding(1);
    my $error    = {};
    my $param    = {};

    $zUser->can_create_bbs() or return { code => 1 };
    _validate_bbs_create_param( $zApp, $param, $error ) or return $error;

    my $bbs     = $param->{bbs_dir};
    my $name    = $param->{bbs_name};
    my $subname = $param->{bbs_sub_name};

    require Zeromin::BBS;
    my $zBBS = Zeromin::BBS->new( $iKernel, { bbs => $bbs } );
    $zBBS->create(
        {   category   => $param->{bbs_category},
            request    => $iRequest,
            sub_title  => $unijp->set( $subname, 'utf8' )->$encoding,
            title      => $unijp->set( $name, 'utf8' )->$encoding,
            uploadable => $param->{bbs_uploadable},
        }
    ) or return { code => 7 };
    $zApp->logger( 1, 'CREATED BBS: %s', [$bbs] );

    return { code => 0, id => $zBBS->get_id() };
}

sub _remove {
    my ($zApp) = @_;
    my $zUser     = $zApp->user() || return { code => 1 };
    my $bbs_range = $zUser->get_current_user()->{bbs};
    my $error     = {};
    my $param     = {};

    $zUser->can_remove_bbs() or return { code => 1 };
    if ( my $iBBS = $zApp->bbs() ) {
        my $bbs = $iBBS->get_id();
        ( $bbs_range == 0 or $bbs == $bbs_range ) or return { code => 1 };
        require Zeromin::BBS;
        my $zBBS = Zeromin::BBS->new( $zApp->kernel(), { id => $bbs } );
        $zBBS->remove() or return { code => 2 };
        return { code => 0 };
    }

    return { code => 1 };
}

sub change_category {
    my ($zApp) = @_;
    my $zUser    = $zApp->user() || return { code => 1 };
    my $iRequest = $zApp->request();
    my $status   = {};
    $zUser->can_create_bbs() or return { code => 1 };

    my @param    = $iRequest->param('bbs');
    my $category = $iRequest->param('category');
    for my $id (@param) {
        $id = Img0ch::Kernel::intval($id) || next;
        my $zBBS = Zeromin::BBS->new( $zApp->kernel(), { id => $id } );
        if ( $zBBS->change_category($category) ) {
            $zApp->logger(
                1,
                'CHANGED CATEGORY TO %s: %s',
                [ $category, $id ]
            );
            $status->{$id} = $category;
        }
        else {
            $zApp->logger(
                1,
                'TRIED CHANGING INEXIST CATEGORY (%s): %s',
                [ $category, $id ],
            );
            $status->{$id} = 0;
        }
    }
    { code => 0, done => $status };
}

sub update_index {
    _task(
        shift, 'can_update_bbs', 'update',
        'UPDATED INDEX ON BBS',
        'TRIED UPDATING INDEX ON BBS',
    );
}

sub update_subject {
    _task(
        shift, 'can_update_subject', 'update_subject',
        'UPDATED SUBJECT ON BBS',
        'TRIED UPDATING SUBJECT ON BBS',
    );
}

sub recreate_subject {
    _task(
        shift, 'can_recreate_subject', 'recreate_subject',
        'RECREATED SUBJECT ON BBS',
        'TRIED RECREATING SUBJECT ON BBS',
    );
}

sub repair_upload_file_table {
    _task(
        shift,
        'can_create_bbs',
        'repair_upload_file_table',
        'REPAIRED UPLOADED FILE TABLE ON BBS',
        'TRIED REPAIRING UPLOADED FILE TABLE ON BBS',
    );
}

sub stop {
    _task( shift, 'can_remove_bbs', 'stop', 'STOPPED BBS',
        'TRIED STOPPING BBS',
    );
}

sub remove {
    _task( shift, 'can_remove_bbs', 'remove', 'REMOVED BBS',
        'TRIED REMOVING BBS',
    );
}

sub update_bbsmenu {
    _task2( shift, 'can_update_bbslist', 'update_bbsmenu', 'UPDATED BBSMENU',
    );
}

sub repair_bbs_table {
    _task2( shift, 'can_create_bbs', 'repair_bbs_table', 'REPAIRED BBS TABLE',
    );
}

sub _task {
    my ( $zApp, $priv_meth, $task_meth, $success, $fail ) = @_;
    my $zUser = $zApp->user() || return { code => 1 };
    $zUser->$priv_meth or return { code => 1 };
    my $status = {};

    my $iRequest = $zApp->request();
    my @param = $iRequest->param('bbs');
    for my $id (@param) {
        $id = Img0ch::Kernel::intval($id) || next;
        my $zBBS = Zeromin::BBS->new( $zApp->kernel(), { id => $id } );
        if ( $zBBS->$task_meth( $id, $iRequest ) ) {
            $zApp->logger( 1, ( $success . ': %s' ), [$id] );
            $status->{$id} = 1;
        }
        else {
            $zApp->logger( 0, ( $fail . ': %s' ), [$id] );
            $status->{$id} = 0;
        }
    }
    { code => 0, done => $status };
}

sub _task2 {
    my ( $zApp, $priv_meth, $task_meth, $log ) = @_;
    my $zUser = $zApp->user() || return { code => 1 };
    $zUser->$priv_meth or return { code => 1 };

    my $zBBS = Zeromin::BBS->new( $zApp->kernel(), { id => 0 } );
    my $done = $zBBS->$task_meth();
    $zApp->logger( 1, $log, [] );
    { code => 0, done => $done };
}

sub _validate_bbs_create_param {
    my ( $zApp, $param, $error ) = @_;
    my $iRequest = $zApp->request();
    my $category = $iRequest->param_int('category') || 1;
    my $dir      = $iRequest->param('dir');
    my $name     = $iRequest->param('BBS_TITLE');
    my $subname  = $iRequest->param('BBS_SUBTITLE');

    if ( $dir =~ /\A\s*\z/xms ) {
        $zApp->logger( 0, 'NO BBS DIRECTORY WAS SET' );
        %{$error} = ( code => 2 );
        return 0;
    }
    elsif ( $name =~ /\A\s*\z/xms ) {
        $zApp->logger( 0, 'NO BBS NAME WAS SET' );
        %{$error} = ( code => 3 );
        return 0;
    }
    elsif ( $subname =~ /\A\s*\z/xms ) {
        $zApp->logger( 0, 'NO BBS SUB NAME WAS SET' );
        %{$error} = ( code => 4 );
        return 0;
    }
    elsif ( $dir !~ /\A[\w\-\.]+\z/xms ) {
        $zApp->logger( 0, 'INVALID BBS DIRECTORY WAS SET' );
        %{$error} = ( code => 5 );
        return 0;
    }
    elsif ($dir eq 'test'
        or $dir eq '_system'
        or $dir eq '_template'
        or $dir eq 'static' )
    {
        $zApp->logger( 0, 'RESERVED WORD (BBS DIRECTORY) WAS SET' );
        %{$error} = ( code => 6 );
        return 0;
    }

    $param->{bbs_category}   = $category;
    $param->{bbs_dir}        = $dir;
    $param->{bbs_name}       = $name;
    $param->{bbs_sub_name}   = $subname;
    $param->{bbs_uploadable} = $iRequest->param('BBS_MODE') eq 'picture';
    1;
}

1;
__END__
