use IO::Socket;
use File::Basename;

use strict;
use warnings;

use Time::HiRes qw(usleep);

my $scriptInfo = "2021-04-28 sr";

my $dummy; 
my $resp = "";
my $answer = "";

# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# remarks and definitions
# ---------------------------------------------------------------------------
# 
# call this script using 
# perl LicelMainBasic.pl <ip_address> <port>
# - ip_address is the IP address of the PC running Licel Main
# - port is set in the ini file Licel Main.ini (see below)
#
# this script will work if the file Licel Main.ini is prepared as follows 
# the script will write a log file 
#
# [TCPIP_API]
# Active = TRUE
# Port = 2058
# ...
# [Module0]
# Active = TRUE
# Path = Control APD-PMT.vi
# Name = Detectors
# Controllers = 0
# CAPs = PMT,APD
# Push = FALSE
# TCPIP_API = TRUE
# ...
# [Module1]
# Active = TRUE
# Path = Control Timing.vi
# Name = "Timing Control"
# Controllers = 0
# CAPs = TIMER
# Push = FALSE
# TCPIP_API = FALSE
# ...
# [Module5]
# Active = TRUE
# Path = TCPIP Acquis.vi
# Name = Acquisition
# Controllers = 0
# CAPs = TR
# Push = FALSE
# TCPIP_API = TRUE

# (visible!) Tab indices 
# depends on Licel Main.ini and the visible tabs in Licel Main 
my $licelMainTab = 7; 
my $pmtTab = 0;  
my $timingTab = 1;  
my $acqTab = 5;  

# there are sub-tabs in control apd-pmt, we need the pmt tab! 
my $pmtInternalTab = 0;

# there are sub-tabs in control timing, we need the Acquisition Timing tab! 
my $timingInternalTab = 2;

# target shots for the acquisitions are defineable: 
my $targetShots = 500;  

# target records (number of acqusitions (files) are defineable: 
# $targetRecords = 0: never stop (manual, only)
my $targetRecords = 1;  

# define dataset info for analog and photon counting at 4 tzrtansient recorders 
# DATASET <trAddress> <memory> <mode> <active> <noBins> <reduction> <wavelength> [Laser]
my $submitDataset = "";
my @trDataset = ("0 A AN 1 16380 0 532", 
                 "0 A PC 1 16380 0 532",
                 "1 A AN 1 16380 0 405",
                 "1 A PC 1 16380 0 405",
                 "2 A AN 1 16380 0 1064",
                 "2 A PC 1 16380 0 1064",
                 "3 A AN 1 16380 0 566",
				 "3 A PC 1 16380 0 566"); 

# let us add some custum info 
# CUSTOMINFO <trAddress> <memory> <mode> <"customInfo">
my $submitCustomInfo = "";
my @trCustomInfo = ("0 A AN \"this is my channel 1\"", 
                 "0 A PC \"this is my channel 2\"",
                 "1 A AN \"this is my channel 3\"",
                 "1 A PC \"this is my channel 4\"",
                 "2 A AN \"this is my channel 5\"",
                 "2 A PC \"this is my channel 6\"",
                 "3 A AN \"this is my channel 7\"",
				 "3 A PC \"this is my channel 8\"");

# frequency to estimate a timeout value and for submission to Control Timing
my $freq = 100; 

# other timing values 
my $repetitionRate = 100; 
my $lamp2Acquisition = 200; 
my $acquisitionLength = 10; 
my $acqusition2QSwitch = 100;
my $qSwitchLength = 300; 
my $startDelay = 50; 

# pmt number, indices, set voltages 
my $pmtNum = 4; 
my @pmtIndex = (0, 1, 2, 3); 
my @pmtVoltage = (770.0, 780.0, 790.0, 800.0); 

# sleep settings (wait time between steps to beeter see what happens)
my $sleepActive = 1;
my $sleepWaitSec = 1.0;   
  
  
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------

# ---------------------------------------------------------------------------
# log file
# ---------------------------------------------------------------------------
my $thisSkript = basename($0);
my @fileTokens = split('\.', $thisSkript);
my $filename = $fileTokens[0] . ".log";
my $myFile = undef;

open($myFile, '>' . $filename);
sub lprint {
  my ($pr) = @_;
  print $myFile $pr;
  print $pr;
}
lprint "*** Run skript " . $thisSkript . " [" . $scriptInfo . "]\n\n";

# ---------------------------------------------------------------------------
# call lprint when die
# ---------------------------------------------------------------------------
local $SIG{__DIE__} = sub {lprint $_[0] . "*** " . $thisSkript . " was stopped\n"};

my $numArgs = @ARGV;
if ($numArgs < 2) {
  die "Too few arguments, call skript using\n" . $thisSkript . " <IP> <Port>";
}

my $oldIP = $ARGV[0];
my $oldPort = $ARGV[1];

# ---------------------------------------------------------------------------
# tcp/ip send
# ---------------------------------------------------------------------------

sub sendCommand {
  my ($socket, $command) = @_;
  lprint $command . "\r\n";
  print $socket $command ."\r\n";
  $dummy =  <$socket>;
  lprint $dummy;
  $dummy;
}

# ------------------------------------------------------
# switch all HV 
# ------------------------------------------------------

sub switchHV {
  my $sock = $_[0]; 
  my $num = $_[1]; 
  my @index = @{$_[2]};
  my $active = $_[3]; 
  for (my $i=0; $i < $num; $i++) {
    $resp = sendCommand($sock, "PMT: HV " . $index[$i] . " " . $active);
    $resp = sendCommand($sock, "PMT: READ " . $index[$i]);
    $resp = sendCommand($sock, "PMT: HV? " . $index[$i]);
  }  
}

# ------------------------------------------------------
# status 
# ------------------------------------------------------

sub getStatusValue { 
  my ($sock, $search) = @_;
  my $fToken = ""; 
  my $resp = sendCommand($sock, "STATUS?");
  my @token = split(' ', $resp);
  my $numToks = @token;
  my $numIt = $numToks-1;
  my $returnVal = -1;
  # 1st token is my search token 
  # 2nd token is 'STATUS' 
  my $found = -1; 
  my $i = 0; 
  my @idx = (1..$numIt); 
  for $i (@idx) {
    if  (($found = index("$token[$i]", $search)) != -1) {
	  $fToken = $token[$i]; 
	  last; 
    }  
  }
  if ($found != -1) {
    @token = split('=', $fToken);
	$returnVal = $token[1]; 
  }
  $returnVal; 
}

sub waitTR_SetDone {
  my ($sock, $waitSeconds) = @_; 
  my $_TR_SetDone = -1; 
  my $starttime = time; 
  my $elapsed = 0; 
  while ($_TR_SetDone <= 0 and ($elapsed = time - $starttime) <= $waitSeconds) {
    $_TR_SetDone = getStatusValue($sock, "TR_SET"); 
	lprint $elapsed . "\n"; 
    sleep(1); 
  } 
  $elapsed; 
}

sub waitForRecords {
  my ($sock, $waitRecords, $waitSeconds) = @_; 
  my $records = 0; 
  my $starttime = time; 
  my $elapsed = 0; 
  while (($records = getStatusValue($sock, "RECORDS")) < $waitRecords and ((($elapsed = time - $starttime) <= $waitSeconds) or $waitSeconds < 0)) {
    lprint $records . "\n"; 
 	Time::HiRes::sleep(0.1);
  } 
  $records; 
}

sub waitForAutoRun {
  my ($sock, $waitAutoStatus, $waitSeconds) = @_; 
  my $autoStatus = -1; 
  my $starttime = time; 
  my $elapsed = 0; 
  while (($autoStatus = getStatusValue($sock, "AUTO")) != $waitAutoStatus and ((($elapsed = time - $starttime) <= $waitSeconds) or $waitSeconds < 0)) {
    lprint $autoStatus . "\n"; 
 	Time::HiRes::sleep(0.1);
  } 
  $autoStatus; 
}

sub sleepSec {
  my ($sleepActive, $sleepSeconds) = @_; 
  if ($sleepActive == 1) {
 	Time::HiRes::sleep($sleepSeconds);
  }
}

# ------------------------------------------------------
# timeout estimate
# ------------------------------------------------------

sub getAcqTimeout {
  my ($freq, $shots, $records) = @_; 
# take a factor of 2 
  my $seconds = 2 * $shots * $records / $freq; 
  $seconds; 
}

# ------------------------------------------------------
# start date, time
# ------------------------------------------------------

sub logDate{
  my ($msg) = @_;
  my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
  my $timestamp = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
  lprint $msg . $timestamp . "\n";
}

# time & date
  logDate("Start: ");
  my $starttime = time;
  
# ---------------------------------------------------------------------
# ---------------------------------------------------------------------
# go
# ---------------------------------------------------------------------
# ---------------------------------------------------------------------

  lprint "Connecting to: " . $oldIP . ":" . $oldPort . "\n";
  my $sock = new IO::Socket::INET (
  PeerAddr => $oldIP,
  PeerPort => $oldPort,
  Proto => 'tcp', );
  die "Could not create socket: $!" unless $sock;

  $resp = sendCommand($sock, "MAIN: TAB " . $licelMainTab);
  $resp = sendCommand($sock, "MAIN: VER?");
  $resp = sendCommand($sock, "MAIN: *IDN?");
  sleepSec($sleepActive, $sleepWaitSec);   
  
  lprint "\nSwitch to PMT tab\n"; 
  $resp = sendCommand($sock, "MAIN: TAB " . $pmtTab);
  $resp = sendCommand($sock, "PMT: VER?");
  sleepSec($sleepActive, $sleepWaitSec);   
  
  lprint "\nSwitch to the timing tab\n"; 
  $resp = sendCommand($sock, "MAIN: TAB " . $timingTab);
  $resp = sendCommand($sock, "TIMING: VER?");
  sleepSec($sleepActive, $sleepWaitSec);   

  lprint "\nSwitch to Acquis tab\n"; 
  $resp = sendCommand($sock, "MAIN: TAB " . $acqTab);
# this is the first tab switch to Acquis, the configuration dialog may pops up 
# if the acquis.ini settings does not match the identified transient recorders 
# we should wait until the TR parameters have been set, give the user 10 minutes time 
  my $waitForTR = 0; 
  my $waitForTR_Time = 10; 
  if ( ($waitForTR = waitTR_SetDone($sock, $waitForTR_Time)) > $waitForTR_Time ) {
    die "wait time for the TR configuration elapsed " . $waitForTR; 
  }
  $resp = sendCommand($sock, "VER?");
#  stop a running acquisition commented out
#  $resp = sendCommand($sock, "STOP");
  sleepSec($sleepActive, $sleepWaitSec);   

  lprint "\nSet PMT target voltage values\n"; 
  $resp = sendCommand($sock, "MAIN: TAB " . $pmtTab);
  $resp = sendCommand($sock, "PMT: TAB " . $pmtInternalTab);
  for (my $i=0; $i < $pmtNum; $i++) {
	$resp = sendCommand($sock, "PMT: HVSETTING " . $pmtIndex[$i] . " " . $pmtVoltage[$i]);
  } 
  sleepSec($sleepActive, $sleepWaitSec);   

# set trigger times 
  lprint "\nSwitch the trigger times\n"; 
  $resp = sendCommand($sock, "MAIN: TAB " . $timingTab);
  $resp = sendCommand($sock, "TIMING: TAB " . $timingInternalTab);
  $resp = sendCommand($sock, "TIMING: TIMES " . $repetitionRate . " " . $lamp2Acquisition . " " .$acquisitionLength . " " . $acqusition2QSwitch . " " . $qSwitchLength . " " .  $startDelay . " " . $freq);
  sleepSec($sleepActive, $sleepWaitSec);   

# define my datasets 
  lprint "\nTransfer my datasets\n"; 
  $resp = sendCommand($sock, "MAIN: TAB " . $acqTab);
  foreach $submitDataset (@trDataset) {
    $resp = sendCommand($sock, "DATASET " . $submitDataset);
  }
  foreach $submitCustomInfo (@trCustomInfo) {
    $resp = sendCommand($sock, "CUSTOMINFO " . $submitCustomInfo);
  }
  sleepSec($sleepActive, $sleepWaitSec);   
	
# ---------------------------------------------------------------------
# main part  
# ---------------------------------------------------------------------

my $records = 0; 
my $autoStatus = -1; 
my $timeout = -1; 
# frequency described above 	

my $runNo = 0;
# number of sequences OFFSET-GAIN-Acquisitions  


# voltage on
  lprint "\nSwitch the voltages ON\n"; 
  $resp = sendCommand($sock, "MAIN: TAB " . $pmtTab);
  $resp = sendCommand($sock, "PMT: TAB " . $pmtInternalTab);
  switchHV($sock, $pmtNum, \@pmtIndex, "ON");
  sleepSec($sleepActive, $sleepWaitSec);   

# set trigger switchhes 
# bitwise Lamp - Acquisition - QSwitch - Gating - Master 
  $resp = sendCommand($sock, "MAIN: TAB " . $timingTab);
  $resp = sendCommand($sock, "TIMING: TAB " . $timingInternalTab);
  $resp = sendCommand($sock, "TIMING: MODE " . 7);
  sleepSec($sleepActive, $sleepWaitSec);   
	
# acq Tab
  lprint "\nStart the acquisition(s)\n"; 
  $resp = sendCommand($sock, "MAIN: TAB " . $acqTab);
  $resp = sendCommand($sock, "RECORDS " . $targetRecords);
  $resp = sendCommand($sock, "SHOTS " . $targetShots);
  $timeout = getAcqTimeout($freq, $targetShots, $targetRecords);
  $resp = sendCommand($sock, "AUTO 1");
	
# wait until the number of records in the STATUS? is like RECORDS=$targetRecords
  if ($targetRecords > 0) {
    $records = waitForRecords($sock, $targetRecords, $timeout);
	  if ($records != $targetRecords) { 
	    die "wait time for the acquisiton elapsed " . $timeout;
	  } 
  }

# target records == 0? we must wait for that somebody manually stops (wait time -1)
# then AUTO in the STATUS? must be like AUTO=0 
  if ($targetRecords == 0) {
  sleepSec($sleepActive, $sleepWaitSec);   
  $autoStatus = waitForAutoRun($sock, 0, -1);
    if ($autoStatus != 0) { 
      die "wait acquisiton stop failed";
    } 
  }

# voltage off
  lprint "\nSwitch the voltages OFF\n"; 
  $resp = sendCommand($sock, "MAIN: TAB " . $pmtTab);
  $resp = sendCommand($sock, "PMT: TAB " . $pmtInternalTab);
  switchHV($sock, $pmtNum, \@pmtIndex, "OFF");
  sleepSec($sleepActive, $sleepWaitSec);   
  
# Triggers off 
  lprint "\nSwitch the triggers OFF\n"; 
  $resp = sendCommand($sock, "MAIN: TAB " . $timingTab);
  $resp = sendCommand($sock, "TIMING: TAB " . $timingInternalTab);
  $resp = sendCommand($sock, "TIMING: MODE " . 0);
  sleepSec($sleepActive, $sleepWaitSec);   

#let's switch back to the acquis tab 
  lprint "\nGo back to the acquis tab\n"; 
  $resp = sendCommand($sock, "MAIN: TAB " . $acqTab);
  
# ---------------------------------------------------------------------
# end
# ---------------------------------------------------------------------
  close($sock);
  lprint "\n*** " . $thisSkript . " end\n";
  my $elapsedtime = time - $starttime;
  lprint "Elapsed time: " . $elapsedtime . " seconds\n";
  logDate("Test end: ");
  close $myFile;
  