import sys
import datetime
import threading
import json
import RXTools
import argparse
import time

# Copyright Trimble 2020
#
# This script will configure multiple receivers in parallel. Typical use
# will be:
#
#
# -- Upload and install the clone files to each receiver
# (receivers.json defines the login to the receivers and the name of the
# clone files). The same clone file can be loaded to multiple receivers
# (just use the same name in receivers.json for the XML file)
# python configRX.py -s receivers.json 
#
# -- Same as above, but sets to factory defaults before applying the
# clone file
# python configRX.py -s receivers.json -r
#
# -- Creates and downloads a clone file for each receiver defined in
# receivers.json. Each entry in receivers.json must have a unique XML
# filename
# python configRX.py -s receivers.json -d
#
# -- does the same as (python configRX.py -s receivers.json). However,
# it also downloads GNSS only data from 10.1.150.XXX (with credentials
# admin password). This GNSS data is then uploaded to the receivers. If
# you have an old clone file the GNSS data may be out of date. By using
# this command you can install the desired clong file with fresh GNSS
# data. The GNSS data is also available locally as "GNSS.xml"
# python configRX.py -s receivers.json -i 10.1.150.XXX -c admin:password
#
# -- Same as above, but sets to factory defaults before applying the
# clone
# python configRX.py -s receivers.json -i 10.1.150.XXX -c admin:password -r
#
# -- Same as the above. However, the "-g" tells the script to only
# update the "GNSS.xml" to each receiver, it does not install the clone
# files pointed to in receivers.json. The -r does nothing in this mode
# python configRX.py -s receivers.json -g -i 10.1.150.XXX -c admin:password
#
# -- Use the IP addresses/credentials from receivers.json, but ignore
# the clone files receivers.json points to. Instead install
# "yourClone.xml" to each receivers. For example, if you have a GNSS XML
# file (with a set of almanacs/ephemeris), you may want to load just
# this file to each receiver.
# python configRX.py -s receivers.json -u yourClone.xml
#
# Also supports the -r to clear to defaults first
#

# Thread to perform the upgrade - we'll run N threads in parallel. One for each receiver
# we are going to flash
def workerUpgrade(num):
  try:
    # Some of the clone operations don't work in testmode - why?
    testmode = RXTools.IsTestModeEnabled(stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"))
    if(testmode == False):
      RXTools.EnableTestMode(stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"))

    if(args.ErrorLog):
      print("Clearing the error log RX = %s" % ( stations[num].get("long") ) )
      RXTools.ClearErrorLog(stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"))
    elif(args.Download):
      print("Starting Clone Download RX = %s" % ( stations[num].get("long") ) )
      RXTools.CloneAllConfig(stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"), stations[num].get("clone"))
      RXTools.DownloadClone(stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"), stations[num].get("clone"))
    else:
      if(args.GNSS == False): # only update the GNSS requested
        print("Starting Clone Upload RX = %s" % ( stations[num].get("long") ) )
        if(args.Upload):
          # User supplied the name of an XML file to upload, ignore the
          # one pointed to by the JSON file
          filename = args.Upload
        else:
          filename = stations[num].get("clone")

        # delete an old clone file if it exists
        RXTools.DeleteFile( stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"), '/Internal/Clone', filename)
        # Upload the clone file
        RXTools.UploadClone(stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"), filename)
        time.sleep(20)
        # Install a clone file clearing to factory defaults before it is applied (when -r is present)
        RXTools.InstallClone(stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"), filename, clear=args.Revert)
      else: # args.GNSS is True so update GNSS.xml
        # delete an old clone file if it exists
        print("Starting Upload and install of GNSS data RX = %s" % ( stations[num].get("long") ) )
        RXTools.DeleteFile( stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"), '/Internal/Clone', 'GNSS.xml')
        RXTools.UploadClone(stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"), 'GNSS.xml')
        time.sleep(20)
        RXTools.InstallClone(stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"), 'GNSS.xml')
    
    # finally disable testmode if it was disabled when we started
    if(testmode == False):
      RXTools.DisableTestMode(stations[num].get("addr"), stations[num].get("user"), stations[num].get("pw"))
  except:
    print("!!!! Problem with connecting to the receiver receiver Name = %s IP = %s File = %s !!!!" % 
           ( stations[num].get("long"), stations[num].get("addr"), stations[num].get("fw") ) )
    return

  # Normal exit
  nowStr = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  print("%s - Completed configuration update RX = %s IP = %s" % 
         (nowStr, stations[num].get("long"),stations[num].get("addr")) )
  return

if __name__ == "__main__":

  ######################################################################
  # Parse arguments
  parser = argparse.ArgumentParser(description='Configure receivers')
  parser.add_argument('-s','--stations', help='Filename of the station JSON e.g. --stations global.json')
  parser.add_argument('-d','--Download', help='Download clone files from the receiver', action="store_true")
  parser.add_argument('-u','--Upload', help='Upload an XML file (ignore the XML files pointed to by the JSON file), e.g. --Upload GNSS.xml')
  parser.add_argument('-r','--Revert', help='Revert to factory defaults before installing the clone file', action="store_true")
  parser.add_argument('-i','--IP', help='IP Address of a receiver we will use to get the latest GNSS data')
  parser.add_argument('-c','--Credentials', help='Username and password (for receiver at -i) format is user:password')
  parser.add_argument('-g','--GNSS', help='Update the GNSS only (needs -i to get the file, otherwise assumes a local GNSS.xml)', action="store_true")
  parser.add_argument('-e','--ErrorLog', help='Clears the error log (needs -s) does not upload a clone file', action="store_true")
  args = parser.parse_args()
  ######################################################################

  if(args.stations):
    with open(args.stations,'r') as f: 
      data = json.load(f)

    stations = []
    gotStations = False
    # New JSON format
    for i in range(len(data)):
      if('RX' in data[i]):
        stations = data[i]['RX']
        gotStations = True
    
    # Old format
    if(gotStations == False):
      stations = data
  else:
    print('require a station JSON file')
    sys.exit(1)

  if(args.ErrorLog == False): # Error log clearing can't be combined
                              # with other operations
    if(args.Download):
      # We are downloading the configuration - need a unique file for each
      # receiver - for the upload we can write the same file to multiple
      # receivers.
      cloneFiles = []  
      for num in range(len(stations)):
        cloneFiles.append(stations[num]['clone'])
    
      cloneFiles = list(set(cloneFiles))
      if(len(cloneFiles) != len(stations)):
        print("Don't have a unique set of clone files - exiting")
        sys.exit(2)
    else:
      if(args.IP):
        if(args.Credentials):
          (user, password) = args.Credentials.split(":")
        else:
          user = "admin"
          password = "password"

        try:
          print("Creating and downloading GNSS clone data")
          RXTools.DeleteFile( args.IP, user, password, '/Internal/Clone', 'GNSS.xml')
          RXTools.CloneGNSSConfig(args.IP, user, password, 'GNSS.xml')
          RXTools.DownloadClone(args.IP, user, password, 'GNSS.xml')
        except:
          print("Error getting GNSS clone data")
          sys.exit(1)
 
  nowStr = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  if(args.Download):
    print("\n%s - Starting Parallel config download of receivers found in %s\n" % (nowStr, args.stations) )
  else:
    print("\n%s - Starting Parallel config update of receivers found in %s\n" % (nowStr, args.stations) )

  # Now install a set of threads, one for each receiver. Each thread
  # will install firmware for a single receiver
  threadHandle = []
  for num in range(len(stations)):
    t = threading.Thread(target=workerUpgrade, args=(num,))
    threadHandle.append(t)
    t.start()
    # 50ms sleep - gives the task time to start before we start the next
    # one. This simply reduces the chance of the debug getting mangled
    # on stdout.
    time.sleep(0.05) 

  # Give the threads a couple of seconds to connect to the receiver and
  # complete outputing any status to stdout (otherwise the output in normal
  # operation can get mixed up with what follows). This is to simply
  # make the debug neater on stdout. There are cases, e.g. a problem
  # with a receiver, that can result in debug cluttering stdout.
  # However, for most normal cases this will work.
  time.sleep(2) 

  nowStr = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  print("\n%s - Waiting for update to complete on all receivers" % nowStr)
  # Now wait for the upgrades to finish, the following code will block
  # until all of the threads have returned
  for i in range(len(threadHandle)):
    threadHandle[i].join()
 
  nowStr = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  print("%s - Configuration update completed" % nowStr)


