package HNS::PIM::Schedule;
# $Id: Schedule.pm,v 1.21 2000/08/01 16:15:14 masato Exp $
################################################################

=head1 NAME

HNS::PIM::Schedule - スケジュールクラス

=head1 SYNOPSIS

 my $sch = new HNS::PIM::Schedule(dir=>'diary');
 $sch->Read;
 print $sch->AsHTML;


=cut

################################################################
use strict;
#require 'jcode.pl';
use ObjectTemplate;
use CodeConv;
@HNS::PIM::Schedule::ISA = qw(ObjectTemplate);

use HNS;
use HNS::System;
use HNS::Template;

attributes qw(contents error);

################################################################
use vars qw($MaxNum $ContentTemplate $PastContentTemplate $TodayContentTemplate $BeginTemplate $EndTemplate @WeekString @ABCString $Unfixed $Range $RRange $GraceDays);

$MaxNum = 8;                # max number of showing
@WeekString = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');

@ABCString = ('Early', 'Middle', 'Later');
$Unfixed = "??";
$Range = 1;
$RRange = $Range;
$GraceDays = 0;

# template
$ContentTemplate = qq(<li><strong>%month/%day%week</strong> %content</li>\n);
$TodayContentTemplate = '';
$PastContentTemplate = qq(<li>%month/%day%week %content</li>\n);
$BeginTemplate = "<ul>";
$EndTemplate = "</ul>";

sub initialize($)
{
    my $self = shift;
    $self->contents([]);
}
################################################################

=head2 $t->Read;

データファイルを読み込む

=cut

sub Read ($)
{
    my $self = shift;
    use DateTime::Date;
    my $date = new DateTime::Date;
    $date->year($HNS::Status->start_time->year);
    $date->month($HNS::Status->start_time->month);
    $date->day($HNS::Status->start_time->day);

    my %abc = (A => 10.5, B => 20.5, C => 31.5,
               a => 10.5, b => 20.5, c => 31.5);
    my %dowNum = ('sun', 0, 'mon', 1, 'tue', 2, 'wed', 3,
		  'thu', 4, 'fri', 5, 'sat', 6,
		  'su',  0, 'mo',  1, 'tu',  2, 'we',  3,
		  'th',  4, 'fr',  5, 'sa',  6,
		  '日',  0, '月',  1, '火',  2, '水',  3,
		  '木',  4, '金',  5, '土',  6);

    # collect reading file
    my @files;

    my $readMonths = $Range;
    if ($GraceDays > 0) {
	$date-='1M';
    	$readMonths++;
    }
    for (0 .. $readMonths) {
	push(@files, $self->get_files($date));
	$date+='1M';
    }

#    print "content-type: text/html\n\n";

    # read yotei files
    for my $file (@files){
	# read file
	open(F, $file) || next;
#	print "$file, ";
	# set default year, month
	my ($y, $m) = $file =~ /y(\d{4})(\d{2})$/;
	
	# read content
	while (<F>){
	    next if /^$/;
	    CodeConv::toeuc(*_);
	    my ($num, $content) = /^(\S+)\s+(.*)$/;
	    my $date = new DateTime::Date(year=>$y, month=>$m);
	    if ($num =~ m!^(\d+)/(\d+)$!){     # 03/31 content..
		$date->month($1), $date->day($2);
	    } elsif ($num =~ m!^(\d+)$!){      # 31 content..
		$date->day($num);
	    } elsif ($num =~ m!^(\d+)/([ABCabc])$!) { # 03/A content ...
		$date->month($1);
		$date->day($abc{$2});
	    } elsif ($num =~ m!^[ABCabc]$!) {     # A content ...
		$date->day($abc{$num});
	    } else {
		$date->day(32);
	    }
	    push(@{$self->contents}, {date=>$date, content=>$content});
	}
	close F;
    }
    closedir(DIR);

    # add repeat plan
    my $filename = "$HNS::System::DiaryDir/repeat";
    my @repeats;
    open(F, $filename);
    while(<F>){
	next if /^\s*#/;
	next if /^\s*$/;
	if (/^([^\[\s]+)(?:\[([^\]]+)\])?\s+(.*)$/) {
	    my @range = parse_range($2);
	    push(@repeats, [$1, $3, @range]); # [num content begin end]
	} elsif (/^\[([^\]]+)\]\s+(.*)$/) {
	    my @range = parse_range($1);
	    push(@repeats, ['', $2, @range]); # [num content begin end]
	}
    }
    close F;
    $date->year($HNS::Status->start_time->year);
    $date->month($HNS::Status->start_time->month);
    $date->day($HNS::Status->start_time->day);
    $readMonths = $RRange;
    if ($GraceDays > 0) {
	$date-='1M';
    	$readMonths++;
    }
    for (0 .. $readMonths) {
	my ($y, $m) = ($date->year, $date->month);
	$date+='1M';
	foreach my $rplan (@repeats) {
	    my ($num, $content, $brng, $erng) = @$rplan;
	    my $tmpd = new DateTime::Date(year=>$y, month=>$m, day=>1);
	    my $dmonth = $tmpd->DaysMonth();
	    if (@$rplan == 4 && $num ne ''
	     && ((sprintf("%04d%02d%02d", $y, $m, $dmonth) < $brng)
	      || (sprintf("%04d%02d%02d", $y, $m, 1) > $erng))) {
		    next;
	    }
	    my $d;
	    my $m2 = $m;
	    if ($num =~ m!^(\d+)/(\d+)$!){        # yearly 1/31 content..
		$m2 = $1;
		$d = $2;
	    } elsif ($num =~ m!^(\d+)/([ABCabc])$!) { # yearly 1/A content ...
		$m2 = $1;
		$d = $abc{$2};
	    } elsif ($num =~ m!^(\d+)/(-?\d+)([^\d].*)$!) {  # yearly 1/nth week
	    	my $dow = $3;
		$dow =~ tr/A-Z/a-z/;
	    	if ($dowNum{$dow} eq '') {
		    $d = 32;
		    $content = "$num? $content";
		} else {
		    $m2 = $1;
		    $d = get_nth_dow($y, $m2, $2, $dowNum{$dow});
		}
	    } elsif ($num =~ m!^(\d+)$!){         # monthly 31 content..
		$d = $num;
	    } elsif ($num =~ m!^[ABCabc]$!) {     # monthly A content ...
		$d = $abc{$num};
	    } elsif ($num =~ m!^[Ee]?(-\d+)?$! && $num ne '') { # monthly End content ...
		$d = $dmonth + $1;
	    } elsif ($num =~ /^(-?\d+)([^\d].*)$/) {  # monthly nth week
	    	my $dow = $2;
		$dow =~ tr/A-Z/a-z/;
	    	if ($dowNum{$dow} eq '') {
		    $d = 32;
		    $content = "$num? $content";
		} else {
		    $d = get_nth_dow($y, $m, $1, $dowNum{$dow});
		}
	    } else {
	    	my $dow = $num;
		$dow =~ tr/A-Z/a-z/;
	    	if ($dowNum{$dow} ne '') {        # weekly
	    	    for (my $i = 1; $i <= 5; $i++) {
			$d = get_nth_dow($y, $m, $i, $dowNum{$dow});
			my $s = sprintf("%04d%02d%02d", $y, $m, $d);
			if ($d >= 1 && $d <= $dmonth
			 && (@$rplan == 2
			  || ($s >= $brng && $s <= $erng))) {
			    my $tmpd2 = new DateTime::Date(year=>$y, month=>$m, day=>$d);
			    push(@{$self->contents}, {date=>$tmpd2, content=>$content});
			}
		    }
		    $d = 0;
	    	    $m2 = 0;
		} elsif (@$rplan == 4 && $num eq '') {	# every day with range
		    for (my $i = 1; $i <= $dmonth; $i++) {
			my $s = sprintf("%04d%02d%02d", $y, $m, $i);
			if ($s >= $brng && $s <= $erng) {
			    my $tmpd2 = new DateTime::Date(year=>$y, month=>$m, day=>$i);
			    push(@{$self->contents}, {date=>$tmpd2, content=>$content});
			}
		    }
		    $d = 0;
	    	    $m2 = 0;
		} else {
		    $content = "$num? $content";
		    $d = 32;
		}
	    }
	    if ($m == $m2 && 1 <= $d && $d <= $dmonth) {
		my $s = sprintf("%04d%02d%02d", $y, $m, $d);
		if ((@$rplan == 2) || ($s >= $brng) && ($s <= $erng)) {
		    $tmpd->day($d);
		    push(@{$self->contents}, {date=>$tmpd, content=>$content});
		}
	    }
	}
    }
}

=head2 $t->AsHTML;

HTML変換して返す

=cut

sub AsHTML($)
{
    my $self = shift;
    my $templ = new HNS::Template;
    my $cnt;
    my $html;
    my $day;
    my $week;

    my $now_date = $HNS::Status->start_time;
    my $limit_date = new DateTime::Date(year=>$now_date->year,
					month=>$now_date->month,
					day=>$now_date->day);
    $limit_date -= "${GraceDays}D";
    $html .= $BeginTemplate;
    
    my $until_date = new DateTime::Date(year=>$now_date->year,
					month=>$now_date->month,
					day=>$now_date->day);
    $until_date += $Range . 'M';
    # error check
    if ($self->error){
	$html .= $self->error;
    }
    for (sort { $a->{date} <=> $b->{date} ||
                $a->{content} cmp $b->{content} } @{$self->contents}){
	next unless $limit_date <= $_->{date} &&   # now and future
	    $until_date > $_->{date};              # until next month
	last if ++$cnt > $MaxNum;                  # less than equal $MaxNum
	$week = "";
	if ($_->{date}->day == 32) { # Unfixed
	    $day = $Unfixed;
	} elsif ($_->{date}->day == 10.5 ) { # early
	    $day = $ABCString[0];
	} elsif ($_->{date}->day == 20.5 ) { # middle
	    $day = $ABCString[1];
	} elsif ($_->{date}->day == 31.5 ) { # late
	    $day = $ABCString[2];
	} else {
	    $day = sprintf("%02d", $_->{date}->day);
	    $week = '(' . $WeekString[$_->{date}->week] . ')';
	}
	my $template;
	if ($now_date > $_->{date}) {
	    $template =  $PastContentTemplate
	} elsif ($now_date == $_->{date}) {
	    $template =  $TodayContentTemplate || $ContentTemplate;
	} else {
	    $template =  $ContentTemplate
	}
	$html .= $templ->Expand($template,
				{year=>sprintf("%04d", $_->{date}->year),
				 month=>sprintf("%02d", $_->{date}->month),
				 day=>$day,
				 week=>$week,
				 content=>$_->{content}});
    }
    $html .= $EndTemplate;
}
################################################################
# private:
sub get_files($$)
{
    my ($self, $date) = @_;
    my @files;
    
    my $file_flat = sprintf("%s/y%04d%02d",
			    $HNS::System::DiaryDir,
			    $date->year, $date->month);
    my $file_year = sprintf("%s/%04d/y%04d%02d",
			    $HNS::System::DiaryDir,
			    $date->year, $date->year, $date->month);
    if (-f $file_flat){
	if (-f $file_year){
	    $self->set_error("Dupricate: $file_flat, $file_year");
#	    die "Dupricate: $file_flat, $file_year";
	}
	push(@files, $file_flat);
    } elsif (-f $file_year){
	push(@files, $file_year);
    }
    return @files;
}

sub set_error($$){
    my ($self, $msg) = @_;
    
    $self->error($msg);
}

#指定年月の第$n$dow曜日($n<0なら月末から数える, $n=-1なら最終)
sub get_nth_dow($$$$) {
    my ($y, $m, $n, $dow) = @_;
    my $dtmp = new DateTime::Date(year=>$y, month=>$m, day=>1);
    my $days = $dtmp->DaysMonth();
    my $d;
    if ($n > 0) {
#	$dtmp->day(1);
	my $first = $dtmp->week();
	if ($dow >= $first) {
	    $d = ($n-1)*7 + $dow - $first + 1;
	} else {
	    $d = $n*7 + $dow - $first + 1;
	}
    } elsif($n < 0) {
	$dtmp->day($days);
	my $last = $dtmp->week();
	if ($dow <= $last) {
	    $d = $days - ($last - $dow) + ($n+1)*7;
	} else {
	    $d = $days - ($last - $dow) + $n*7;
	}
    }
    if ($d < 1 || $d > $days) {
	return 0;
    }
    return $d;
}

# repeatの範囲指定をparseする
# 戻り値: ()                       : 範囲指定なし
#         ('yyyymmdd', 'yyyymmdd') : 範囲の最初と最後の日
sub parse_range($) {
    my ($spec) = @_;
    my @ba = (0, 1, 1);
    my @ea = (9999, 12, 31);
    return () if ($spec eq '');
    if ($spec =~ /^([^-]*)-(.*)$/) {
    	my ($b, $e) = ($1, $2);
	if ($b eq '') {
#	    @ba = (0, 1, 1);
	} elsif ($b =~ /^\d{4}$/) {
	    @ba = ($b, 1, 1);
	} elsif ($b =~ m!^(\d{4})/(\d+)$!) {
	    @ba = ($1, $2, 1);
	} elsif ($b =~ m!^(\d{4})/(\d+)/(\d+)$!) {
	    @ba = ($1, $2, $3);
	}
	if ($e eq '') {
#	    @ea = (9999, 12, 31);
	} elsif ($e =~ /^\d{4}$/) {
	    @ea = ($e, 12, 31);
	} elsif ($e =~ m!^(\d{4})/(\d+)$!) {
#	    my $tmpd = new DateTime::Date(year=>$y, month=>$m, day=>1);
#	    my $dmonth = $tmpd->DaysMonth();
#	    @ea = ($1, $2, $dmonth);
	    @ea = ($1, $2, 31);
	} elsif ($e =~ m!^(\d{4})/(\d+)/(\d+)$!) {
	    @ea = ($1, $2, $3);
	}
    } else {
    	# illegal format
    }
    return (sprintf("%04d%02d%02d", @ba), sprintf("%04d%02d%02d", @ea));
}

1;
