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

package Zeromin::Subject;

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

sub remove {
    my ( $zSubject, $key ) = @_;
    my $order = $zSubject->{_order};

    for ( my $i = 0; $i < @{$order}; $i++ ) {
        if ( $order->[$i] == $key ) {
            splice @$order, $i, 1;
            last;
        }
    }

    if ( delete $zSubject->{_cache}->{$key} ) {
        $zSubject->{_count}--;
        return 1;
    }
    return 0;
}

sub update {
    my ( $zSubject, $info, $recreate ) = @_;
    my $iKernel = $zSubject->{_kernel};
    defined $info or $info = {};
    %$info = ( recovered => [], removed => [] );

    my %done = ();
    my $iBBS = Img0ch::BBS->new( $iKernel, { id => $zSubject->{_bbs} } );
    my $path = $iBBS->path();

    require Zeromin::Thread;
    my $zThread = Zeromin::Thread->new( $iBBS, 0 );
    if ($recreate) {
        $zSubject->make_backup($path);
        opendir my $fh, "$path/dat"
            or $iKernel->throw_io_exception("$path/dat");
        my @dat = readdir $fh;
        closedir $fh
            or $iKernel->throw_io_exception("$path/dat");
        for my $key (@dat) {
            $key =~ /\A(\d{9,10})\.dat\z/xms or next;
            $key = $1;
            $zThread->set_key($key);
            my $res_count = $zThread->load();
            my ( undef, $count ) = @{ $zSubject->get($key) };
            my $subject = $zThread->get_subject();
            if ($count) {
                _count( $zThread, \$res_count );
                if ($res_count) {
                    $zSubject->sage( $key, $res_count, $subject );
                }
                else {
                    $zSubject->remove($key);
                    push @{ $info->{removed} }, $key;
                }
            }
            else {
                _count( $zThread, \$res_count );
                if ($res_count) {
                    $zSubject->age( $key, $res_count, $subject );
                    push @{ $info->{recovered} }, $key;
                }
            }
            $done{$key} = 1;
        }
        for my $key ( @{ $zSubject->to_array() } ) {
            if ( !$done{$key} ) {
                $zSubject->remove($key);
                push @{ $info->{removed} }, $key;
            }
        }
    }
    else {
        my $result = $zSubject->to_array();
        for my $key (@$result) {
            $zThread->set_key($key);
            my $count = $zThread->load();
            _count( $zThread, \$count );
            $done{$key} or $zSubject->sage( $key, $count );
            if ( !$count ) {
                $zSubject->remove($key);
                push @{ $info->{removed} }, $key;
            }
            $done{$key} = 1;
        }
    }

    $zSubject->save("$path/subject.txt");
    return keys %done;
}

sub make_backup {
    my ( $zSubject, $base ) = @_;
    my $iKernel = $zSubject->{_kernel};
    my $iBBS = Img0ch::BBS->new( $iKernel, { id => $zSubject->{_bbs} } );

    defined $File::Basename::VERSION or require File::Basename;
    defined $File::Copy::VERSION     or require File::Copy;
    my @date = localtime( time() );
    my ( $sec, $min, $hour, $day, $month, $year ) = localtime( time() );
    $year  += 1900;
    $month += 1;
    my $path = File::Basename::dirname( $iBBS->get_repos_path(0) )
        . sprintf( '/subject_%04d_%02d_%02d_%02d_%02d_%02d.bak',
        $year, $month, $day, $hour, $min, $sec );
    $base or $base = $iBBS->path();
    File::Copy::copy( "${base}/subject.txt", $path )
        or $iKernel->throw_io_exception("${base}/subject.txt");

    return 1;
}

sub get_utf8 {
    my ( $zSubject, $key ) = @_;
    my $iKernel = $zSubject->{_kernel};
    my ( $subject, $res ) = @{ $zSubject->get($key) };

    $subject = $iKernel->get_encoded_str( $subject, 'utf8',
        $zSubject->{_encoding} );

    return [ $subject, $res ];
}

sub gets_by_condition {
    my ( $zSubject, $condition ) = @_;
    my $now       = time();
    my $keys      = $zSubject->to_array();
    my $ret       = [];
    my $resultset = $zSubject->{_rs};

    require Img0ch::BBS;
    my $bbs      = $zSubject->{_bbs};
    my $iBBS     = Img0ch::BBS->new( $zSubject->{_kernel}, { id => $bbs } );
    my $iSetting = $iBBS->get_setting_instance();
    my $iThread  = $iBBS->get_thread_instance(0);
    my $max_size = $iSetting->get_int('BBS_DATMAX') * 1024;
    my $i        = 0;

    my $expires  = $condition->{bydate};
    my $max_res  = $condition->{byres};
    my $pos      = $condition->{bypos};
    my $writable = $condition->{bywriteable};
    my $unused   = $condition->{byspeed};

    for my $key (@$keys) {
        $iThread->set_key($key);
        my $res    = $zSubject->get($key)->[1];
        my $elapse = ( $now - $key ) || 1;
        my $speed  = $res / $elapse * 86400;
        my $mtime  = ( stat( $iThread->path() ) )[9];
        $i++;
        if ( $expires and $now - $expires >= $mtime ) {
            push @$ret, [ $key, { reason => 'date', value => $mtime } ];
            next;
        }
        if ( $max_res and $res >= $max_res ) {
            push @$ret, [ $key, { reason => 'res', value => $res } ];
            next;
        }
        if ( $pos and $i >= $pos ) {
            push @$ret, [ $key, { reason => 'pos', value => $i } ];
            next;
        }
        if ( $speed and $res > 1 and $unused >= $speed ) {
            $speed = sprintf '%01.1f', $speed;
            push @$ret, [ $key, { reason => 'speed', value => $speed } ];
            next;
        }
        if ($writable) {
            my $errno = 0;
            if ( !$iThread->can_write( \$errno, $max_size ) ) {
                push @$ret,
                    [ $key, { reason => 'writeable', value => $errno } ];
                next;
            }
        }
    }
    return $ret;
}

sub include_special_threads { $_[0]->{__include_special} = 1 }

sub exclude_special_threads { $_[0]->{__include_special} = 0 }

sub to_array {
    my ($zSubject) = @_;
    return $zSubject->{__include_special}
        ? $zSubject->{_order}
        : [ grep { !/\A924/xms } @{ $zSubject->{_order} } ];
}

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

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

sub _count {
    my ( $zThread, $count ) = @_;
    my $error = 0;
    if ( !$zThread->can_write( \$error ) ) {
        if ( $error == 1 or $error == 2 ) {
            $$count--;
        }
    }
    return;
}

1;
__END__
