package TDS::PIM::Schedule;
# $Id: Schedule.pm,v 1.27 2001/01/03 08:20:55 tom Exp $
################################################################

=head1 NAME

TDS::PIM::Schedule - schedule

=head1 SYNOPSIS

 use TDS::PIM::Schedule;

 $s = new TDS::PIM::Schedule;
 $s->Read;
 print $s->AsHTML;

=cut

################################################################

use strict;
use vars qw(@ISA
	    $ContentTemplate $ContentSpanTemplate
	    $DateFormat $DateFormatMonthly
	    $NextMonthTemplate
	    $BeginTemplate $EndTemplate
	    $MaxNum $FutureLimitMonth $FutureLimitMonthAsta
	    $Delim
	    $TodayColor $NormalColor);

use ObjectTemplate;
use DateTime::Date;
use Template;
use JConv;

use TDS::System;
use TDS::PIM::Base;

@ISA = qw(TDS::PIM::Base);

{
    # initialize static variables
    # template for container data
    $ContentTemplate = qq(<li><span style="color: %color">%date%delim%content</span></li>\n) unless defined $ContentTemplate;
    $ContentSpanTemplate = qq(<li><span style="color: %color">%start_date-%end_date%delim%content</span></li>\n) unless defined $ContentSpanTemplate;
    $DateFormat = qq(%year/%month/%day(%week_holy)) unless defined $DateFormat;
    $DateFormatMonthly = qq(%year/%month) unless defined $DateFormatMonthly;
    $NextMonthTemplate = qq(%month) unless defined $NextMonthTemplate;

    # begin, end template
    $BeginTemplate = "<ul>\n" unless defined $BeginTemplate;
    $EndTemplate = "</ul>\n" unless defined $EndTemplate;

    # max number of displayin
    $MaxNum = 8 unless defined $MaxNum;
    $FutureLimitMonth = 2 unless defined $FutureLimitMonth;
    $FutureLimitMonthAsta = 1 unless defined $FutureLimitMonthAsta;

    # color
    $TodayColor = "red" unless defined $TodayColor;
    $NormalColor = "black" unless defined $NormalColor;

    # delimiter between date and content
    $Delim = ": " unless defined $Delim;
}

attributes qw(default_year now_date);

=head1 MEMBER FUNCTIONS

=cut

################################################################

sub initialize($)
{
    my $self = shift;

    $self->now_date(new DateTime::Date);
    $self->now_date->Set($TDS::Status->start_time->year,
			 $TDS::Status->start_time->month,
			 $TDS::Status->start_time->day);
    
    $self->cache_basename("schedule");
    $self->SUPER::initialize;
}
			 
sub Read($)
{
    my $self = shift;

    # generic datafile
    $self->ReadFile($self->GetDataFilename);
##    warn times, " Read Schedule:", $self->GetDataFilename;
    # diary_dir/YYYY/
    #  in this year
    my $year = $self->now_date->year;
    $self->default_year($year);
    $self->ReadFile($self->GetDataFilename($year));
##    warn times, " Read Schedule:", $self->GetDataFilename($year);
    #  in next year
    $year++;
    $self->default_year($year);
    $self->ReadFile($self->GetDataFilename($year));
}

sub AddContentByLine($$)
{
    my ($self, $line) = @_;

    # my ($date_str, $comment) = /^([A-Za-z\d\*\/\-]+)\s(.*)$/;
    my ($date_str, $comment) = $line =~ /^([^\s]+)\s(.*)$/;

    # specify range
    if ($date_str =~ /\-/){
	my ($ymd1, $ymd2) = split(/\-/, $date_str);
	my ($y1, $m1, $d1) = split("/", $ymd1);
	my ($y2, $m2, $d2) = split("/", $ymd2);
	
	unless ($d1){              # complete in MM/DD
	    ($m1, $d1) = ($y1, $m1);
	    $y1 = $self->default_year
	    }
	unless ($d2){              # complete in MM/DD
	    ($m2, $d2) = ($y2, $m2);
	    $y2 = $self->default_year;
	}
	$self->PushSpan(new DateTime::Date(year=>$y1,month=>$m1,day=>$d1),
			new DateTime::Date(year=>$y2,month=>$m2,day=>$d2),
			$comment);
	# in range specified, skip to next data
	next;
    } elsif ($date_str !~ /\d/){           # week
	my $date = new DateTime::Date(year=>$self->now_date->year,
				      month=>$self->now_date->month,
				      day=>$self->now_date->day);
	for (1..7){
	    if ($date->week_string('ABBR') =~ /^$date_str$/i ||
		$date->week_string('FULL') =~ /^$date_str$/i ||
		$date->week_string('JAPANESE') =~ /^$date_str$/i){
		push(@{$self->content},
		     {date=>$date, comment=>$comment,
		      is_today=>$date == $self->now_date});
		last;
	    }
	    $date++;
	}
	# skip in specifying week
	next;
    }
    
    # decompose date
    my @tmp = split("/", $date_str);
    my ($year, $month, $day) = @tmp;

    # partly specifying is NOT supported. (may be supported in future)
    if ($day =~ /^[abc]$/){
	my %hash = ('a'=>1, 'b'=>10, 'c'=>20);
	$day = $hash{$day};
	$comment .= "<strong>partly specifying is NOT supported</strong>";
    }
    # in MM/DD format, regard as $self->default_year
    if (@tmp == 2){
	($month, $day) = ($year, $month);
	$year = $self->default_year || $self->now_date->year;
    } elsif (@tmp < 2) {
	print qq(<strong>Warning: schedule data item must be as "[YYYY/]MM/DD" : $date_str</strong>);
    }
    # set date
    my $date = new DateTime::Date;
    $date->year($year);
    $date->month($month);
    $date->day($day);
    # 柔軟指定 (*/01/13: birthday など)
    
    # 結構面倒な条件分岐をしています。
    # ケースとしては、
    # 1) Y/M/D
    # 2) Y/M/*
    # 3) Y/*/D
    # 4) Y/*/*
    # 5) */M/D
    # 6) */M/*
    # 7) */*/D
    # 8) */*/*
    
    # と８種類ありますが、8) は除外します。
    
    # 処理手順は、
    # **?
    #   ***(8)
    #   **D(7)
    # *M?
    #   *M*(6)
    #   *MD(5)
    # Y*?
    #   Y**(4)
    #   Y*D(3)
    # YM*(2)
    # YMD(1)
    
    # です。
    
    if ($year =~ /\*/ && $month =~ /\*/){  # */*/DD
	if ($day =~ /\*/){        # */*/* って全部じゃん
	    die "schedule: */*/* not allowed"; # 警告の方がよいかな？
	}
	my $cnt_date = new DateTime::Date(year=>$self->now_date->year,
					  month=>$self->now_date->month,
					  day=>$day);
	my $cnt = 0;
	for (1..12){
	    my $tmp_date = new DateTime::Date(year=>$cnt_date->year,
					      month=>$cnt_date->month,
					      day=>$cnt_date->day);
#		print $tmp_date->year, $tmp_date->month, $tmp_date->day, ",";
	    unless ($tmp_date < $self->now_date){
		push(@{$self->content},
		     {date=>$tmp_date, comment=>$comment,
		      is_today=>$tmp_date == $self->now_date
		      });
		$cnt++;
		last if $cnt >= $FutureLimitMonthAsta;
	    }
	    $cnt_date += '1M';
	}
    } elsif ($year =~ /\*/){              # */MM/DD
	# 今年と来年だけ：それ以降は表示してもしょうがない
	for (0..1){
	    my $tmp_date = new DateTime::Date(year=>$self->now_date->year+$_,
					      month=>$month,
					      day=>$day);
	    if ($day =~ /\*/){
		# 先月以前のはスキップ
		$tmp_date->day(31);                  # 一旦月末にしてから
		next if $tmp_date < $self->now_date; # 比較
		my $this_month = ($tmp_date->year == $self->now_date->year &&
				  $tmp_date->month == $self->now_date->month);
		$self->PushAllDaysMonth($tmp_date, $comment, $this_month);
	    } else {
		next if  $tmp_date < $self->now_date;
		push(@{$self->content},
		     {date=>$tmp_date, comment=>$comment,
		      is_today=>$tmp_date == $self->now_date
		      });
	    }
	    last;    # 一つでも格納すれば終わり
	}
    } elsif ($month =~ /\*/){            # YYYY/*/DD
	if ($year == $self->now_date->year){
	    if ($day =~ /\*/){         # YYYY/*/*
#		    $self->PushAllDaysYear($date, $comment);
		$self->PushSpan(new DateTime::Date(year=>$year,
						   month=>1,
						   day=>1),
				new DateTime::Date(year=>$year,
						   month=>12,
						   day=>31),
				$comment);
	    } else {                   # YYYY/*/D
		my $m = $self->now_date->month;
		my $dt = new DateTime::Date(year=>$year,
					    month=>$self->now_date->month,
					    day=>$day);
		for (1..$FutureLimitMonthAsta){
		    if ($dt => $self->now_date){
			# データ配列に格納
			push(@{$self->content},
			     {date=>$dt->Dup, comment=>$comment,
			      is_today=>$dt == $self->now_date
			      });
		    }
		    $dt->Increment('1M');
		}
	    }
	}
    } elsif ($day =~ /\*/){              # YYYY/MM/*
	my $diff = ($date->year*12+$date->month) -
	    ($self->now_date->year*12+$self->now_date->month);
	if ($diff >=0 &&                     # 古くなく
	    $diff <= $FutureLimitMonth){     # 指定内だったら
	    my $this_month = ($date->year == $self->now_date->year &&
			      $date->month == $self->now_date->month);
	    $self->PushAllDaysMonth($date, $comment, $this_month);
	}
    } else {                             # 普通の YYYY/MM/DD
	next if $date < $self->now_date;    # 過去のは表示しない
	# データ配列に格納
	push(@{$self->content},
	     {date=>$date, comment=>$comment,
	      is_today=>$self->now_date==$date});
    }
}

sub PushAllDaysMonth ($$$;$)
{
    my ($self, $date, $comment, $this_month) = @_;
    
    $date->day(1);
    my $end_date = new DateTime::Date;
    $end_date->Set($date->year, $date->month, $date->DaysMonth);

    push(@{$self->content},
	 {date=>$date, all_days=>1, is_today=>$this_month,
	  comment=>$comment});
}
sub PushSpan ($$$$)
{
    my ($self, $start, $end, $comment) = @_;

    if ($end < $self->now_date){
	next;                # 過ぎた予定
    }
    my $is_today;
    if ($start <= $self->now_date){
	$is_today = 1;
    }
    push(@{$self->content},
	 {date=>$start, 
	  start_date=>$start,
	  end_date=>$end,
	  comment=>$comment,
	  is_today=>$is_today,
	  span=>1});
}
################################################################
sub AsContentHTML($)
{
    my $self = shift;
    my $html;

##    warn "Schedule: AsContentHTML";
    $html = $BeginTemplate;
    my $cnt = 0;
    for (sort {$a->{date} <=> $b->{date} || $a->{comment} cmp $b->{comment}}
	 @{$self->content}){
#	print $_->{date}->month, $_->{date}->day, $_->{comment}, "<br>";
	my $diff = ($_->{date}->GetTime - $self->now_date->GetTime)/(24*60*60);
	#	print $_->{date}->month, ", ";
	#	print "$diff, ";
	last if $diff > $FutureLimitMonth*30;
	#	print $_->{date}->day, ",";
	
	my $params;
	$params->{'color'} = ($_->{is_today})? $TodayColor : $NormalColor;
	$params->{'delim'} = $Delim;
	$_->{date}->GetParams($params);
	
	# 来月
	my $next_month = $_->{date}->Dup;
	$next_month += "1M";
	my $tmp_params = $next_month->GetParams;
	$params->{next_month} = Expand($NextMonthTemplate, $tmp_params);
	
	# 来々月
	my $next2_month = $next_month->Dup;
	$next2_month += "1M";
	$tmp_params = $next2_month->GetParams;
	$params->{next2_month} = Expand($NextMonthTemplate, $tmp_params);
	#	$params->{'content'} = $_->{comment};
	$params->{'content'} = Expand($_->{comment}, $params);
	if ($_->{span}){
	    $params->{'start_date'} = Expand($DateFormat, $params);
	    $_->{end_date}->GetParams($params);
	    $params->{'end_date'} = Expand($DateFormat, $params);
	    $html .= Expand($ContentSpanTemplate, $params);
	} elsif ($_->{all_days}){
	    $params->{'date'} = Expand($DateFormatMonthly, $params);
	    $html .= Expand($ContentTemplate, $params);
	} else {
	    $params->{'date'} = Expand($DateFormat, $params);
	    $html .= Expand($ContentTemplate, $params);
	}
	
	# 指定数のみ表示
	$cnt++;
	last if $cnt >= $MaxNum;
    }
    $html .= $EndTemplate;
    
    return $html;
}

################
# override TDS::Cache
sub IsFresh($)
{
    my $self = shift;

    return 0 unless $self->SUPER::IsFresh;

    # yesterday's cache is not fresh
    my $cache_lm = $self->GetCacheLastModified;
    my $dt = new DateTime::Date;
    $dt->SetTime($cache_lm, $TDS::System::TZ);

#    warn $dt->Dump, $self->now_date->Dump;
    return 0 if $dt != $self->now_date;

    # check lm with datafiles
    my $year = $self->now_date->year;

    # general data
    my $general_lm = $self->GetDataFileLastModified;
    return 0 if $general_lm > $cache_lm;

    # this year's data
    my $this_year_lm = $self->GetDataFileLastModified($year);
    return 0 if $this_year_lm > $cache_lm;

    # next year's data
    my $next_year_lm = $self->GetDataFileLastModified($year+1);
    return $0 if $next_year_lm > $cache_lm;

    return 1;
}
################
sub GetDataFilename ($;$)
{
    my ($self, $year) = @_;

    require TDS::DirInfo;
    if ($year){
	return sprintf("%s/%04d/%s.dat",
		       &TDS::DirInfo::GetDiaryDir(),
		       $year,
		       $self->cache_basename);
    } else {
#	warn "datafilenmae: ", TDS::DirInfo::GetDataDir(), ", ", $self->cache_basename;
	my $filename= sprintf("%s/%s.dat",
		       &TDS::DirInfo::GetDataDir(),
		       $self->cache_basename);
#	warn "filename: $filename";
	return $filename;
    }
}
sub GetDataFileLastModified($;$)
{
    my ($self, $year) = @_;

    my $filename = $self->GetDataFilename($year);
    my $lm = (stat($filename))[9];
#    warn "lm($year): $filename, $lm";
    return $lm;
}
	
1;
