#/*
# *  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: Archive.pm 1826 2009-04-08 15:23:25Z hikarin $
#

package Zeromin::Archive;

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

sub new {
    my ( $zClass, $zBBS ) = @_;
    my $iBBS = $zBBS->can('parent') ? $zBBS->parent() : $zBBS;
    my $zArchive = $zClass->SUPER::new($iBBS);

    $zArchive->{__bbs}   = $iBBS;
    $zArchive->{__key}   = 0;
    $zArchive->{__cache} = undef;
    $zArchive;
}

sub set_key { $_[0]->{__key} = $_[1]; return }

sub create {
    my ( $zArchive, $key ) = @_;
    my $iBBS = $zArchive->{__bbs};

    require Img0ch::Pool::Thread;
    $key ||= $zArchive->{__key};
    my $iPool = Img0ch::Pool::Thread->new( $iBBS, $key );
    $iPool->load();
    my $res     = $iPool->count() || return 0;
    my $iKernel = $zArchive->{__bbs}->get_kernel();
    my $iConfig = $iKernel->get_config();

    if ( !$iConfig->get('DynamicArchive') ) {
        my $subject = $iPool->subject();
        my $path    = $zArchive->path($key);
        my $html    = join '/', $path, $key . '.html';
        my $dat     = join '/', $path, $key . '.dat';

        if ( !-d $path ) {
            defined $File::Path::VERSION or require File::Path;
            File::Path::mkpath($path) or $iKernel->throw_io_exception($path);
        }

        my $fh    = $iKernel->get_write_file_handle($dat);
        my $count = $iPool->count();
        my $stack = [];
        for ( my $i = 1; $i <= $count; $i++ ) {
            my $res     = $iPool->get($i);
            my $first   = $i == 1 ? 1 : 0;
            my $name    = $res->[0];
            my $mail    = $res->[1];
            my $id      = $res->[3];
            my $date    = $id ? ( join ' ', $res->[2], $id ) : $res->[2];
            my $comment = $res->[4];
            print ${fh} $name, '<>', $mail, '<>', $date, '<>', $comment, '<>',
                ( $first ? $subject : '' ), "\n"
                or $iKernel->throw_io_exception($dat);
            $comment =~ s{
                (ftp|https?)://
                ([A-Za-z0-9\-\.]+)/?
                ([-_.!~*'()a-zA-Z0-9;/?:\@\&=+\$,%#]*)/?
                }{<a href="$1://$2/$3">$1://$2/$3</a>}gxms;
            $comment =~ s{&gt;&gt;(\d+)(-)?(\d+)?}
                {<a href="#r$1">>>$1$2$3</a>}gxms;
            $comment =~ s{>>([\d\+\-,]+)}{&gt;&gt;$1}gxms;
            push @$stack,
                {
                isFirst => $first,
                index   => $i,
                resno   => $i,
                name    => $name,
                mail    => $mail,
                date    => $date,
                text    => $comment,
                };
        }
        close $fh or $iKernel->throw_io_exception($dat);

        my $iSetting  = $iBBS->get_setting_instance();
        my $iTemplate = $iBBS->get_template_instance(
            {   file    => 'archive',
                setting => $iSetting,
            }
        );
        $iTemplate->param(
            {   Subject => $subject,
                ZThread => $stack,
                Thread  => $stack,
                Date    => _format_date( time() ),
            }
        );
        $iTemplate->save($html);
        $zArchive->set( $key, $res, $subject );
        $zArchive->save();
        $zArchive->_plugin( 'zeromin.archive.thread', $key );
    }

    return 1;
}

sub remove {
    my ( $zArchive, $key ) = @_;
    my $iRepos = $zArchive->{_repos};
    my $bbs    = $zArchive->{_bbs};

    $key ||= $zArchive->{__key};
    my $res = $iRepos->remove("I:A.${bbs}.${key}.res");

    if ($res) {
        my $iKernel = $zArchive->{_kernel};
        my $path    = $zArchive->path($key);
        my $html    = join '/', $path, $key . '.html';
        my $dat     = join '/', $path, $key . '.dat';
        if ( -w $dat ) {
            unlink $dat or $iKernel->throw_io_exception($dat);
        }
        if ( -w $html ) {
            unlink $html or $iKernel->throw_io_exception($html);
        }
        $iRepos->remove("I:A.${bbs}.${key}.subject");
        $zArchive->save();
        $zArchive->{_count}--;
        $zArchive->_plugin( 'zeromin.remove.archive', $key );
        return 1;
    }
    return 0;
}

sub update {
    my ($zArchive) = @_;
    my $iBBS       = $zArchive->{__bbs};
    my $iKernel    = $zArchive->{_kernel};
    my $path       = $iBBS->path('kako');

    -d $path or return 0;
    opendir my $fh, $path or $iKernel->throw_io_exception($path);
    my @file = readdir $fh;
    closedir $fh or $iKernel->throw_io_exception($path);

    my $iSetting = $iBBS->get_setting_instance();
    my $banner   = $iBBS->get_metadata_instance()->main_banner();
    my $title    = $iSetting->get('BBS_TITLE');
    my $found    = [];
    my $tmplargv = { file => 'archive_index', setting => $iSetting };
    for my $token (@file) {
        next if $token !~ /\A\d+\z/xms;
        my $_path = join '/', $path, $token;
        -d $_path or next;
        my $iTemplate = $iBBS->get_template_instance($tmplargv);
        $zArchive->_update( $iTemplate, $token, $path, $title, \$banner );
        push @$found, { token => $token };
    }

    my $iTemplate = $iBBS->get_template_instance($tmplargv);
    $iTemplate->param(
        {   Banner    => $banner,
            BBS_TITLE => $title,
            IsToken1  => 1,
            Tokens    => $found,
        }
    );
    $iTemplate->save("${path}/index.html");
    return 1;
}

sub path {
    my ( $zArchive, $key ) = @_;
    my $path = $zArchive->{__bbs}->path('kako');

    return $key
        ? join( '/', $path, ( substr $key, 0, 4 ), ( substr $key, 0, 5 ) )
        : $path;
}

sub to_array_with_page {
    my ( $zArchive, $item_per_page, $offset ) = @_;

    defined $Data::Page::VERSION or require Data::Page;
    my $entries = $zArchive->to_array();
    my $page = Data::Page->new( scalar @$entries, $item_per_page, $offset );
    return ( [ $page->splice($entries) ], $page );
}

sub _update {
    my ( $zArchive, $iTemplate, $token1, $dir, $title, $banner ) = @_;
    my $cache = $zArchive->{__cache} ||= $zArchive->to_array();
    my $found = [];
    my ( $min, $max ) = ( 0, 0 );

    for my $key ( @{$cache} ) {
        if ( $key =~ /\A\d{9}\z/xms ) {
            $min = sprintf '%04d00000', $token1;
            $max = sprintf '%04d00000', $token1 + 1;
        }
        else {
            $min = sprintf '%04d000000', $token1;
            $max = sprintf '%04d000000', $token1 + 1;
        }
        if ( $key > $min and $key <= $max ) {
            push @$found, $key;
        }
    }

    my $stack = [];
    for my $key ( @{$found} ) {
        my ( $subject, $res ) = @{ $zArchive->get($key) };
        push @$stack,
            {
            token2  => substr( $key, 0, 5 ),
            key     => $key,
            date    => _format_date($key),
            subject => $subject,
            };
    }

    $iTemplate->param(
        {   Archives  => $stack,
            Banner    => $$banner,
            BBS_TITLE => $title,
            token1    => $token1,
            IsToken2  => 1,
        }
    );
    $iTemplate->save("${dir}/${token1}/index.html");
    return 1;
}

sub _format_date {
    my ($time) = @_;
    my @d = localtime($time);
    $d[5] += 1900;
    $d[4] += 1;
    return sprintf '%02d/%02d/%02d %02d:%02d:%02d', $d[5], $d[4], $d[3],
        $d[2], $d[1], $d[0];
}

sub _plugin {
    my ( $zArchive, $at, $key ) = @_;
    require Zeromin::Plugin;
    my $iBBS    = $zArchive->{__bbs};
    my $zPlugin = Zeromin::Plugin->new($iBBS);
    $zPlugin->do( $at, $zArchive, [ $iBBS, $key ] );
    return;
}

1;
__END__

=head1 NAME

Zeromin::Archive - 過去ログの具体的な操作を行うクラス

=head1 SYNOPSYS

  use Zeromin::Archive

  my $zArchive = Zeromin::Archive->new($iBBS);
  $zArchive->load();
  for my $key ( @{ $zArchive->to_array() } ) {
      $zArchive->create($key);
  }

  $zArchive->update();
  $zArchive->save();

=head1 DESCRIPTION

L<Img0ch::Archive>を継承してさらに具体的な操作を行うクラスです。

=head2 new

=over 4

=item Arguments

$iBBS (Img0ch::BBS)

=item Return Value

$zArchive (Zeromin::Archive itself)

=back

I<Zeromin::Archive>のオブジェクトを作成します。

=head2 set_key

=over 4

=item Arguments

$thread_key

=item Return Value

none

=back

オブジェクト内部に保存されているスレッドキーを変更します。
別のスレッドの内容を読み込む場合に利用します。

=head2 create

=over 4

=item Arguments

$thread_key

=item Return Value

1 or 0

=back

プールスレッドを過去ログに変換します。
レス数が0のプールスレッドの変換を試みた場合は0を返します。
動的な過去ログ作成(DynamicArchive)が有効の場合は何もせず1を返します。
それ以外にI/Oエラーが発生した場合は例外を送出します。

また、I<zeromin.archive.thread>に対応するプラグインは
このメソッドで呼び出され、実行されます。引数としてスレッドキーが渡されます。

=head2 remove

=over 4

=item Arguments

$thread_key

=item Return Value

1 or 0

=back

スレッドキーに対応する過去ログを削除します。
レス数が0の過去ログの削除を試みた場合は0を返します。
それ以外にI/Oエラーが発生した場合は例外を送出します。

また、I<zeromin.remove.archive>に対応するプラグインは
このメソッドで呼び出され、実行されます。引数としてスレッドキーが渡されます。

=head2 path

=over 4

=item Arguments

$thread_key?

=item Return Value

$path_to_archived_thread

=back

スレッドキーに対応する過去ログのパスを作成し、それを返します。
引数なしで呼び出すと過去ログのディレクトリまでのパスを返します。

=head2 to_array_with_page

=over 4

=item Arguments

$item_per_page, $offset

=item Return Value

$reference_to_all_thread_keys, $Data_Page_object

=back

I<to_array()>に似ていますが、ページオブジェクトL<Data::Page>が
追加で返します。

=head1 AUTHOR

hkrn E<lt>hikarin@users.sourceforge.jpE<gt>

=cut
