#!/usr/bin/perl -w

use strict;

use IO::Socket;
#use IO::Select;
use Term::ReadLine;
use Getopt::Long;

use Astro::Time;
use CLI;

use RtFC;

sub Shutdown ();

$SIG{INT} = \&Shutdown;

my $config_file = undef;

my $control_host = defined($ENV{RTFC_CONTROL}) ?
                                   $ENV{RTFC_CONTROL}: 'localhost';

if (defined $ENV{RTFC_CONFIG}) {
  $config_file = $ENV{RTFC_CONFIG};
} elsif (-f "~/.rtfc_config") {
  $config_file = "~/.rtfc_config";
}

# Hash containing various antenna based values
my %offsets = ();
my %clockrate = ();
my %secoffsets = ();
my %channels = ();

GetOptions('control=s'=>\$control_host, 'config=s'=>\$config_file);

# Tweak the terminal...
my $term = new Term::ReadLine 'RtFC ui';

my $features = $term->Features;

$term->ornaments('md,me,,') if ($$features{ornaments});

my $sock_UI = IO::Socket::INET->new(PeerAddr  => $control_host,
				    PeerPort  => UIPORT,
				    ReuseAddr => 1
				    )
  || die "Couldn't create local socket: $@\n";

my $cli = new CLI;

my $default = sub ($$$) {
  my ($line, $key, $value) = @_;
  warn " Unknown command $key\n";
};

my ($integration, $year, $month, $day, $dayno, $mode, $npoint, $pack, 
    $wait, $past, $vex, $postfix);

# CLI commands. Makes extensive use of global variables

# Grab some data from all connected telescopes
sub dograb ($$) {
  my ($value, $newcorr) = @_;

  my $ut = str2turn($value, 'H');
  my $grab_mjd = cal2mjd($day, $month, $year, $ut);

  # Send corr or re-corr status
  
  if ($newcorr) {
    # Is the grab for the future?
    my $current_time = now2mjd();

    my $offset = ($grab_mjd-$current_time)*60*60*24;

    my $hms  = turn2str($ut, 'h', 0);
    if ($offset >-$wait ) {
      # Need to wait
      $offset  += $wait;

      printf("Waiting to %.1f sec to grab data ($hms)\n", $offset);

      sleep($offset);
    } elsif (!$past) {
      printf "Skipping $hms\n";
      return;
    }
  }

  my $command = new RtFC::Command::Corrgrab(time, $newcorr, $grab_mjd,
					    $integration, $npoint,
					    $pack, $vex, $postfix, \%offsets, \%clockrate,
					    \%secoffsets, \%channels);

  my $status = $command->send($sock_UI, 'control process');
  if (!$status) {
    print "Error sending message to control process!\n";
    return;
  }
};

my $grab = sub ($) {
  my $value = shift;
  if (!defined $value) {
    print "Usage: grab hh:mm:ss\n";
    return;
  }

  dograb($value, 1);
};

my $readfile = sub ($) {
  my $filename = shift;

  $filename =~ s/\s*$//;  # Remove trailing spaces

  if (!defined $filename) {
    print "Usage: readfile <filename>\n";
    return;
  }

  if (! -e $filename) {
    print "$filename does not exist\n";
    return;
  }
  if (! -f $filename) {
    print "$filename is not a plain file\n";
    return;
  }
  if (! -r $filename) {
    print "$filename is not readable\n";
    return;
  }

  $cli->restore_config($filename);
};

my $recorr = sub ($) {
  my $value = shift;
  if (!defined $value) {
    print "Usage: recorr hh:mm:ss\n";
    return;
  }

  dograb($value, 0);
};

# Show which telescopes are connected currently
my $showobs = sub () {
  my $command = new RtFC::Command::Showobs;

  my $status = $command->send($sock_UI, 'control process');
  if (!$status) {
    warn "Error sending OBS command";
  } else {
    # Read response - block until it arrives as we are running
    # interactively

    $command = recv_command($sock_UI,undef,'correlator');

    if (defined $command) {
      my $connected;
      my @obs = @{$command->value};

      if (@obs) {
	
	my $maxlength = 0;
	foreach (@obs) {
	  if (length($_->[1]) > $maxlength) {
	    $maxlength = length($_->[1]);
	  }
	}
	my $format = sprintf "  %%2s %%-%ds  %%s\n\n", $maxlength;
	
	print "\nConnected obs:\n\n";
	foreach (@obs) {
	  if ($_->[2]) {
	    $connected = 'Enabled';
	  } else {
	    $connected = 'Disabled';
	  }
	  printf($format, $_->[0], $_->[1], $connected);
	}
      } else {
	print "\nNo observatories connected\n\n";
      }
    } else {
      warn "Error receiving response from OBS command";
    }

  }

};

# Disable a connected telescope
my $disableobs = sub ($) {
  my $value = shift;

  my @ants = split '\s+', $value;
  foreach (@ants) {
    my $command = new RtFC::Command::Disableobs($_);
    my $status = $command->send($sock_UI, 'control process');
    # Ignore return status for now
  }
};

# Re-enable a connected telescope
my $enableobs = sub ($) {
  my $value = shift;

  my @ants = split '\s+', $value;
  foreach (@ants) {
    my $command = new RtFC::Command::Enableobs($_);
    my $status = $command->send($sock_UI, 'control process');
    # Ignore return status for now
  }
};

# Set antenna offset
my $obsoffset = sub ($) {
  my $values = shift;
  if (defined $values) {
    my @values = split /\s+/, $values;

    if (scalar(@values)>2) { # Too many arguments
      warn "Too many arguments (>2) for offset command\n";
    } elsif (@values==2) {    # Set offset
      my $ant = $values[0];
      my $value = $values[1];

      if ($value eq 'unset') {
	if (exists $offsets{lc($ant)}) {
	  delete $offsets{lc($ant)};
	} else {
	  warn "No offsets set for $ant\n";
	}
      } else {
	$offsets{lc($ant)} = $value; # Should check value
      }
    } else { # Must have one value - show value
      my $ant = $values[0];
      if (exists $offsets{lc($ant)}) {
	print "$ant = $offsets{lc($ant)}\n";
      } else {
	print "No offset for $ant\n";
      }
    }
  } else {
    if (%offsets) {
      while (my ($key, $value) = each %offsets) {
	print "$key = $value\n";
      }
    } else {
      print "No offsets recorded\n";
    }
  }
};
	
# Set antenna integral second offset
my $obssecoffset = sub ($) {
  my $values = shift;
  if (defined $values) {
    my @values = split /\s+/, $values;

    if (scalar(@values)>2) { # Too many arguments
      warn "Too many arguments (>2) for offset command\n";
    } elsif (@values==2) {   # Set secoffset
      my $ant = $values[0];
      my $value = $values[1];

      if ($value<-127 || $value > 127) {
	warn "$value is out of range (-127 -- 127) for second offsets\n";
      } else {
	if ($value eq 'unset') {
	  if (exists $secoffsets{lc($ant)}) {
	    delete $secoffsets{lc($ant)};
	  } else {
	    warn "No secoffsets set for $ant\n";
	  }
	} else {
	  $secoffsets{lc($ant)} = $value; # Should check value
        }
      }
    } else { # Must have one value
      my $ant = $values[0];
      if (exists $secoffsets{lc($ant)}) {
	print "$ant = $secoffsets{lc($ant)} sec\n";
      } else {
	print "No offset for $ant\n";
      }
    }
  } else {
    if (%secoffsets) {
      while (my ($key, $value) = each %secoffsets) {
	print "$key = $value\n";
      }
    } else {
      print "No offsets recorded\n";
    }
  }
};

# Set antenna clock rate
my $clockrate = sub ($) {
  my $values = shift;
  if (defined $values) {
    my @values = split /\s+/, $values;

    if (scalar(@values)>2) { # Too many arguments
      warn "Too many arguments (>2) for clockrate command\n";
    } elsif (@values==2) {   # Set antena rate
      my $ant = $values[0];
      my $value = $values[1];

      if ($value eq 'unset') {
	if (exists $clockrate{lc($ant)}) {
	  delete $clockrate{lc($ant)};
	} else {
	  warn "No rates set for $ant\n";
	}
      } else {
	$clockrate{lc($ant)} = $value; # Should check value
      }

    } else { # Must have one value - show value
      my $ant = $values[0];
      if (exists $clockrate{lc($ant)}) {
	print "$ant = $clockrate{lc($ant)}\n";
      } else {
	print "No rates for $ant\n";
      }
    }
  } else {
    if (%clockrate) {
      while (my ($key, $value) = each %clockrate) {
	print "$key = $value\n";
      }
    } else {
      print "No clockrates recorded\n";
    }
  }
};

# Individual station channel selection
my $channels = sub ($) {
  my $values = shift;
  if (defined $values) {
    my @values = split /\s+/, $values;
    my $ant = shift @values;

    if (scalar(@values)==0) {
      if (exists $channels{lc($ant)}) {
	my @chans = mask2chan($channels{lc($ant)});
	print "$ant: @chans\n";
      } else {
	print "No channel range set for $ant\n";
      }
    } elsif ($values[0] eq 'unset') {
	if (exists $channels{lc($ant)}) {
	  delete $channels{lc($ant)};
	} else {
	  warn "No channels set for $ant\n";
	}
    } else {
      foreach (@values) {
	if (!/^\d+$/) {
	  warn "Invalid value $_\n";
	  return;
	}
	if ($_<1 || $_>MAXCHANNELS) {
	  warn sprintf("$_ is out of possible channel range (1-%d)\n",
		       MAXCHANNELS);
	  return;
	}
      }
      my $chans = 0;
      foreach (@values) {
	$chans |= 1<<($_-1);
      }
      $channels{lc($ant)}= $chans;
    }
  } else {
    if (%channels) {
      while (my ($key, $value) = each %channels) {
	my @chans = mask2chan($value);
	print "$key: @chans\n";
      }
    } else {
      print "No station channels set\n";
    }
  }
};

# Functions needed by CLI variables
my $new_dayno = sub {
  my $dayno = shift;
  ($day, $month) = dayno2cal($dayno, $year);
};

my $new_month = sub {
  my $month = shift;
  $dayno = cal2dayno($day, $month, $year);
};

my $new_day = sub {
  my $day = shift;
  $dayno = cal2dayno($day, $month, $year);
};

my $check_pol = sub {
  my $pol = shift;
  if ($pol eq 'Lcp') {
    $_[0] =LCP;
  } elsif ($pol eq 'Rcp') {
    $_[0] =RCP;
  }
};


my $quit = 0;
$cli->add(COMMAND, 'Quit', sub {$quit=1});
$cli->add(COMMAND, 'Exit', sub {$quit=1});
$cli->add(COMMAND, 'Bye', sub {$quit=1});
$cli->add(COMMAND, 'Grab', $grab);
$cli->add(COMMAND, 'Recorr', $recorr);
$cli->add(COMMAND, 'Observatories', $showobs);
$cli->add(COMMAND, 'Disable', $disableobs);
$cli->add(COMMAND, 'Enable', $enableobs);
$cli->add(COMMAND, 'Offset', $obsoffset);
$cli->add(COMMAND, 'Secoffset', $obssecoffset);
$cli->add(COMMAND, 'Rate', $clockrate);
$cli->add(COMMAND, 'channels', $channels);
$cli->add(COMMAND, 'Readfile', $readfile);

my $dummy;
($dummy, $dummy, $dummy, $day, $month, $year) = gmtime();
$month++;
$year+=1900;

$cli->add(VAR, 'Year', \$year, INTEGER, $year);
$cli->add(VAR, 'Dayno', \$dayno, INTEGER, 1, {min      => 1,
					      max      => 366,
					      function => $new_dayno});
$cli->add(VAR, 'Day', \$day, INTEGER, $day, {function => $new_day,
					  min      => 1,
					  max      => 31});
$cli->add(VAR, 'Month', \$month, INTEGER, $month, {function => $new_month,
					      min      => 1,
					      max      => 12});

# Force a recalc of the dayno
&$new_day($day);

$cli->add(VAR, 'Integration', \$integration, FLOAT, 1.0, {min => 0.0});

$cli->add(VAR, 'Pack', \$pack, INTEGER, 0);

$cli->add(VAR, 'nchan', \$npoint, INTEGER, 256, {min => 0,
						max => 1000000});

$cli->add(VAR, 'Wait', \$wait, INTEGER, 5, {min => 0,
					    max => 65535});

$cli->add(VAR, 'Past', \$past, BOOLEAN, 1);

$cli->add(VAR, 'vex', \$vex, STRING, '');
$cli->add(VAR, 'postfix', \$postfix, STRING, '');

$cli->default($default);

# Read the config file
if (defined $config_file) {
  if (-f $config_file) {
    $cli->restore_config($config_file);
  } else {
    warn "Config file $config_file does not exists!\n";
  }
}

## Setup alarm callback
#my $sel = undef;
#my $receiveMessages = sub {
#  if (! defined $sel) {
#    $sel = new IO::Select($sock_UI);
#  }
#
#  warn "\nDEBUG: Is anything there?\n";
#  my @ready = $sel->can_read(0);
#  if (@ready) {
#    warn "   YES!\n";
#    my $command = recv_command($sock_UI,undef,'ui');
#    if (defined $command) {
#      warn "  Got " . $command->name . " command\n";
#    } else {
#      warn "Error receiving command\n";
#    }
#  } else {
#    warn "   NO\n";
#  }
#};

#$SIG{ALRM} = $receiveMessages;
#alarm 20;

# Set the xterm title bar
print "\033]0;RtFC ui.pl\007";

while (! $quit) {
  my $line = $term->readline('> ');
  next if ! defined $line;

  $cli->parse($line);
}

sub Shutdown () {
  warn "Closing sockets\n";

  $sock_UI->shutdown(2) if (defined $sock_UI);
  $sock_UI = undef;
  exit;
}



END {
  Shutdown();
}
