#!/usr/bin/perl

use strict;
use warnings;

use lib './lib/perl';
use File::Path;
use File::Spec;
use Net::FTP;
use Carp;

sub ConnectFTP ();
sub CreateRemoteDirectory ($);
sub DisconnectFTP ();
sub Initialise ();
sub Main ();
sub MarkUpdated ($$);
sub NeedsUpdating ($$);
sub PutFile ($$$);
sub UpdateBinary ($$$);
sub UpdateConfigFiles ();
sub UpdateDir ($$$);
sub UpdateHTMLFiles ();
sub UpdateTFiles ();
sub UpdateText ($$$);

my (%config) = (
  # change the following as necessary for your event
  'ftp_server' => 'www.scrabble-assoc.com',
  'ftp_user' => 'username',
  'ftp_password' => 'password',
  'ftp_path' => '/httpdocs/wherever',
  'event_paths' => [qw(
    2008-nsc-a 2008-nsc-b 2008-nsc-c
    2008-nsc-d 2008-nsc-e 2008-nsc-f
    )],
  # do not change below this line
  'event_data' => {},
  );


Main;

sub CreateRemoteDirectory ($) {
  my $dir = shift;
  my $rv = $global::ftp->mkdir($dir);
  if ($rv || $global::ftp->code =~ /^(521|550)$/) {
    return 1;
    }
  else {
    return 0;
    }
  }

sub ConnectFTP () {
  return if $global::ftp;
  $global::ftp = new Net::FTP(
    $config{'ftp_server'},
    Debug => 0,
    Passive => 1,
    BlockSize => 10240) 
    or die "Cannot connect to FTP server at $config{'ftp_server'}: $!";
  $global::ftp->login(
    $config{'ftp_user'},
    $config{'ftp_password'},
    ) 
    or die "Cannot login to FTP server as $config{'ftp_user'}: " 
      . $global::ftp->message();
  }

sub DisconnectFTP () {
  if ($global::ftp) {
    $global::ftp->quit();
    $global::ftp = undef;
    }
  }

sub Initialise () {
  for my $event (@{$config{'event_paths'}}) {
    $config{'event_data'}{$event}{'htmldir'} = 'html'; # default value
    my $config_fn = File::Spec->catfile($event, 'config.tsh');
    open my $fh, "<$config_fn" or die "Can't open $config_fn: $!";
    while (<$fh>) {
      if (/^division\s+\S+\s+(\S+)/) {
	push(@{$config{'event_data'}{$event}{'tfiles'}}, $1);
        }
      elsif (/^config\s+html_directory\s*=\s*(.*)/) {
	die "this doesn't work yet";
	my $path = $1;
	$path =~ s/\s+$?//;
	$path =~ s/^'(.*)';?$/$1/ || $path =~ s/^"(.*)"$/$1/;
	$config{'event_data'}{$event}{'htmldir'} = $path;
        }
      }
    }
  }

sub Main () {
  my $lastchanged = 0;
  Initialise;
  print "Sleeping.\n";
  while (1) {
    my $changed = UpdateTFiles;
    $changed = 1 if UpdateConfigFiles;
    $changed = 1 if UpdateHTMLFiles;
    if ($changed) {
      print "Updated, rechecking.\n";
      sleep 1;
      $lastchanged = 1;
      next;
      }
#   print "Not updated.\n";
    if ($lastchanged) {
      print "Sleeping.\n";
      $lastchanged = 0;
      }
    DisconnectFTP;
    sleep 10;
    }
  }

sub MarkUpdated ($$) {
  my $flagfile = shift;
  my $localfile = shift;
  
  my ($volume, $directory, $file) = File::Spec->splitpath($flagfile);
  my $dirpath = File::Spec->catpath($volume, $directory, '');
# warn $dirpath;
  mkpath $dirpath;
  if (open my $fh, ">$flagfile") {
    my $mtime = (stat $localfile)[9];
    close $fh;
    unless (utime $mtime, $mtime, $flagfile) {
      warn "utime failed: $!";
      return 0;
      }
    warn "Updated: $localfile ($flagfile, ".scalar(localtime($mtime)).")\n";
    return 1;
    }
  else {
    warn "Can't update flag file $flagfile: $!";
    return 0;
    }
  }

sub NeedsUpdating ($$) {
  my $flagfile = shift;
  my $localfile = shift;

  my $needs = (!-f $flagfile) || -M $flagfile > -M $localfile;
# warn "NU: $localfile does " . ($needs ? '' : 'not ') . "need to be changed.";
  return $needs;
  }

sub PutFile ($$$) {
  my $flagfile = shift;
  my $localfile = shift;
  my $remotefile = shift;
  
  my $tries = 1;
  my $tried_making_path = 0;
  my $remote_tmp = $remotefile;
  $remote_tmp =~ s/([^\/]+)+$/tsh.tmp/ 
    or die "$remotefile has no trailing file component";
  while ($tries <= 10) {
    if (my $rv = $global::ftp->put($localfile, $remote_tmp)) {
      $rv = $global::ftp->rename($remote_tmp, $remotefile);
      if ($rv) { 
	return MarkUpdated $flagfile, $localfile;
        }
      else { 
	my $code = $global::ftp->code();
	warn "Received code $code renaming $remote_tmp to $remotefile";
	return 0;
	}
      }
    my $code = $global'ftp->code();
    if (($code == 550 || $code == 553) && !$tried_making_path) { # bad path
      $tried_making_path++;
      $remotefile =~ s/^\/{2,}/\//;
      my @path_components = split(m!/!, $remotefile);
      pop @path_components;
#     while ($path_components[0] eq '') { shift @path_components; }
      for my $i (0..$#path_components) {
	my $prefix = join('/', @path_components[0..$i]);
	CreateRemoteDirectory $prefix;
	}
      next;
      }
    elsif ($code == 426) { # connection reset by peer
      warn "Got a code 426 on try $tries during PUT $localfile $remotefile.\n";
      DisconnectFTP;
      sleep 5;
      ConnectFTP;
      }
# Should try reconnecting in this case
#    elsif ($code == 150) { # Opening connection - often means connection dropped
#      print ERROR "Got a code 150 on try $tries during PUT $origfile $vfile.\n";
#      sleep 6;
#      }
    else {
      warn "Unexpected error ($code) during PUT $localfile $remotefile: " . $global::ftp->message() . "\n";
      DisconnectFTP;
      return 0;
      }
    }
  continue { $tries++; }
  warn "Too many retries for PUT $localfile $remotefile.\n"; 
  return 0;
  }

sub UpdateBinary ($$$) {
  my $flagfile = shift;
  my $localfile = shift;
  my $remotefile = shift;
  
  my $updated = 0;
# warn "UB1: $localfile";
  if (NeedsUpdating($flagfile, $localfile)) {
    ConnectFTP;
    $global::ftp->binary();
#   warn "UB2: $localfile";
    $updated = PutFile $flagfile, $localfile, $remotefile;
    }
  return $updated;
  }

=item UpdateConfigFiles;

Check all .t files to see if they need to be updated.
Return true if any files were successfully updated.

=cut

sub UpdateConfigFiles () {
  my $updated = 0;
  while (my ($en, $ep) = each %{$config{'event_data'}}) {
    mkdir "flags/$en";
    for my $tfn (@{$ep->{'tfiles'}}) {
      my $u = UpdateText(
	File::Spec->catfile('flags', $en, $tfn),
	File::Spec->catfile($en, $tfn),
	"$config{'ftp_path'}/$en/$tfn",
        );
      $updated ||= $u;
      }
    }
  return $updated;
  }
sub UpdateDir ($$$) {
  my $flagdir = shift;
  my $localdir = shift;
  my $remotedir = shift;
  
  my $updated = 0;
# warn "UpdateDir($flagdir,$localdir,$remotedir)";
  opendir(my $dh, $localdir) or confess "Can't opendir($localdir): $!";
  for my $file (readdir($dh)) {
    next if $file =~ /^\./;
    my $path = File::Spec->catfile($localdir, $file);
#   warn $path;
    if (-f $path) {
      if ($file =~ /\.(?:css|html?|t|txt)$/) {
#     warn "...text";
	my $u = UpdateText(
	  File::Spec->catfile($flagdir, $file),
	  $path, 
	  "$remotedir/$file"
	  );
	$updated ||= $u;
        }
      else {
#     warn "UD: $path is binary";
	my $u = UpdateBinary(
	  File::Spec->catfile($flagdir, $file),
	  $path, 
	  "$remotedir/$file"
	  );
	$updated ||= $u;
        }
      }
    elsif (-d $path) {
#     warn "...directory";
      my $u = UpdateDir(
	File::Spec->catfile($flagdir, $file),
	$path, 
	"$remotedir/$file"
	);
      $updated ||= $u;
      }
    else {
      warn "Skipping: $path";
      }
    }
  closedir($dh);
  return $updated;
  }

=item UpdateHTMLFiles;

Check all files in the HTML directory to see if they need to be updated.
Return true if any files were successfully updated.

=cut

sub UpdateHTMLFiles () {
  my $updated = 0;
  while (my ($en, $ep) = each %{$config{'event_data'}}) {
    my $u = UpdateDir(
      File::Spec->catdir('flags', $en, 'html'),
      File::Spec->catdir($en, $ep->{'htmldir'}),
      "$config{'ftp_path'}/$en/html"
      );
    $updated ||= $u;
    }
  return $updated;
  }

sub UpdateText ($$$) {
  my $flagfile = shift;
  my $localfile = shift;
  my $remotefile = shift;
  
  my $updated = 0;
  if (NeedsUpdating($flagfile, $localfile)) {
    ConnectFTP;
#   $global::ftp->ascii();
    $global::ftp->binary();
    $updated = PutFile $flagfile, $localfile, $remotefile;
    }
  return $updated;
  }

=item UpdateTFiles;

Check all .t files to see if they need to be updated.
Return true if any files were successfully updated.

=cut

sub UpdateTFiles () {
  my $updated = 0;
  while (my ($en, $ep) = each %{$config{'event_data'}}) {
    mkdir "flags/$en";
    my $u = UpdateText(
      File::Spec->catfile('flags', $en, 'config.tsh'),
      File::Spec->catfile($en, 'config.tsh'),
      "$config{'ftp_path'}/$en/config.tsh",
      );
    $updated ||= $u;
    }
  return $updated;
  }
