#/*
# *  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 970 2007-08-21 14:52:15Z hikarin $
#

package Zeromin::BBS;

use strict;
use base qw(Img0ch::BBS);

sub new {
    my ( $zClass, $iKernel, $hash ) = @_;
    my $zBBS = $zClass->SUPER::new( $iKernel, $hash );
    my $dir = $hash->{bbs} || $zBBS->{_dir};
    $zBBS->{_dir} = $dir;
    $zBBS->{_path} = join '/', $iKernel->get_config()->get('BBSPath'), $dir;
    $zBBS;
}

sub create {
    my ( $zBBS, $hash ) = @_;
    my $bbs = $zBBS->get_name() || return 0;

    if (   $bbs !~ /\A[\w_]+\z/xms
        or $bbs =~ /\A\d+\z/xms
        or $bbs eq 'test' )
    {
        return 0;
    }

    my $path = $zBBS->path();
    return 0 if -d $path;

    my $iKernel = $zBBS->get_kernel();
    my $iConfig = $iKernel->get_config();
    my $iRepos  = $zBBS->get_common_repos();
    my $static  = $iConfig->get('SystemPath');
    my $rebuild = exists $hash->{__rebuild} ? 1 : 0;

    my $id = $zBBS->get_id();
    if ( !$rebuild ) {
        $id = $iRepos->get_int('I:B._') + 1;
        $iRepos->set( "I:B.c.${id}", ( $hash->{category} || 1 ) );
        $iRepos->set( "I:B.d.${bbs}", $id );
        $iRepos->set( "I:B.i.${id}",  $bbs );
        $iRepos->set( 'I:B._',        $id );
        $zBBS->{_id}     = $id;
        $zBBS->{_system} = $iKernel->get_repos(
            $iKernel->get_repos_path("${bbs}/system") ),;
    }

    my $repos = join '/', $iConfig->get('RepositoryRoot'), $bbs;
    if ( !-d $repos ) {
        mkdir $repos, 0755 or $iKernel->throw_io_exception($repos);
    }

    mkdir $path, 0755 or $iKernel->throw_io_exception($path);
    if ( !-d "${path}/i" ) {
        mkdir "${path}/i", 0755
            or $iKernel->throw_io_exception("${path}/i");
    }
    if ( !-d "${path}/dat" ) {
        mkdir "${path}/dat", 0755
            or $iKernel->throw_io_exception("$path/dat");
    }
    if ( !-d "${path}/log" ) {
        mkdir "${path}/log", 0755
            or $iKernel->throw_io_exception("${path}/log");
    }
    if ( !-d "${path}/img" ) {
        mkdir "${path}/img", 0755
            or $iKernel->throw_io_exception("${path}/img");
    }
    if ( !-d "${path}/kako" ) {
        mkdir "${path}/kako", 0755
            or $iKernel->throw_io_exception("${path}/kako");
    }
    if ( !-d "${path}/pool" ) {
        mkdir "${path}/pool", 0755
            or $iKernel->throw_io_exception("${path}/pool");
    }

    require File::Copy;
    my $init = "${static}/init";

    #    if ( !-r "${path}/kanban.gif" ) {
    #        File::Copy::copy( "${init}/default_img.gif",
    #            "${path}/kanban.gif" )
    #            or $iKernel->throw_io_exception("${init}/default_img.gif");
    #    }
    if ( !-r "${path}/ba.gif" ) {
        File::Copy::copy( "${init}/default_bac.gif", "${path}/ba.gif" )
            or $iKernel->throw_io_exception("${init}/default_bac.gif");
    }

    require Zeromin::Metadata;
    my $zMeta = Zeromin::Metadata->new($zBBS);
    {
        local ( $!, *FH );

        my $head = join '/', $init, 'default_hed.txt';
        open *FH, "<${head}"    ## no critic
            or $iKernel->throw_io_exception($head);
        $zMeta->head(
            \do { local $/ = undef; <FH> }
        );
        close *FH or $iKernel->throw_io_exception($head);

        my $meta = join '/', $init, 'default_fot.txt';
        open *FH, "<${meta}"    ## no critic
            or $iKernel->throw_io_exception($meta);
        $zMeta->foot(
            \do { local $/ = undef; <FH> }
        );
        close *FH or $iKernel->throw_io_exception($meta);

        my $t_end = join '/', $static, '1000.txt';
        open *FH, "<${t_end}"    ## no critic
            or $iKernel->throw_io_exception($t_end);
        $zMeta->thread_end(
            \do { local $/ = undef; <FH> }
        );
        close *FH or $iKernel->throw_io_exception($t_end);
    }

    require Zeromin::Setting;
    my $zSetting = Zeromin::Setting->new($zBBS);
    for my $key ( @{ $zSetting->keyset(1) } ) {
        $zSetting->set( $key, $zSetting->get_default($key) );
    }

    $zSetting->set( 'BBS_BG_PICTURE',      "../${bbs}/ba.gif" );
    $zSetting->set( 'BBS_TITLE_PICTURE',   "../${bbs}/kanban.gif" );
    $zSetting->set( 'BBS_REFERER_CUSHION', 'ime.nu/' );

    if ( my $inherit = $hash->{inherit} ) {
        my $iBBSInherit = Img0ch::BBS->new( $iKernel, { id => $inherit } );
        $iBBSInherit->get_id()
            or $iBBSInherit
            = Img0ch::BBS->new( $iKernel, { bbs => $inherit } );
        $zSetting->merge( Zeromin::Setting->new($iBBSInherit) );
    }

    $zSetting->set( 'BBS_TITLE',    $hash->{title} );
    $zSetting->set( 'BBS_SUBTITLE', $hash->{sub_title} );
    $zSetting->set( 'BBS_READONLY', 'on' );
    $zSetting->set( 'BBS_MODE', $hash->{uploadable} ? 'picture' : 'none' );
    $zSetting->save();

    require Img0ch::Subject;
    my $iSubject = Img0ch::Subject->new($zBBS);
    $iSubject->save("${path}/subject.txt");
    $iRepos->save();

    my $iRequest = $hash->{request};
    $iRequest and $zBBS->update( undef, $iRequest );
    if ( !$rebuild ) {
        require Zeromin::Plugin;
        my $iBBS    = $zBBS->parent();
        my $zPlugin = Zeromin::Plugin->new($iBBS);
        $zPlugin->do( 'zeromin.create.bbs', $iBBS );
    }

    1;
}

sub remove {
    my ( $zBBS, $only_db_data ) = @_;
    my $path = $zBBS->path();
    ( !$only_db_data and !-d $path ) and return 0;

    require Zeromin::Cap;
    require Zeromin::Filter::IP;
    require Zeromin::Filter::NGWord;
    require Zeromin::Metadata;
    require Zeromin::Plugin;
    require Zeromin::Setting;
    require Zeromin::User;
    my $iBBS     = $zBBS->parent();
    my $iKernel  = $zBBS->get_kernel();
    my $iRepos   = $zBBS->get_common_repos();
    my $zCap     = Zeromin::Cap->new($iBBS);
    my $zFIP     = Zeromin::Filter::IP->new($iBBS);
    my $zFNG     = Zeromin::Filter::NGWord->new($iBBS);
    my $zMeta    = Zeromin::Metadata->new($iBBS);
    my $zPlugin  = Zeromin::Plugin->new($iBBS);
    my $zSetting = Zeromin::Setting->new($iBBS);
    my $zUser    = Zeromin::User->new($iBBS);
    $zPlugin->do( 'zeromin.remove.bbs', $iBBS );

    my $bbs = $zBBS->get_name();
    my $id  = $zBBS->get_id();
    $iRepos->remove("I:B.c.${id}");
    $iRepos->remove("I:B.d.${bbs}");
    $iRepos->remove("I:B.i.${id}");
    $iRepos->save();

    -d $path or return 1;
    $zCap->remove_from_bbs();
    $zPlugin->remove_from_bbs();
    $zUser->remove_from_bbs();
    $zMeta->remove();
    $zSetting->remove();
    for my $object ( $zFIP, $zFNG ) {
        map { $object->remove( $_->{id} ) } @{ $object->all_in() };
        $object->save();
    }

    require File::Basename;
    my $repos_path = File::Basename::dirname( $zBBS->get_repos_path('a') );
    require File::Find;
    File::Find::find(
        {   bydepth  => 1,
            no_chdir => 1,
            untaint  => 1,
            wanted   => sub {
                if ( !-l and -d _ ) {
                    rmdir $File::Find::name
                        or $iKernel->throw_io_exception($File::Find::name);
                }
                else {
                    unlink $File::Find::name
                        or $iKernel->throw_io_exception($File::Find::name);
                }
                }
        },
        $path,
        $repos_path,
    );
    1;
}

sub rebuild {
    my ( $zBBS, $option ) = @_;
    my $ret = $zBBS->create( { %{ $option || {} }, __rebuild => 1 } );

    require Zeromin::Plugin;
    my $iBBS    = $zBBS->parent();
    my $zPlugin = Zeromin::Plugin->new($iBBS);
    $zPlugin->do( 'zeromin.rebuild.bbs', $iBBS );

    return $ret;
}

sub update {
    my ( $zBBS, $id, $iRequest, $iKernel ) = @_;
    my $iBBS;
    if ( ( ref $zBBS || '' ) eq __PACKAGE__ ) {
        -d $zBBS->path() or return 0;
        $iBBS    = $zBBS->parent();
        $iKernel = $zBBS->get_kernel();
    }
    else {
        $iBBS = Img0ch::BBS->new( $iKernel, { id => $id } );
        -d $iBBS->path() or return 0;
    }

    require Img0ch::App::BBS;
    my $iApp = Img0ch::App::BBS->new( $iKernel, $iRequest->request() );
    $iApp->request()->init( $iKernel->get_config() );
    $iApp->trigger_plugin( 'bbs.init', 'DisableBBSInitPlugin', $iBBS );
    $iApp->update_subback($iBBS);
    $iApp->update_index($iBBS);

    require Zeromin::Plugin;
    my $zPlugin = Zeromin::Plugin->new($iBBS);
    $zPlugin->do( 'zeromin.update.bbs', $iApp );

    1;
}

sub update_subject { shift->_update_subject( 0, 'zeromin.update.subject' ) }

sub update_bbsmenu {
    my ($zBBS) = @_;

    my $category = {};
    for my $bbs ( @{ $zBBS->all() } ) {
        my $cat_id = $bbs->{category};
        $category->{$cat_id} ||= [];
        push @{ $category->{$cat_id} },
            {
            id      => $bbs->{id},
            bbs     => $bbs->{name},
            subname => $bbs->{subname},
            dir     => $bbs->{dir},
            };
    }

    require Unicode::Japanese;
    require Zeromin::Category;
    my $iKernel   = $zBBS->get_kernel();
    my $zCategory = Zeromin::Category->new($iKernel);
    my $unijp     = Unicode::Japanese->new();
    my $encoding  = $iKernel->get_encoding(1);
    my $tagset    = [];
    for my $cat_id ( sort keys %$category ) {
        my $boards = $category->{$cat_id};
        push @{$tagset},
            {
            boards => $category->{$cat_id},
            id     => $cat_id,
            name   => $unijp->set( $zCategory->get($cat_id)->{name}, 'utf8' )
                ->$encoding,
            };
    }

    require Img0ch::Template;
    my @d     = localtime( time() );
    my $date  = join '/', ( $d[5] + 1900 ), ( $d[4] + 1 ), $d[3];
    my $param = { Category => $tagset, LastModified => $date };
    my $base  = $iKernel->get_config()->get('BBSPath');
    my $iTemplate;
    $iTemplate = Img0ch::Template->new( $iKernel,
        { dir => 'zeromin', file => 'bbsmenu' } );
    $iTemplate->param($param);
    $iTemplate->save("${base}/bbsmenu.html");
    $iTemplate = Img0ch::Template->new( $iKernel,
        { dir => 'zeromin', file => 'bbsmenu2' } );
    $iTemplate->param($param);
    $iTemplate->save("${base}/bbsmenu2.html");
    return 1;
}

sub recreate_subject {
    shift->_update_subject( 1, 'zeromin.recreate.subject' );
}

sub repair_bbs_table {
    my ($zBBS)   = @_;
    my $iKernel  = $zBBS->get_kernel();
    my $iConfig  = $iKernel->get_config();
    my $iRepos   = $zBBS->get_common_repos();
    my $base     = $iConfig->get('BBSPath');
    my $rps_root = $iConfig->get('RepositoryRoot');
    my $last_id  = $iRepos->get('I:B._');
    my $repaired = [];

    local ( $!, *DH );
    opendir *DH, $base or $iKernel->throw_io_exception($base);
    my @dirs = readdir *DH;
    closedir *DH;

    require Zeromin::Metadata;
    require Zeromin::Setting;
    require Img0ch::Subject;

    for my $dir (@dirs) {
        $dir =~ /\A\.+\z/xms and next;
        $dir =~ /\A\W+\z/xms and next;
        $dir eq 'test' and next;

        my $path = join '/', $base, $dir;
        -d $path or next;
        my $setting = join '/', $path, 'SETTING.TXT';
        -r $setting or next;
        my $setting_hash = {};

        local ( $!, *FH );
        open *FH, "<${setting}"    ## no critic
            or $iKernel->throw_io_exception($setting);
        while ( my $line = <FH> ) {
            chomp $line;
            $line =~ /\A(\w+)=(.+)\z/xms or next;
            my ( $key, $value ) = ( $1, $2 );
            $setting_hash->{$key} = $value;
        }
        close *FH or $iKernel->throw_io_exception($setting);

        my $iBBSNew = Zeromin::BBS->new( $iKernel, { bbs => $dir } );
        my $id = $iBBSNew->get_id();
        if ( !$id ) {
            $iRepos->set( "I:B.d.${dir}",     ++$last_id );
            $iRepos->set( "I:B.i.${last_id}", $dir );
            $id              = $last_id;
            $iBBSNew->{_dir} = $dir;
            $iBBSNew->{_id}  = $id;
            $iRepos->save();
        }

        my $repos = join '/', $rps_root, $dir;
        if ( !-d $repos ) {
            mkdir $repos, 0755 or $iKernel->throw_io_exception($repos);
        }

        my $zMeta = Zeromin::Metadata->new($iBBSNew);
        {
            local $/ = undef;
            local ( $!, *FH );
            my $head = join '/', $path, 'head.txt';
            if ( -r $head ) {
                open *FH, "<${head}"    ## no critic
                    or $iKernel->throw_io_exception($head);
                $zMeta->head( \<FH> );
                close *FH or $iKernel->throw_io_exception($head);
            }
            my $meta = join '/', $path, 'meta.txt';
            if ( -r $meta ) {
                open *FH, "<${meta}"    ## no critic
                    or $iKernel->throw_io_exception($meta);
                $zMeta->meta( \<FH> );
                close *FH or $iKernel->throw_io_exception($meta);
            }
            my $foot = join '/', $path, 'foot.txt';
            if ( -r $foot ) {
                open *FH, "<${foot}"    ## no critic
                    or $iKernel->throw_io_exception($foot);
                $zMeta->foot( \<FH> );
                close *FH or $iKernel->throw_io_exception($foot);
            }
        }

        my $zSetting = Zeromin::Setting->new($iBBSNew);
        for my $key ( @{ $zSetting->keyset() } ) {
            $zSetting->set( $key,
                $setting_hash->{$key} || $zSetting->get_default($key) );
        }
        $zSetting->save();
        push @$repaired, $dir;
    }

    $repaired;
}

sub repair_tag_table {
    my ($zBBS) = @_;

    require Img0ch::Setting;
    require Zeromin::Subject;
    require Zeromin::Thread;
    require Zeromin::Upload;
    my $iBBS     = $zBBS->parent();
    my $zSubject = Zeromin::Subject->new($zBBS);
    my $zThread  = Zeromin::Thread->new( $zBBS, 0 );
    my $zUpload  = Zeromin::Upload->new( $zBBS, 0 );
    my $max      = Img0ch::Setting->new($iBBS)->get_int('BBS_IMG_TAG_MAX');

    $zSubject->load();
    $zThread->disable_xhtml_break();
    $zUpload->remove_all_tag_data();
BBS:
    for my $key ( @{ $zSubject->to_array() } ) {
        $zThread->set_key($key);
        $zUpload->set_key($key);
        $zUpload->count() or next BBS;
        my $count = $zThread->count();
    THREAD:
        for ( my $i = 1; $i < $count; $i++ ) {
            $zUpload->get($i)->[0] or next THREAD;
            my $comment = $zThread->get($i)->[4];
            $zUpload->set_tags( \$comment, $max, $i, 1 );
        }
    }
    $zUpload->commit_tag_data();

    return 1;
}

sub repair_upload_file_table {
    my ($zBBS) = @_;
    my $repaired = {};

    require Zeromin::Plugin;
    require Zeromin::Subject;
    require Zeromin::Thread;
    my $iBBS     = $zBBS->parent();
    my $zPlugin  = Zeromin::Plugin->new($iBBS);
    my $plugin   = $zPlugin->iterator( 'zeromin.repair.upload', sub { } );
    my $zSubject = Zeromin::Subject->new($zBBS);
    my $result   = $zSubject->to_array();
    my $zThread  = Zeromin::Thread->new( $zBBS, 0 );
    for my $key (@$result) {
        $zThread->set_key($key);
        $repaired->{$key} = $zThread->repair_upload_file_table($plugin);
    }

    $repaired;
}

sub change_category {
    my ( $zBBS, $cat_id ) = @_;

    require Zeromin::Category;
    my $zCategory = Zeromin::Category->new( $zBBS->get_kernel() );
    $zCategory->get($cat_id)->{id} == 0 and return 0;

    my $iRepos = $zBBS->get_common_repos();
    my $bbs_id = $zBBS->get_id();
    $iRepos->set( "I:B.c.${bbs_id}", $cat_id );
    $iRepos->save();

    return 1;
}

sub parent {
    my ($zBBS) = @_;
    my $singleton = $zBBS->{__singleton};

    if ( !$singleton ) {
        my $dir = $zBBS->{_dir};
        my $iBBS = Img0ch::BBS->new( $zBBS->get_kernel(), { bbs => $dir } );
        $iBBS->{_dir}        = $dir;
        $iBBS->{_path}       = $zBBS->{_path};
        $zBBS->{__singleton} = $singleton = $iBBS;
    }
    return $singleton;
}

sub category {
    my ( $zBBS, $cat_id ) = @_;
    my $count = 0;

    $zBBS->get_common_repos()->iterate(
        sub {
            my ( $key, $value, $count, $cat_id ) = @_;
            if ( $$value eq $cat_id and $key =~ /\AI:B.c\.\d+\z/xms ) {
                $$count++;
            }
            return 0;
        },
        \$count,
        $cat_id
    );
    return $count;
}

sub get {
    my ( $zBBS, $id, $unijp, $zCategory ) = @_;
    my $iRepos = $zBBS->get_common_repos();
    my $dir    = $iRepos->get("I:B.i.${id}");

    if ($dir) {
        require Img0ch::Setting;
        my $iBBS     = Img0ch::BBS->new( $zBBS->get_kernel(), { id => $id } );
        my $iSetting = Img0ch::Setting->new($iBBS);
        my $name     = $iSetting->get('BBS_TITLE');
        my $subname  = $iSetting->get('BBS_SUBTITLE');
        my $category = $iRepos->get("I:B.c.${id}") || 1;
        my $cname    = $zCategory ? $zCategory->get_name($category) : '';
        if ($unijp) {
            $name    = $unijp->set( $name,    'sjis' )->get();
            $subname = $unijp->set( $subname, 'sjis' )->get();
        }
        {   id       => $id,
            name     => $name,
            subname  => $subname,
            dir      => $dir,
            category => $category,
            cname    => $cname,
        };
    }
    else {
        {   id       => 0,
            name     => '',
            subname  => '',
            dir      => '',
            category => 0,
            cname    => '',
        };
    }
}

sub gets {
    my ( $zBBS, $bbs_ids, $unijp, $zCategory ) = @_;
    my $result = [];
    map { push @$result, $zBBS->get( $_, $unijp, $zCategory ) } @$bbs_ids;
    return $result;
}

sub gets_with_page {
    my ( $zBBS, $bbs_ids, $unijp, $zCategory, $item_per_page, $offset ) = @_;

    require Data::Page;
    my $entries = $zBBS->gets( $bbs_ids, $unijp, $zCategory );
    my $page = Data::Page->new( scalar @$entries, $item_per_page, $offset );
    return ( [ $page->splice($entries) ], $page );
}

sub all {
    my ( $zBBS, $unijp, $zCategory ) = @_;
    my $id_table = {};
    my $ret      = [];
    my $iter     = $zBBS->get_common_repos()->iterate(
        sub {
            my ( $key, $value, $id_table ) = @_;
            if ( $key =~ /\AI:B.d.(?:.+?)\z/xms ) {
                $id_table->{$$value} = 1;
            }
            return 0;
        },
        $id_table
    );

    for my $id ( keys %$id_table ) {
        push @$ret, $zBBS->get( $id, $unijp, $zCategory );
    }
    $ret;
}

sub all_with_page {
    my ( $zBBS, $unijp, $zCategory, $item_per_page, $offset ) = @_;

    require Data::Page;
    my $entries = $zBBS->all( $unijp, $zCategory );
    my $page = Data::Page->new( scalar @$entries, $item_per_page, $offset );
    return ( [ $page->splice($entries) ], $page );
}

sub _update_subject {
    my ( $zBBS, $recreate, $at ) = @_;

    require Zeromin::Subject;
    my $zSubject = Zeromin::Subject->new($zBBS);
    my $info     = {};

    $zSubject->load();
    my $count = $zSubject->update( $info, $recreate );
    $info->{done} = $count;

    require Zeromin::Plugin;
    my $iBBS    = $zBBS->parent();
    my $zPlugin = Zeromin::Plugin->new($iBBS);
    $zPlugin->do( $at, $iBBS, [$info] );

    $info;
}

1;
__END__
