#!/usr/bin/perl -w

use strict;

use IO::Select;
use IO::Socket;

use Astro::Time;
use RtFC;

sub process_UI_command ($$);
sub process_obs_command ($$$$);
sub process_corr_command ($);
sub Initialize ();
sub Shutdown ();
sub doPing ($\@);

#################################################
# Main program
#################################################

$SIG{INT} = \&Shutdown;

my ($sock_UI, $sock_obs) = Initialize();
my $sock_corr = undef;

# Loops for ever, waiting for data on the various sockets. We might
# also want to run periodic commands

my $sel = new IO::Select($sock_UI, $sock_obs);

my @ready;
my ($buf);

# List of active user interfaces
my @UIs = ();

# Lists of observatory OBJECTS which are currently connected
my @Obs = ();


# Set the xterm titlebar
print "\033]0;RtFC control.pl\007";

$| = 1;  # Flush stdout

my $slept = 0;
my $count = 0;
while(1) {
  @ready = $sel->can_read(60);
  if (!@ready) {
    $count++;
    if ($count>=10) {
      print '.';
      $slept = 1;
      $count = 0;
    }
    doPing($sock_corr, @Obs);
    next;
  }
  if ($slept) {
    print "\n";
    $slept = 0;
  }
  foreach my $fh (@ready) {
    if($fh == $sock_UI) {
      # UI connection request
      # Create a new socket
      my $newUI = $sock_UI->accept();
      push @UIs, $newUI;
      $sel->add($newUI);
    } elsif($fh == $sock_obs) {
      # Observatory connection request
      print "Got a new observatory socket connection...\n";
      # Create a new socket
      my $newobs = $sock_obs->accept();
      push @Obs, new RtFC::ObsLink($newobs);
      $sel->add($newobs);
    } elsif (defined $sock_corr && $fh == $sock_corr) {
      my $command = recv_command($sock_corr,$sel,'correlator');

      if ($command) {
	process_corr_command($command);
      } else {
	if (! defined $command) { # Remote socket closed
	  $sock_corr = undef;
	}
      }
    } else {
      # Must have data from one of the client sockets...
      my @killedsock = ();
      my $i=0;
      foreach my $ui (@UIs) {
	if ($fh==$ui) { # Yep, this is a UI socket with some data

	  my $command = recv_command($ui, $sel,'ui', $i, \@killedsock);

	  if (defined $command) {
	    process_UI_command($command, $ui);
	  } else {
	    # We can ignore these errors at the moment
	  }
	}
	$i++;
      }
      # Remove any UI sockets which were closed
      foreach (@killedsock) {
	splice @UIs, $_, 1; # Remove the i'th element
      }

      @killedsock = ();
      $i=0;
      foreach my $obs (@Obs) {
	my $sock = $obs->socket;
	if ($fh==$sock) { # Yep, this is a obs socket with some data

	  my $command = recv_command($sock,$sel,'observatory',$i, \@killedsock);

	  if ($command) {
	    process_obs_command($obs, $command, $i, \@killedsock);
	  } else {
	    # We can ignore these errors at the moment
	  }
	}
	$i++;
      }
      # Remove any UI sockets which were closed
      foreach (@killedsock) {
	splice @Obs, $_, 1; # Remove the i'th element
      }
    }
  }
}

sub Initialize () {
  # Create a listening socket for the UI (user interface) control
  # clients. No reason not to allow more than one of these.
  # Also create a listen socket for the observatory process
  # Must handle "lots" of these

  my $sock_UI = IO::Socket::INET->new(LocalPort => UIPORT,
				      Listen    => 10,
				      ReuseAddr => 1)
    || die "Couldn't create server socket for UI: $@\n";


  my $sock_obs = IO::Socket::INET->new(LocalPort => OBSPORT,
				       Listen    => 10,
				       ReuseAddr => 1)
    || die "Couldn't create server socket for observatories: $@\n";

  return ($sock_UI, $sock_obs);
}

sub process_corr_command ($) {
  my $command = shift;

  if ($command->type==OBSGRAB_COMMAND) {
    # Send to appropriate observatory process - find which is it
    my $sent = 0;
    foreach (@Obs) {
	if (lc($_->code) eq lc($command->antid) &&
	    $_->datastream eq $command->datastream) {
	my $name = $_->name;
	my $status = $command->send($_->socket, $name);
	if (!defined $status) { # Ignore other error states for the moment
	  warn "Failed to send Obsgrab command to $name\n";
	}
	$sent = 1;
	last;
      }
    }
    if (!$sent) {
      printf("Connection to ". $command->antid." must have closed. Cannot send Obsgrab command\n");
    }

  } else {
    my $cmdtype = $command->name;
    warn "Got unexpected $cmdtype command from Correlator process\n";
  }
}

sub process_UI_command ($$) {
  my $command = shift;
  my $ui = shift;

  my $name = $command->name;
  my $type = $command->type;

  if ($command->type==CORRGRAB_COMMAND) {
    # Send to onto correlator and create grab command for observatories
    # if grab not recorr

    if (defined $sock_corr) { # No point sending if no corr process connected

      my @connected_obs = ();
      foreach my $obs (@Obs) {
	next if (! $obs->enabled);
	push @connected_obs, $obs->code . $obs->datastream;  # Concatenate datastream ID to keep track
      }

      if (scalar(@connected_obs)==0) {
	warn "No observatories connected. Skipping\n";
	return;
      }

      $command->observatories(\@connected_obs);

      my $status = $command->send($sock_corr, 'Correlator');

      if (!defined $status) { # Ignore other error states for the moment
	Shutdown();
      }

      # if ($command->newcorr) {
      # 	my $grabcommand = new RtFC::Command::ObsGrab($command->code, 
      # 						     $command->mjd,
      # 						     $command->integration,
      # 						     0, 0
      # 						    );
      # 	foreach my $obs (@Obs) {
      # 	  next if (! $obs->enabled);

      # 	  if (exists $command->secoffsets->{lc($obs->code)}) {
      # 	    $grabcommand->secoffset($command->secoffsets->{lc($obs->code)});
      # 	  } else {
      # 	    $grabcommand->secoffset(0);
      # 	  }
      # 	  if (exists $command->channels->{lc($obs->code)}) {
      # 	    $grabcommand->channels($command->channels->{lc($obs->code)});
      # 	  } else {
      # 	    $grabcommand->channels(0);
      # 	  }

      # 	  my $status = $grabcommand->send($obs->socket, $obs->name);

      # 	  if (!defined $status) { # Ignore other error states for the moment
      # 	    Shutdown();
      # 	  }
      # 	}
      #      }
    } else {
      warn "Correlator not connected. Ignoring GRAB command\n";
    }

  } elsif ($command->type==SHOWOBS_COMMAND) {

    my @obssum = ();
    if (@Obs) {
      foreach my $obs (@Obs) {
	my $enabled;
	if ($obs->enabled) {
	  $enabled = 1;
	} else {
	  $enabled = 0;
	}
	push @obssum, [$obs->code, $obs->name, $enabled];
      }
    }

    my $response = new RtFC::Command::Obscon(@obssum);

    my $status = $response->send($ui, 'User interface');

    if (!defined $status) { # Ignore other error states for the moment
      Shutdown();
    }

  } elsif ($command->type==DISABLEOBS_COMMAND) {
    my $id = $command->antenna_id;

    my $found = 0;
    foreach my $obs (@Obs) {
      if (lc($obs->code) eq lc($id)) {
	$found=1;
	$obs->disable;
	printf("\n Disabled %s\n", $obs->name);
      }
    }
    if (!$found) {
      print "\n Not connected to $id\n";
    }

  } elsif ($command->type==ENABLEOBS_COMMAND) {
    my $id = $command->antenna_id;

    my $found = 0;
    foreach my $obs (@Obs) {
      if (lc($obs->code) eq lc($id)) {
	$found=1;
	$obs->enable(1);
	printf("\n Enabled %s\n", $obs->name);
      }
    }
    if (!$found) {
      print "\n Not connected to $id\n";
    }

  } else {
    printf STDERR "Unknown command (%d) received\n", $command->type;
  }
}

sub process_obs_command ($$$$) {
  my ($obs, $command, $i, $killedsock) = @_;

  if ($command->type==ANTENNA_CONNECT_COMMAND) {
    if ($command->antenna_name eq 'CORRELATOR') {
      # This is not an observatory, but the correlator process
      if (defined $sock_corr) {
	warn "Multiple connection requests from correlator process\n";
	warn "Connection denied\n";
	my $cmd = new RtFC::Command::Error(DUPLICATE_CONNECTION);
	my $status = $cmd->send($obs->socket, 'Correlator');
	# Can safely ignore error messages

	# Now need to remove this connection
	$sel->remove($obs->socket);
	$obs->socket->shutdown(2);
	unshift @{$killedsock}, $i; # Store largest index first
      } else {
	# New correlator connection register and remove from @Obs list
	$sock_corr = $obs->socket;
	unshift @{$killedsock}, $i; # Store largest index first
	print "Registered correlator connection\n";
      }
    } else {
      printf "Connection is from %s\n", $command->antenna_name;
      printf "  DEBUG  Datastream %d\n", $command->datastream;
      $obs->name($command->antenna_name);
      $obs->code($command->antenna_id);
      $obs->datastream($command->datastream);
    }
  } else {
    printf STDERR "Unknown command (%d) received\n", $command->type;
  }
}

sub Shutdown () {

  warn sprintf("Got %d UIs to close\n", scalar @UIs);
  foreach my $ui (@UIs) {
    $ui->shutdown(2);
  }

  warn sprintf("Got %d Obs to close\n", scalar @Obs);
  foreach my $obs (@Obs) {
    $obs->socket->shutdown(2);
  }

  $sock_UI->shutdown(2) if (defined $sock_UI);
  $sock_obs->shutdown(2) if (defined $sock_obs);
  $sock_corr->shutdown(2) if (defined $sock_corr);

  exit(0);

}

sub doPing ($\@) {
  my ($corr, $obs) = @_;
  my $status;
  my $ping = new RtFC::Command::Ping;
#  warn "doPing\n";

  if (defined $corr) {
    $status = $ping->send($corr, 'Ping');
    if (!$status) {
      print "Error sending Ping to corr process\n";
    }
  }
#  foreach my $u (@$ui) {
#    $status = $ping->send($u, 'Ping');
#    if (!$status) {
#      print "Error sending Ping to UI process\n";
#    }
#  }

  foreach my $o (@$obs) {
    $status = $ping->send($o->socket, 'Ping');
    if (!$status) {
      print "Error sending Ping to Obs process\n";
    }
  }
}

END {
#  warn "We got to the very end\n";

  foreach my $ui (@UIs) {
    $ui->shutdown(2) if (defined $ui);
  }

  foreach my $obs (@Obs) {
    $obs->socket->shutdown(2) if (defined $obs);
  }

  $sock_UI->shutdown(2) if (defined $sock_UI);
  $sock_obs->shutdown(2) if (defined $sock_obs);
  $sock_corr->shutdown(2) if (defined $sock_corr);
}
