#!/usr/bin/python3

# SPDX-FileCopyrightText: 2024 European Southern Observatory (ESO)
#
# SPDX-License-Identifier: LGPL-3.0-only

"""
Start all TREx processed:
- nwif
- GNURadio instance(s)

This python script is executed by trexsup when
transitioning to Enabled state, and is terminated
when exiting Enabled state.
This script:
   - starts the GNU Radio with the specified script
   - the nwif with the specified cfg file.
Together the above applications are the full TREx system.

The script expects two arguments:
- the path to the GNU Radio python file.
- the nwif configuration file

The above is configured in the trexsup yaml file.
"""
import subprocess
import os
import sys

import signal
import time

#
# launch nwif and the GNU Radio processes
#
class SoftwareStarter:
    def __init__(self, py_file_1, py_file_2, nwif_cfg_file, runnwif=True):
        self.gnur_process_1=None
        if py_file_1 != '':
            if py_file_1.endswith(".py"):
                gnur_cmd_1 = ["python3", py_file_1]
                subprocess.run(["ciiLogSend", "--logtype", "INFO", "--sourceid", "trexStartUp", "--appname", "trexStartUp", "--message", "GNU Radio python file: "+py_file_1])
            else:
                gnur_cmd_1 = ["python3", "-m", py_file_1]
                subprocess.run(["ciiLogSend", "--logtype", "INFO", "--sourceid", "trexStartUp", "--appname", "trexStartUp", "--message", "GNU Radio python module: "+py_file_1])
            self.gnur_stdout_1 = open("/tmp/gnur_py1.log", "w")
            self.gnur_process_1 = subprocess.Popen(gnur_cmd_1,
                stdout=self.gnur_stdout_1,
                stderr=subprocess.STDOUT
            )
        self.gnur_process_2=None
        if py_file_2 != '':
            if py_file_2.endswith(".py"):
                gnur_cmd_2 = ["python3", py_file_2]
                subprocess.run(["ciiLogSend", "--logtype", "INFO", "--sourceid", "trexStartUp", "--appname", "trexStartUp", "--message", "GNU Radio python file: "+py_file_2])
            else:
                gnur_cmd_2 = ["python3", "-m", py_file_2]
                subprocess.run(["ciiLogSend", "--logtype", "INFO", "--sourceid", "trexStartUp", "--appname", "trexStartUp", "--message", "GNU Radio python module: "+py_file_2])
            self.gnur_stdout_2 = open("/tmp/gnur_py2.log", "w")
            self.gnur_process_2 = subprocess.Popen(gnur_cmd_2,
                stdout=self.gnur_stdout_2,
                stderr=subprocess.STDOUT
            )
            
        time.sleep(2)  # give GNUR some time to start up
        if runnwif:
            self.rtc_stdout = open("/tmp/nwif.log", "w")
            self.rtc = subprocess.Popen(["nwif", "-l", "INFO", "-c", nwif_cfg_file],
            # self.rtc = subprocess.Popen(["nwif", "-l", "DEBUG", "-c", nwif_cfg_file],
                stdout=self.rtc_stdout,
                stderr=subprocess.STDOUT
            )
            print("trexStartUp waiting for processes to come up")
            time.sleep(1)  # give NWIF some time to start up
            time.sleep(1)  # give NWIF some time to start up
        else:
            print("Skipping nwif launch, as per request")   
            self.rtc = None

        signal.signal(signal.SIGTERM, self.SignalHandler)
        
        # check processes are running
        launch_failed=False
        if self.gnur_process_1 is not None:
            poll = self.gnur_process_1.poll()
            if poll is not None:
                err_msg = "GNU Radio process 1 terminated with exit code: " + str(poll)
                with open("/tmp/gnur_py1.log", "r") as f:
                    err_msg = err_msg+": "+str(f.read().replace("\n", " "))
                print(err_msg)
                subprocess.run(["ciiLogSend", "--logtype", "ERROR", "--sourceid", "trexStartUp", "--appname", "trexStartUp", "--message", err_msg])         
                launch_failed=True
        if self.gnur_process_2 is not None:
            poll = self.gnur_process_2.poll()
            if poll is not None:
                err_msg = "GNU Radio process 2 terminated with exit code: " + str(poll)
                with open("/tmp/gnur_py2.log", "r") as f:
                    err_msg = err_msg+": "+str(f.read().replace("\n", " "))
                print(err_msg)
                subprocess.run(["ciiLogSend", "--logtype", "ERROR", "--sourceid", "trexStartUp", "--appname", "trexStartUp", "--message", err_msg])
                launch_failed=True
        if self.rtc is not None:
            poll = self.rtc.poll()
            if poll is not None:
                err_msg = "NWIF process terminated with exit code: " + str(poll)
                with open("/tmp/nwif.log", "r") as f:
                    err_msg = err_msg+": "+str(f.read().replace("\n", " "))
                print(err_msg)
                subprocess.run(["ciiLogSend", "--logtype", "ERROR", "--sourceid", "trexStartUp", "--appname", "trexStartUp", "--message", err_msg])
                launch_failed=True
        if launch_failed:
            if self.gnur_process_1 is not None:
                self.gnur_process_1.send_signal(signal.SIGTERM) 
            if self.gnur_process_2 is not None:
                self.gnur_process_2.send_signal(signal.SIGTERM) 
            if self.rtc is not None:
                self.rtc.send_signal(signal.SIGTERM)
        else:
            print("trexStartUp processes running, exit with SIGTERM, e.g. kill -SIGTERM "+str(os.getpid()))
            
    def SignalHandler(self, sig, frame):
        print("trexStartUp SIGTERM received")
        if self.gnur_process_1 is not None:
            self.gnur_process_1.send_signal(signal.SIGTERM) 
        if self.gnur_process_2 is not None:
            self.gnur_process_2.send_signal(signal.SIGTERM) 
        if self.rtc is not None:
            self.rtc.send_signal(signal.SIGTERM) 

    def Wait(self):
        exit_codes = []
        if self.gnur_process_1 is not None:
            exit_codes.append(self.gnur_process_1.wait())
        if self.gnur_process_2 is not None:
            exit_codes.append(self.gnur_process_2.wait())
        if self.rtc is not None:
            exit_codes.append(self.rtc.wait())
        print("trexStartUp exit codes: "+str(exit_codes))
        if any(code != 0 for code in exit_codes):
            subprocess.run(["ciiLogSend", "--logtype", "ERROR", "--sourceid", "trexStartUp", "--appname", "trexStartUp", "--message", "start_trex processes terminated abnormally, with exit codes: "+str(exit_codes)])
        else:
            subprocess.run(["ciiLogSend", "--logtype", "INFO", "--sourceid", "trexStartUp", "--appname", "trexStartUp", "--message", "start_trex processes terminated with exit codes: "+str(exit_codes)])
        sys.exit(0)

#
# search $CFGPATH for file_name and return it,
# if not found, exit the application with an error message
#
def FindFile(file_name):
    paths = os.environ['CFGPATH'].split(":")
    found = False
    for path in paths:
        if os.path.isfile(os.path.join(path, file_name)):
            found = True
            file_name = os.path.join(path, file_name)
            break
    if not found:
        print("Specified file not found in $CFGPATH: ", file_name)
        sys.exit(-2)
    return file_name

def main():
    argidx = 1
    if len(sys.argv) == 1 or sys.argv[argidx].startswith("--help"):
        print("This script is used by trexsup to launch nwif and FS and GPAO flowgraphs.")
        print("Arguments: [--help] [params] [-fs <path to FS script>] [-gpao <path to GPAO script>] <nwif config file>")
        print("[--help]: print this help info and exit")
        print("[params]: only the param 'nonwif' is supported, and if specified, it should be the first argument")
        print("            nownif skips launching of nwif executable.")
        print("[-fs <...>] path to the Field Stab. GNU Radio python file")
        print("            if the path ends with .py, the file is found using $CFGPATH, otherwise it is assumed to be a python module")
        print("[-gpao <...>] path to the GPAO GNU Radio python file")
        print("            if the path ends with .py, the file is found using $CFGPATH, otherwise it is assumed to be a python module")
        print("<nwif config file> is the final argument, it is the yaml file passed to nwif on the command line.")
        print("Example 1: trexStartUp -fs trex_v020_centroiding config/nwif/nwif_sim.yaml")
        print("Example 2: trexStartUp -fs trex_v020_centroiding -gpao trexsup_switch_test config/nwif/nwif_sim.yaml")
        print("Example 3: trexStartUp -gpao trexsup_switch_test config/nwif/nwif_sim.yaml")
        print("Example 4: trexStartUp nonwif -fs trex_v020_centroiding -gpao trexsup_switch_test config/nwif/nwif_sim.yaml")
        sys.exit(-1)
    runnwif = True
    fs_py_file=''
    gpao_py_file=''
    if sys.argv[argidx].startswith("nonwif"):
        runnwif = False
        argidx += 1
    if sys.argv[argidx] == "-fs":
        argidx += 1
        if len(sys.argv) == argidx:
            print("trexStartUp missing -fs script, see help text (--help)")
            sys.exit(-2)
        fs_py_file = sys.argv[argidx]
        argidx += 1
        if fs_py_file.endswith(".py"):
            fs_py_file = FindFile(fs_py_file)
    if sys.argv[argidx] == "-gpao":
        argidx += 1
        if len(sys.argv) == argidx:
            print("trexStartUp missing -gpao script, see help text (--help)")
            sys.exit(-2)
        gpao_py_file = sys.argv[argidx]
        argidx += 1
        if gpao_py_file.endswith(".py"):
            gpao_py_file = FindFile(gpao_py_file)
    if len(sys.argv) == argidx:
        print("trexStartUp missing nwif cfg file, see help text (--help)")
        sys.exit(-2)
    nwif_cfg_file = sys.argv[argidx]
    if len(sys.argv) > argidx+1:
        print("trexStartUp unexpected arguments, see help text (--help)")
        sys.exit(-2)
    print("trexStartUp running with FS: ["+fs_py_file+"], GPAO: ["+gpao_py_file+"], nwif cfg: ["+nwif_cfg_file+"]")    
    # find each file in $CFGPATH
    # the nwif command will resolve nwif_cfg_file, so we only
    # want to check the file exists in this code.
    FindFile(nwif_cfg_file)

    s = SoftwareStarter(fs_py_file, gpao_py_file, nwif_cfg_file, runnwif)
    s.Wait()


if __name__ == "__main__":
    main()
