#/*
# *  Copyright 2007-2009 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 1840 2009-06-17 14:03:48Z 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 $base = $iKernel->get_config()->get('BBSPath');
    my $dir  = $hash->{bbs} || $zBBS->{_dir};
    $zBBS->{_base} = $base;
    $zBBS->{_dir}  = $dir;
    $zBBS->{_path} = join '/', $base, $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();
    my $utf8 = $hash->{utf8} ? 1 : 0;
    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.d.${bbs}.utf8", $utf8 );
        $iRepos->set( "I:B.i.${id}.utf8",  $utf8 );
        $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);
    {
        my $fh;
        my $head = join '/', $init, 'default_hed.txt';

        $fh = $iKernel->get_read_file_handle($head);
        $zMeta->head(
            \$iKernel->get_encoded_str(
                do { local $/ = undef; <$fh> },
                'utf8', 'sjis'
            )
        );
        close $fh or $iKernel->throw_io_exception($head);

        my $meta = join '/', $init, 'default_fot.txt';
        $fh = $iKernel->get_read_file_handle($meta);
        $zMeta->foot(
            \$iKernel->get_encoded_str(
                do { local $/ = undef; <$fh> },
                'utf8', 'sjis'
            )
        );
        close $fh or $iKernel->throw_io_exception($meta);

        my $t_end = join '/', $static, '1000.txt';
        $fh = $iKernel->get_read_file_handle($t_end);
        $zMeta->thread_end(
            \$iKernel->get_encoded_str(
                do { local $/ = undef; <$fh> },
                'utf8', 'sjis'
            )
        );
        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" );

    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' );
    $utf8 and $zSetting->set( 'BBS_TEMPLATE', 'default_utf8' );
    $zSetting->save();

    my $iSubject = $zBBS->get_subject_instance();
    $iSubject->save("${path}/subject.txt");

    $iRepos->load();
    $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 );
    $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 );

    $zCap->remove_from_bbs();
    $zPlugin->remove_from_bbs();
    $zUser->remove_from_bbs();
    $zSetting->remove();

    for my $object ( $zFIP, $zFNG ) {
        map { $object->remove( $_->{id} ) } @{ $object->all_in() };
        $object->save();
    }

    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();

    defined $File::Basename::VERSION or require File::Basename;
    my $repos  = File::Basename::dirname( $zBBS->get_repos_path('a') );
    my @remove = ($repos);
    !$only_db_data and push @remove, $path;

    defined $File::Path::VERSION or require File::Path;
    File::Path::rmtree( \@remove );

    return 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 Zeromin::Category;
    my $iKernel   = $zBBS->get_kernel();
    my $zCategory = Zeromin::Category->new($iKernel);
    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   => $iKernel->get_encoded_str(
                $zCategory->get($cat_id)->{name},
                $encoding, 'utf8'
            ),
            };
    }

    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 = [];

    #    for my $id ( @{ $zBBS->get_all_ids() } ) {
    #        my $zBBSIter = Zeromin::BBS->new( $iKernel, { id => $id } );
    #        ( $zBBSIter->get_id() and !-d $zBBSIter->path() )
    #            and $zBBSIter->remove(1);
    #    }

    opendir my $fh, $base or $iKernel->throw_io_exception($base);
    my @dirs = readdir $fh;
    closedir $fh;

    require Zeromin::Metadata;
    require Zeromin::Setting;

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

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

        my $iBBSNew = Zeromin::BBS->new( $iKernel, { bbs => $dir } );
        my $fh      = $iKernel->get_read_file_handle($setting);
        my $from    = $iBBSNew->get_encoding();

    SETTING:
        while ( my $line = <$fh> ) {
            chomp $line;
            $line =~ /\A(\w+)=(.+)\z/xms or next SETTING;
            my ( $key, $value ) = ( $1, $2 );
            $setting_hash->{$key}
                = $iKernel->get_encoded_str( $value, 'utf8', $from );
        }
        close $fh or $iKernel->throw_io_exception($setting);

        my $id = $iBBSNew->get_id();
        if ( $iRepos->get("I:B.i.${id}") ne $dir ) {
            $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();
            $iBBSNew->get_common_repos()->load();
        }

        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;
            my $head = join '/', $path, 'head.txt';
            if ( -r $head ) {
                $fh = $iKernel->get_read_file_handle($head);
                my $buffer
                    = $iKernel->get_encoded_str( <$fh>, 'utf8', $from );
                close $fh or $iKernel->throw_io_exception($head);
                $zMeta->head( \$buffer );
            }
            my $meta = join '/', $path, 'meta.txt';
            if ( -r $meta ) {
                $fh = $iKernel->get_read_file_handle($meta);
                my $buffer
                    = $iKernel->get_encoded_str( <$fh>, 'utf8', $from );
                close $fh or $iKernel->throw_io_exception($meta);
                $zMeta->meta( \$buffer );
            }
            my $foot = join '/', $path, 'foot.txt';
            if ( -r $foot ) {
                $fh = $iKernel->get_read_file_handle($foot);
                my $buffer
                    = $iKernel->get_encoded_str( <$fh>, 'utf8', $from );
                close $fh or $iKernel->throw_io_exception($foot);
                $zMeta->foot( \$buffer );
            }
        }

        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 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      = $iBBS->get_setting_instance()->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 and -d join( '/', $zBBS->{_base}, $dir ) ) {
        my $iBBS     = Img0ch::BBS->new( $zBBS->get_kernel(), { id => $id } );
        my $iSetting = $iBBS->get_setting_instance();
        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) {
            my $charset = $iBBS->get_encoding();
            $name    = $unijp->set( $name,    $charset )->get();
            $subname = $unijp->set( $subname, $charset )->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;
    @$result = grep { !$_->{id} } @$result;
    return $result;
}

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

    defined $Data::Page::VERSION or 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 $ret = [];

    for my $id ( @{ $zBBS->get_all_ids() } ) {
        my $data = $zBBS->get( $id, $unijp, $zCategory );
        $data->{id} or next;
        push @$ret, $data;
    }

    return $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 get_all_ids {
    my ($zBBS)   = @_;
    my $id_table = {};
    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
    );

    return [ keys %$id_table ];
}

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__
