#!/usr/bin/perl -w
#
# make-history - make an HTML page with our movie history
#
# $Id: make-history,v 1.5 2005/01/11 15:54:13 esm Exp $
#
(our $ME = $0) =~ s|.*/||;
(our $VERSION = '$Revision: 1.5 $ ') =~ tr/[0-9].//cd;

use strict;

use Date::Parse;
use File::stat;
use Time::localtime;

###############################################################################
# BEGIN user-customizable section

# File in which we have all our movies
our $Movie_File = 'movies.source';

# Our output
our $Out = 'history.html';

# IMDB page
our $URL   = "http://us.imdb.com/M/title-exact?";	# URL to point to

# When to stop looking.
use constant EPOCH => str2time('Jan 1 2001');
defined EPOCH
  or die "$ME: Internal error: Could not parse EPOCH";

# How to run cvs diff
our @CVS_DIFF = qw(cvs -n -f diff -u);

# Months of the year
our @MoY = qw(January February March     April   May      June
	      July    August   September October November December);

# Set up safe, well-known environment
$ENV{PATH}   = '/usr/bin:/bin';		# Where we find rlog, rcsdiff
$ENV{CDPATH} = '';
$ENV{IFS}    = '';

# END   user-customizable section
###############################################################################

###############################################################################
# BEGIN boilerplate args checking, usage messages

sub usage($) {
  my ($exit_code) = @_;
  my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
  if ($exit_code != 0) {
    print  $STREAM "Try `$ME -help' for more information.\n";
  } else {
    print  $STREAM <<EOH;
Usage: $ME [OPTIONS]

blah blah blah

OPTIONS:

  --verbose    show verbose progress indicators
  --dry-run    make no actual changes

  --help       display this message
  --version    display program name and version
EOH

    # Has the POD section (at bottom) been filled in?  Provide pointer to it.
    grep { /\b$ME\b/ } <DATA>
      and print "\nFor man page, type:  perldoc $ME\n";
  }

  exit $exit_code;
}

# Command-line options
my $debug   = 0;
my $dry_run = 0;
my $force   = 0;
my $verbose = 0;
use Getopt::Long;
GetOptions( 'debug!'     => \$debug,
	    'dry-run|n!' => \$dry_run,
	    force        => \$force,
	    verbose      => \$verbose,

	    help         => sub { usage 0 },
	    version      => sub { print "$ME version $VERSION\n"; exit 0 },
) or usage(1);

# Make sure we have the right number of non-option arguments.
# Always tell the user why we fail.
@ARGV
  and (warn "$ME: this script takes no arguments\n"), usage 1;

# END   boilerplate args checking, usage messages
###############################################################################
# BEGIN helper functions

sub find_last_revision($) {
    my $f = shift;

    open ENTRIES, '<', 'CVS/Entries'
      or die "$ME: Cannot open CVS/Entries: $!\n";
    while (defined (my $line = <ENTRIES>)) {
	$line =~ m!^/\Q$f\E/(\d[\.\d]+)/!
	  or next;

	close ENTRIES;
	return $1;
    }
    close ENTRIES;

    die "$ME: Did not find $f in CVS/Entries\n";
}


##########
#  link  #  Return link to IMDB page for a movie
##########
sub imdb($) {
    my $title = shift;

    $title =~ s/\s+\[DVD\]\s*$//;

    $title =~ s/([^a-zA-Z0-9])/sprintf("%%%02X",ord($1))/ge;

    return "$URL$title";
}


#####################
#  reverse_numeric  #  sort helper.  2003, 2002, ...
#####################
sub reverse_numeric($$) {
    $_[1] <=> $_[0]
}

sub by_movie_date($$) {
    my ($a, $b) = @_;

    if ($a =~ /\((\d{4})\)/) {
	my $t_a = $1;
	if ($b =~ /\((\d{4})\)/) {
	    my $t_b = $1;

	    return 0
	      || $t_a <=> $t_b
	      || $a   cmp $b;
	}
    }

    return $a cmp $b;
}


# END   helper functions
###############################################################################

############################## CODE BEGINS HERE ###############################

my $rev = find_last_revision( $Movie_File );

my %date;
my %first_time;
while (1) {
    (my $previous_rev = $rev) =~ s|(?<=\.)(\d+)|$1-1|e;

    # The 1.84 edit switched from default=VHS to default=DVD.  Every
    # movie entry changed.  We don't want to show that.
    if ($rev eq '1.84') {
	$rev = $previous_rev;
	next;
    }

    my @cmd = (@CVS_DIFF, "-r$previous_rev", "-r$rev", $Movie_File);
    print "@cmd\n"					if $verbose;
#    open CVS_DIFF, '-|', @cmd
    my $cmd = join(' ', @cmd);
    open CVS_DIFF, "$cmd |"
      or die "$ME: Cannot fork (@cmd): $!\n";
    while (defined (my $line = <CVS_DIFF>)) {
	$line =~ s/\s+\[(VHS|DVD)\]\s*$//;

	if ($line =~ /^\+\+\+\s+\S+\s+(.*?)\s+\Q$rev\E\s*$/) {
	    $date{$rev} = str2time($1);
	}
	elsif ($line =~ /^\+\s+(.*)/) {
	    $first_time{$1} = $rev;
	}
    }
    close CVS_DIFF;
    $? == 256
      or die "$ME: Error running @cmd: status=$?\n";

    defined $date{$rev}
      or die "$ME: Could not figure out date for $rev\n";

    last if $date{$rev} < EPOCH;

    $rev = $previous_rev;
}


# %first_time is  (key=movie,  val=revision).
# Convert to  HoH (key=YYYY,   val={ key=MM, val={ key=DD, val=[ movie , ...]
my %by_date;
while (my ($movie, $revision) = each %first_time) {
    my $t = localtime($date{$revision});

    my $yy = $t->year + 1900;
    my $mm = $t->mon  +    1;
    my $dd = $t->mday;

    push @{$by_date{$yy}->{$mm}->{$dd}}, $movie;
}

# Now dump them out
my $tmp = "$Out.tmp.$$";
open OUT, ">$tmp"
  or die "$ME: Cannot create $tmp: $!\n";
print OUT <<"-";
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
	PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
	 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
<head>
 <title>Ed's Movies: Recent Additions</title>
 <link rel="stylesheet" type="text/css" href="film.css" />
</head>

<body>
<h1>Ed's Movies: Recent Additions</h1>
<p>
The list below shows changes to my <a href="index.html"><b>movie list</b></a>.
Dates indicate when a movie was <u>added to my list</u>; this does not
necessarily correspond closely to when I got the movie.
</p>
-

for my $yy (sort reverse_numeric keys %by_date) {
    print  OUT "<hr />\n",
               "\n",
               "<h1>$yy</h1>\n";

    for my $mm (sort reverse_numeric keys %{$by_date{$yy}}) {
	for my $dd (sort reverse_numeric keys %{$by_date{$yy}->{$mm}}) {

	    printf OUT "<h2>%s %d</h2>\n", $MoY[$mm-1], $dd;
	    print  OUT "<ul>\n";
	    for my $title (sort by_movie_date @{$by_date{$yy}->{$mm}->{$dd}}) {
		printf OUT " <li><a href=\"%s\">%s</a></li>\n",
		  imdb($title), $title;
	    }
	    print  OUT "</ul>\n";
	}
    }
}

printf OUT <<'-', scalar CORE::localtime(stat($Movie_File)->mtime);
<hr />
 <address>Last updated: %s</address>
</body>
</html>
-

close OUT
  or die "$ME: Error writing $tmp: $!\n";
chmod 0444 => $tmp;
rename $tmp => $Out
  or die "$ME: Cannot rename $tmp: $!\n";

__END__

###############################################################################
#
# Documentation
#

=head1	NAME

FIXME - description of what this script does

=head1	SYNOPSIS

FIXME [B<--foo>]  [B<--bar>]  [B<--verbose>] ARG1 [ARG2...] FIXME

FIXME  B<--help>  |  B<--version>

=head1	DESCRIPTION

B<FIXME> grobbles the frobniz on alternate Tuesdays, except where
prohibited by law.

=head1	OPTIONS

=over 4

=item B<--foo>

FIXME

=item B<--verbose>

Show progress messages.

=item B<--help>

Emit usage hints.

=item B<--version>

Display program version.

=back


=head1	DIAGNOSTICS

FIXME

=head1	ENVIRONMENT

FIXME

=head1	FILES

FIXME

=head1	RESTRICTIONS

FIXME

=head1	SEE ALSO

FIXME

e.g. L<Ascend::DB|Ascend::DB>
L<Ascend::DB::Project|Ascend::DB::Project>

=head1	AUTHOR

Your Name <FIXME@ascend.com>

Please report bugs or suggestions to <tools-group@ascend.com>

=cut
