# $File: //depot/OurNet-BBS/BBS/MAPLE2/ArticleGroup.pm $ $Author: autrijus $
# $Revision: #15 $ $Change: 1600 $ $DateTime: 2001/08/29 23:35:16 $

package OurNet::BBS::MAPLE2::ArticleGroup;

use strict;
use warnings;
use fields qw/bbsroot board basepath name dir recno mtime btime/,
           qw/_ego _hash _array/;

use OurNet::BBS::Base (
    '$packstring'    => 'Z33Z1Z14Z6Z73C',
    '$packsize'      => 128,
    '@packlist'      => [qw/id savemode author date title filemode/],
);

my %chronos;

sub basedir {
    no warnings 'uninitialized';
    return join('/', @{$_[0]}{qw/bbsroot basepath board dir/});
}

sub new_id {
    my $self = shift;
    my ($id, $file);

    my $chrono = time();

    no warnings 'uninitialized';
    
    $chronos{$self->{board}} = $chrono 
        if $chrono > $chronos{$self->{board}};

    while ($id = "D.$chrono.A") {
        $file = join('/', $self->basedir, $id);
        last unless -e $file;
        $chrono = ++$chronos{$self->{board}};
    }

    mkdir join('/', $self->basedir, $self->{name});
    return $id;
}

sub refresh_id {
    my ($self, $key) = @_;

    $self->{name} ||= $self->new_id;

    if (defined $self->{recno}) {
	my $file = join('/', $self->basedir, $self->{name}, '.DIR');
	$self->filestamp($file, 'btime');
    }

    my $file = join('/', $self->basedir, '.DIR');
    local $/ = \$packsize;
    open(my $DIR, $file) or die "can't read DIR file for $self->{board}: $!";

    if (defined $self->{recno}) {
        seek $DIR, $packsize * $self->{recno}, 0;
        @{$self->{_hash}}{@packlist} = unpack($packstring, <$DIR>);
        if ($self->{_hash}{id} ne $self->{name}) {
            undef $self->{recno};
            seek $DIR, 0, 0;
        }
    }

    unless (defined $self->{recno}) {
        $self->{recno} = 0;

        while (my $data = <$DIR>) {
            @{$self->{_hash}}{@packlist} = unpack($packstring, $data);
            # print "$self->{_hash}{id} versus $self->{name}\n";
            last if ($self->{_hash}{id} eq $self->{name});
            $self->{recno}++;
        }

	no warnings 'uninitialized';

        if ($self->{_hash}{id} ne $self->{name}) {
            $self->{_hash}{id} = $self->{name};
            $self->{_hash}{author}   ||= 'guest.';
            $self->{_hash}{date}     = sprintf(
		"%2d/%02d", (localtime)[4] + 1, (localtime)[3]
	    );
            $self->{_hash}{title}    = 'กป (untitled)';
            $self->{_hash}{filemode} = 0;

            open($DIR, '+>>', $file) 
		or die "can't write DIR file for $self->{board}: $!";
            print $DIR pack($packstring, @{$self->{_hash}}{@packlist});
            close $DIR;

            mkdir join('/', $self->basedir, $self->{name});
            open($DIR, '>', join('/', $self->basedir, '.DIR'));
            close $DIR;
        }
    }

    return 1;
}

sub FETCHSIZE {
    my $self = $_[0]->ego;

    no warnings 'uninitialized';
    return int((stat(
	join('/', @{$self}{qw/bbsroot basepath board dir name/}, '.DIR')
    ))[7] / $packsize);
}

# Fetch key: id savemode author date title filemode body
sub refresh_meta {
    my ($self, $key, $flag) = @_;

    no warnings qw/uninitialized numeric/;

    my $file = join('/', $self->basedir, $self->{name}, '.DIR');
    my $name;

    if ($self->contains($key) or $key eq 'recno' or $key eq 'btime') {
        goto &refresh_id;
    }
    elsif (!defined($key) and $self->{dir}) {
        $self->refresh_id;
    }

    if ($key and $flag == HASH) {
        # hash key -- no recaching needed
        return if $self->{_hash}{$key};

        my $obj = $self->module(substr($key, 0, 2) eq 'D.'
            ? 'ArticleGroup' : 'Article')->new(
                $self->{bbsroot},
                $self->{board},
                $self->{basepath},
                $key,
                "$self->{dir}/$self->{name}",
            );

        $self->{_hash}{$key} = $self->{_array}[$obj->recno] = $obj;

        return 1;
    }

    open(my $DIR, $file)
	or (warn "can't read DIR file for $file: $!", return);

    if (defined($key) and $flag == ARRAY) {
        # out-of-bound check
        return if $key < 0 or $key >= int((stat($file))[7] / $packsize);

        seek $DIR, $packsize * $key, 0;
        read $DIR, $name, 33;
        $name = unpack('Z33', $name);

        return if exists $self->{_hash}{$name}
		and $self->{_hash}{$name}== $self->{_array}[$key];

        my $obj = $self->module(substr($name, 0, 2) eq 'D.'
            ? 'ArticleGroup' : 'Article')->new(
                $self->{bbsroot},
                $self->{board},
                $self->{basepath},
                $name,
                "$self->{dir}/$self->{name}",
                $key,
            );

        $self->{_hash}{$name} = $self->{_array}[$key] = $obj;

        close $DIR;
        return 1;
    }

    return if $self->filestamp($file);

    seek $DIR, 0, 0;

    foreach my $key (0 .. int((stat($file))[7] / $packsize) - 1) {
        read $DIR, $name, $packsize;
        $name = unpack('Z33x95', $name);

        # return the thing
        $self->{_hash}{$name} = $self->{_array}[$key] = $self->module(
	    substr($name, 0, 2) eq 'D.' ? 'ArticleGroup' : 'Article'
	)->new(
	    $self->{bbsroot},
	    $self->{board},
	    $self->{basepath},
	    $name,
	    "$self->{dir}/$self->{name}",
	    $key,
	);
    }

    close $DIR;

    return 1;
}

sub STORE {
    my ($self, $key, $value) = @_;
    ($self, my $flag) = @{${$self}};

    no warnings 'uninitialized';

    if ($flag == HASH) {
	if ($self->contains($key)) {
	    $self->refresh($key, $flag);
	    $self->{_hash}{$key} = $value;

	    my $file = join('/', $self->basedir, '.DIR');

	    open(my $DIR, '+<', $file) or die "cannot open $file for writing";
	    seek $DIR, $packsize * $self->{recno}, 0;
	    print $DIR pack($packstring, @{$self->{_hash}}{@packlist});
	    close $DIR;
	    return 1;
	}

	# special case: hash without key becomes PUSH.
	die 'arbitary storage of message-ids condered harmful.' if $key;
	$key = $#{$self->{_array}} + 1;
	$flag = ARRAY;
    }
    elsif (!$self->{_array}) {
    	$self->refresh_meta;
    }

    my $obj;

    if ($self->{_array}[$key]) {
	$obj = $self->{_array}[$key];
    }
    else {
	$obj = $self->module('Article', $value)->new(
	    $self->{bbsroot},
	    $self->{board},
	    $self->{basepath},
	    undef,
	    "$self->{dir}/$self->{name}",
	    $flag == ARRAY ? $key : undef,
	);
    }

    use Date::Parse;
    use Date::Format;

    if (ref($value) and $value->{header}) {
	@{$value}{qw/author nick/} = ($1, $2)
	    if $value->{header}{From} =~ m/^\s*(.+?)\s*(?:\((.*)\))?$/g;

	@{$value}{qw/author nick/} = ($2, $1)
	    if $value->{header}{From} =~ m/^\s*\"?(.*?)\"?\s*\<(.*)\>$/g;

	$value->{date}  = time2str(
	    '%m/%d', str2time($value->{header}{Date})
	);
	$value->{date}  =~ s/^0/ /; # how crude!
	$value->{title} = $value->{header}{Subject};
    }

    while (my ($k, $v) = each %{$value}) {
	$obj->{$k} = $v unless $k eq 'body' or $k eq 'id';
    };

    $obj->{body} = $value->{body} if ($value->{body});
    $self->refresh($key, $flag);
}

sub EXISTS {
    my ($self, $key) = @_;
    $self = $self->ego;

    return unless defined $self->{name};
    return 1 if exists ($self->{_hash}{$key});

    my $file = join('/', $self->basedir, $self->{name}, '.DIR');
    return 0 if $self->filestamp($file, 'mtime', 1);

    open(my $DIR, $file) or die "can't read DIR file $file: $!";

    my $board;

    foreach (0 .. int((stat($file))[7] / $packsize)-1) {
        seek $DIR, $packsize * $_, 0;
        read $DIR, $board, 33;
        return 1 if unpack('Z33', $board) eq $key;
    }

    close $DIR;
    return 0;
}

1;
