#!/usr/bin/env perl
# countdown --- display a countdown timer to a specified time
# Author: Noah Friedman <friedman@splode.com>
# Created: 2006-05-20
# Public domain

# $Id: countdown,v 1.4 2009/02/12 21:44:37 friedman Exp $

# Commentary:
# Code:

$^W = 1; # enable warnings

use strict;
use POSIX qw(strftime);
use Time::Local;
use Getopt::Long;

(my $progname = $0) =~ s=.*/==;
my $verbose = 0;

sub compute_deadline
{
  local $_ = join (" ", @_);

  if (/(\d+(?:[---.\/]\d+){2}(?:\s+|:)\d+:\d+(?::\d+|))/) # 2006-05-26 21:55:00
    {
      my @ts = split (/[---.\/: ]+/, $1);
      $ts[0] -= 1900 if ($ts[0] >= 1900); # denormalize year
      $ts[1] -= 1;                        # denormalize month
      $ts[5] = 0 unless defined $ts[5];   # specify seconds

      # timelocal ($sec, $min, $hours, $mday, $mon, $year);
      # We use timelocal, not timegm, since the stamp is in local time.
      return timelocal (reverse @ts);
    }
  elsif (/^(\d+:\d+(?::\d+|))/) # 21:55 or 21:55:00
    {
      my @tm = split (/[.:]+/, $1);
      my @ts = localtime (time);

      $ts[0] = $tm[2] || 0;
      $ts[1] = $tm[1];
      $ts[2] = $tm[0];

      return timelocal (@ts);
    }
  else
    {
      # We use an eval and test so that the script doesn't die when the
      # module can't be loaded at compile-time.
      # Die at run-time if the module is actually needed but isn't found.
      eval "use Date::Parse";
      die if $@;
      return str2time ($_);
    }
  return;
}

sub duration
{
  my $sec = shift;

  #my @time_mult = (qw(31536000 2592000 86400 3600 60     1));
  #my @time_name = (qw(year     month   day   hour minute second));
  my @time_mult = (qw(86400 3600 60 1));
  my @time_name = (qw(day));

  my @result;
  my @clock;
  for (my $i = 0; $i < @time_mult; $i++)
    {
      my $val = int ($sec / $time_mult[$i]);
      $sec -= $val * $time_mult[$i];

      if (defined $time_name[$i])
        {
          push @result, sprintf ("%d %s%s", $val, $time_name[$i],
                                 ($val == 1 ? "" : "s"))
            if ($val != 0 || (@result == 0 && $i+1 == @time_mult));
        }
      else
        {
          push @clock, $val;
        }
    }
  my $clock = join (":", map { sprintf ("%02d", $_) } @clock);
  push @result, $clock if $clock;
  join (", ", @result);
}

sub display
{
  my ($deadline, $verbose) = @_;
  my $daysec = 60 * 60 * 24;
  my $ws = ' ' x 8 . chr(0x8) x 8;  # whitespace + backspace

  my $tsfmt = "%Y-%m-%d %H:%M:%S";
  my $s_deadline = strftime ($tsfmt, localtime ($deadline));

  my $width = length ($s_deadline) + 2;
  my $lfmt = sprintf ("%%-%ss %%-%ss %%s", $width, $width);
  print sprintf ($lfmt, "Current", "Deadline", "Remaining"), "\n" if $verbose;

  my $displayed = 0;
  local $| = 1;
  while (1)
    {
      my $now = time;
      my $left = $deadline - $now;
      last if $left < 0 && $displayed;

      my $s_now  = strftime ($tsfmt, localtime ($now));
      my $s_left = ($left < 0
                    ? "T+" . duration (-$left)
                    : duration ($left));

      my $s = ($verbose
               ? sprintf ($lfmt, $s_now, $s_deadline, $s_left)
               : $s_left);
      print $ws, "\r", $s;
      $displayed = 1;
      select (undef, undef, undef, 0.50); # pause 1/2 sec
    }

  print "\nTime!\n";
}

sub main
{
  local @ARGV = @_;

  Getopt::Long::config ('bundling', 'autoabbrev');
  GetOptions ("v|verbose", \$verbose, );

  unless (@ARGV)
    {
      print STDERR "Usage: $progname [timestamp]\n";
      exit (1);
    }

  $0 = join (" ", $progname, @ARGV);
  display (compute_deadline (@ARGV), $verbose);
}

main (@ARGV);

# eof