#!/usr/bin/expect --
#
# (Change the above pointer as necessary to match the Expect installation on 
# the local machine .. the path output by UNIX/Linux command 'which expect')
#
# Automates the Horizons session required to produce a heliocentric small-body
# SPK file. The latest version of this script is available at:
#
#            ftp://ssd.jpl.nasa.gov/pub/ssd/SCRIPTS/smb_spk
#
# Version 2.0                                                   (Expect v5.45)
#
# Modification History:
#
#  DATE         Who  Change
#  -----------  ---  ---------------------------------------------------------
#  2000-Jan-14  JDG  V1.0
#  2000-Mar-13  JDG  V1.1: Non-specified local file name defaults to SPK ID
#  2001-Nov-20  JDG  Added comment-header, pointer to electronic version
#  2002-May-31  JDG  Allowed 50 year SPK files
#  2002-Oct-14  JDG  Add pattern match for "interval too small"
#  2003-Mar-19  JDG  Expand matching pattern for "interval too small" to avoid
#                     match on possible integration diagnostic message.
#                    Update comments on binary file compatibility and "CAP;"
#  2003-Mar-24  JDG  Extend SPK interval to 200 years
#  2003-Aug-05  JDG  Add detection for "no such object record" error
#  2004-Jan-23  JDG  V1.3: Add process for Kerberos passive FTP detection
#                    Update SPK interval error message
#                    Remove semi-colon detection check (now opt. in Horizons)
#                    SPK ID option added for MASL
#  2004-Aug-09  JDG  Version 1.4: Add override range check
#                    Add more timeout handling
#                    Add I/O model code
#  2012-Aug-14  JDG  Updated comments slightly to reflect server move to
#                     little-endian byte-order Linux server.
#  2015-Sep-15  JDG  Updated comments 
#  2015-Sep-22  JDG  V2.0: Horizons I/O model advanced from ##1DS to ##2
#                     Support SPK binary type specification
#  2017-Mar-13  JDG  Updated "http:" references to "https:"
#
# Key:
#  JDG= Jon.D.Giorgini@jpl.nasa.gov
#
# BACKGROUND:
# -----------
#
# This script ("smb_spk") allows a user to type a single command on a 
# workstation and create a binary OR text transfer format SPK file for a 
# comet or asteroid on that same machine. Planet and satellite SPK files 
# are NOT generated or available this way, but are available separately. 
#
# This script offers network transparency by connecting to the JPL Horizons
# ephemeris system, automating the interaction with it, then transferring 
# the file by FTP back to the user's local machine.
#
# COMMAND LINE:
# -------------
#
#  smb_spk [-t|-b|-1|-2] [small-body] [start] [stop] [e-mail] {file_name}
#
# EXPLANATION:
# ------------
#
#  [-b|-t|-1|-2] 
#
#             A singular flag, either "-b", "-t", "-1", or "-2". REQUIRED.
#
#            -b create file in default binary format. RECOMMENDED. 
#                SPICE Toolkit versions N0052 and later have platform
#                independent reader subroutines which can read files 
#                regardless of the "little-endian" or "big-endian" 
#                byte-order of the platform they were created on. 
#
#            -t create file in ASCII text TRANSFER format. OBSOLETE. 
#                Users must later convert the file to a usable binary SPK 
#                form using utilities "tobin" or "spacit" on the local 
#                platform. This option is now unnecessary and obsolete, 
#                but retained for compatibility. The '-b' option is 
#                recommended for use instead.
#
#            -1 create Type 1 binary SPK format. For legacy users whose
#                applications are not linked against a version of the 
#                SPICE Toolkit subroutine library N0065 or later. Use only
#                if your software can't read the newer Type 21 format. 
#
#            -2 create Type 21 binary SPK format. This new format will
#                become the default Nov 1, 2015 (what will be returned by 
#                the '-b' flag). In some situations, mostly planetary
#                encounters and over long periods of time, it can provide
#                a more accuracy, but requires linking against SPICE
#                Toolkit library N0065 or later, to access the new reader.
#
#   [small-body] 
# 
#            Horizons command to select single asteroid or comet (no planets
#            or satellites .. SPK files for those object are precomputed and
#            distributed separately). ENCLOSE STRING IN QUOTES. REQUIRED.
#
#              Examples: 
#
#               "DES= 1999 JM8;"  (asteroid with designation 1998 JM8)
#               "DES= 1982 HG1;"  (asteroid named Halley)
#               "DES= 1P; CAP;"   (most recent apparition of comet Halley) 
#               "4179;"           (IAU numbered asteroid 4179 Toutatis)
#               "DES= 2099942;"   (asteroid Apophis using its unique SPK ID)
#               "DES= 2004 MN4;"  (asteroid Apophis using its unique designation)
#
#   [start]
# 
#            Date the SPK file is to begin, within span [1900-2100]. REQUIRED.
#              Examples:  2003-Feb-1 
#                         "2003-Feb-1 16:00"
#
#   [stop]
# 
#            Date the SPK file is to end, within span [1900-2100]. REQUIRED.
#            Must be more than 32 days later than [start]. 
#              Examples:  2006-Jan-12
#                         "2006-Jan-12 12:00"
#
#   [e-mail]
#
#            User's Internet e-mail contact address. REQUIRED.
#
#              Example: joe@your.domain.name
#
#            This might be used for critical/urgent follow-up situations,
#            but otherwise not.
#
#   {file_name}
#
#            OPTIONAL name to give the file on your system. If not
#            specified, it uses the SPK ID to assign a local file 
#            name in the current directory. Default form:
#               
#                 #######.bsp  (binary SPK          ... -b argument)
#                 #######.xsp  (transfer format SPK ... -t argument)
# 
#            ... where "#######" is the SPICE ID integer. For example,
#            "1000003.bsp" for 47P/Ashbrook-Jackson.
#
# EXAMPLE:
# --------
#
#    smb_spk -t "Eros;" 2000-Jan-1 2005-JAN-1 joe@your.domain.name a433.tsp
#
#    The above creates a transfer-format file called "a433.tsp" for 
# asteroid 433 Eros over the time span. 
#
#    Quotation marks are strictly necessary only for the small-body look-up
# argument, which will always contain a semi-colon. The Horizons semi-colon
# notation conflicts with the UNIX-shell command-line usage, so must be
# enclosed in quotes to be passed literally into this script.
#
#    The other arguments only need quotes if they contain spaces. In such a
# case, the quotes again insure the whole argument is passed literally to the
# script without being improperly parsed into components.
#
#    For example, the date 2000-Jan-1 does NOT need quotes on the command
# line, but "2000-Jan-1 10:00" does, since the argument contains a space
# delimiter. Instead of remembering this, one could also just enclose all
# command-line arguments in quotes.
#
#    Since this example creates a text transfer file, one would then have to
# convert it to a binary for use on the local host machine:
#
#    tobin a433.tsp   
#
#   The above command creates a binary SPK file called a433.bsp. If the "-b" 
# option had been used when calling 'smb-spk', this step could have been 
# skipped.
#
#   To summarize the resulting SPK file, one can use the following commands:
#
#    commnt -r a433.bsp   (Display internal file comments from Horizons)
#    brief a433.bsp       (Display file time-span and contents)
#
# INSTALLATION REQUIREMENTS
# -------------------------
#
#   'smb_spk' is written in the Expect automation language. The Expect
# interpretation program must be present on the local computer system before 
# 'smb_spk' can run.
#
#   The Expect language is available for free download at the URL:
#
#                      http://expect.nist.gov/
#
#   Expect is an extension of the Tcl/Tk language, which is also required.
# The web site above provides appropriate links to both packages. Installation
# procedures are provided and all packages can typically be installed and 
# their self-check tests completed in about 45 minutes by following the 
# directions.
#
#   Expect is primarily supported on UNIX/Linux platforms, but versions
# for other systems do exist. The primary book on the language is 
# "Exploring Expect" by Don Libes (ISBN 1-56592-090-2)
# 
#   Once the Expect language is installed on your machine, you may need to
# alter the very first line of this script ("#!/usr/bin/expect") to
# point to the installation location on that machine. The script will then
# be able to execute.
#
#   The user's machine must be able to resolve Internet domain names and
# support FTP.
#
#   It is assumed users of this script, and the SPK files it returns, are
# familiar with the SPICE Toolkit and programmatic usage of SPK files.
#
# USAGE
# -----
# 
#   This script will handle typical errors and respond with an indicator 
# message if any are detected, cancelling the run.
#
#   If the script appears to hang, you might change the value of 'quiet' 
# to 1 so as to observer the process and determine if/where it is stopping:
# "set quiet 1". If additional diagnostic information is needed, change 
# exp_internal to 1 ("exp_internal 1") to observe the decisions the script
# is making.
#
#   Tips:
#
#  1) If the small-body search parameters given on the command-line don't match 
# anything in the Horizons database, the script will cancel. Similarly, if
# several objects match, the script will also cancel.
#
#   This latter case occurs most often with comets. The Horizons database
# typically stores orbital elements for the same comet at more than one epoch
# (the different apparitions) because non-gravitational parameters such as
# outgassing may change from apparition to apparition. Thus, while "DES= 1P;" 
# does specify Halley's comet, it does not indicate which of the several 
# apparition records to use. Thus, the script will cancel with a 
# "non-unique match" message.  
#
#   Therefore, for comets, one must also specify the solution case of interest 
# for objects having multiple apparition solutions. 
# 
#   This can be done using the "CAP;" specification on the Horizons look-up
# command. This instructs Horizons to automatically select the most recent 
# apparition solution (prior to the current date) in the database. 
#
#   For example, "DES= 1P; CAP;" will uniquely select the comet Halley 
# apparition solution closest to the current date. "DES= 1P; CAP < 1800;"
# will select the last comet Halley apparition solution prior to the 
# year A.D. 1800.
#
#   It may be necessary to manually connect to Horizons and look at the list
# of solutions it provides so as to narrow your selection.  Object selection 
# is discussed in detail in the Horizons documentation. This script doesn't 
# function any differently, but its' deliberately non-interactive approach
# doesn't provide the same level of feedback. One can check the comments 
# section of the returned SPK file to verify the object is the one expected.  
#
#   Examples of ways to specify objects:
# 
#      "DES= 1999 JM8;"
#      "DES= 1982 HG1;"  (select the asteroid named Halley)
#      "DES= 1P; CAP;"   (select the most recent apparition of comet Halley) 
#      "DES= 2099942;"   (select asteroid Apophis using its unique SPK ID)
#      "DES= 2004 MN4;"  (select asteroid Apophis using its unique designation)
#
#   For automated systems, the best way to specify an object is by using its 
# unique designation or unique SPK ID number, since every object has one.
# An index that relates designations, SPK IDs, and names is maintained here:
#
#      ftp://ssd.jpl.nasa.gov/pub/xfr/DASTCOM.IDX
#
# The index is updated as necessary, as often as hourly. See Horizons 
# documentation for additional information.
#
#      https://ssd.jpl.nasa.gov/?horizons_doc 
#
# Note that objects can have multiple designations. For example, discovery of
# 2010 BT17 was announced. Later, it was determined it was the previously seen 
# 2005 MD11. All provisional designations such as 2010 BT17 are retained and 
# can be used as look-ups, but the primary designation will be returned by 
# the Horizons look-up. 
#
# For example, if you look-up 2010 BT17, it will be found, but the data 
# returned by Horizons will instead refer to it only by its primary designation,
# 2005 MD11. Check the index file referenced above if questions arise.
#
#  2)  It may take a couple seconds to look up an object, then several more 
# seconds to generate and transfer the SPK, depending on how much numerical 
# integration is required and the network state. Typically, however, it should
# take "seconds". The small-body SPK files do not pre-exist and are created 
# on demand.
#
#  3)  The script returns a standard exit status code (0=OK, 1=FAILED) which 
# can be checked by other calling programs. 
#
#  4)  The Horizons small-body SPK file returned contains the target object
# with respect to the Sun (object #10) only. The SPK file is intended to be 
# combined with other SPK files (i.e. loaded into a kernel pool) containing 
# the Earth, Moon, and other planets to derive any required data beyond target
# heliocentric. The DE430 planetary ephemeris SPK can be retrieved here:
#
#      ftp://ssd.jpl.nasa.gov/pub/eph/planets/bsp/de430.bsp 
#
# If such a planetary ephemeris is loaded into the kernel pool along with the
# heliocentric small-body SPK file generated by Horizons, it will be possible
# to generate positions of the target relative to bodies other than the Sun.
#
#  5)  Notable changes/events are posted in the Horizons system news:
#
#      https://ssd.jpl.nasa.gov/?horizons_news
#-----------------------------------------------------------------------------
#
# Establish defaults and turn debugging on/off
#
  set spk_ID_override ""
  exp_internal 0       ; # Diagnostic output: 1= on, 0=off
  set timeout  60      ; # Set timeout to prevent infinite hang if network down
  remove_nulls 0       ; # Disable null removal from Horizons output
#
# Turn off output (set quiet 0; use 'set quiet 1' to observe process)
#
  set quiet 0
  log_user $quiet
#
# Set Horizons server constants
#
  set horizons_machine ssd.jpl.nasa.gov
  set horizons_ftp_dir pub/ssd/
#
# Set local variables
#
  set argc [llength $argv]
  set flag [string tolower [lindex $argv 0]]
#
# Retrieve possible SPK ID override value
#
  scan $flag "%2s%s" file_flag spk_ID_override
  set spkidlen [string length $spk_ID_override]
  if { $spkidlen != 0 } {
   if { [string match "\[1-9]\[0-9]*" $spk_ID_override] == 0 } {
     puts "\SPK ID must be positive integer"
     exit 1
}  elseif { $spk_ID_override < 1000000 || $spk_ID_override > 9999999 } {
     puts "\SPK ID must be between 1000000 and 10000000"
     exit 1
}  else {
     set spk_ID_override ",$spk_ID_override"
    }
}
#
# Process script command-line values and check for basic input problems
#
  if {$argc < 4} {
    puts "\nMissing arguments. Usage:"
    puts { smb_spk [-t|-b|-1|-2] [small-body] [start] [stop] [e-mail] {file_name}}
    puts " "
    exit 1 
} elseif {$argc > 6} {
    puts "\nToo many arguments. May need to use quotes.  Usage:"
    puts { smb_spk [-t|-b|-1|-2] [small-body] [start] [stop] [e-mail] {file_name}}
    puts "Example --"
    puts { smb_spk -t "DES=1999 JM8;" "1999-JAN-2 10:00" "2015-JAN-1" "joe@your.domain.name" 1999jm8.tsp}
    puts " "
    exit 1 
} elseif { [string first "@" [lindex $argv 4] ] < 1 } {
    puts "\nNot Internet e-mail syntax: [lindex $argv 4] " 
    puts " "
    exit 1
} elseif { $file_flag == "-t" } {
    set file_type YES 
    set ftp_type ascii
    set ftp_sufx .xsp
} elseif { $file_flag == "-b" } {
    set file_type NO
    set ftp_type binary
    set ftp_sufx .bsp
} elseif { $file_flag == "-1" } {
    set file_type "1"
    set ftp_type binary
    set ftp_sufx .bsp
} elseif { $file_flag == "-2" } {
    set file_type "21"
    set ftp_type binary
    set ftp_sufx .bsp
} else { 
    puts "Unknown file type: [lindex $argv 0]"
    puts { smb_spk [-t|-b|-1|-2] [small-body] [start] [stop] [e-mail] {file_name}}
    puts " "
    exit 1
  }
  set local_file [lindex $argv 5]
#
# Connect to Horizons server 
#
  spawn telnet $horizons_machine 6775
#
# Wait for main Horizons prompt, set up, proceed 
#
  expect { 
    timeout        {puts "No response from $horizons_machine"; exit 1} 
    "unknown host" {puts "This system cannot find $horizons_machine"; exit 1}
    "Horizons> "   {send PAGE\r} }
  set timeout 15
  expect { 
     timeout       {puts "Horizons timed out (LEVEL=1). Try later or notify JPL." ; send x\r;  exit 1} 
    "Horizons> "   {send "##2\r"} }
  expect { 
     timeout       {puts "Horizons timed out (LEVEL=2). Try later or notify JPL." ; send x\r;  exit 1} 
    "Horizons> "   {send [lindex $argv 1]\r} }
#
# Handle prompt search/select
#
  expect {
     timeout       {puts "Horizons timed out (LEVEL=3). Try later or notify JPL." ; send x\r;  exit 1} 
    -re ".*Continue.*: $"   { 
       send yes\r 
       expect {
          -re ".*PK.*: $"   { send S$spk_ID_override\r  }
          -re ".*lay.*: $"  { 
             send x\r 
             puts "\nCancelled -- unique small-body not found: [lindex $argv 1]"
             puts "\nObject not matched to database OR multiple matches found."
             puts " "
             exit 1
           }
      }
    }
    -re ".*such object record.*" {
       send x/r
       puts "\nNo such object record found: [lindex $argv 1]"
       puts " "
       exit 1 }
    -re ".*Select.*<cr>: $" { send S\r   }
  }
#
# Pick out SPK ID sent by Horizons
#
  if { $argc < 6 } {
    expect { 
     timeout       {puts "Horizons timed out (LEVEL=4). Try later or notify JPL." ; send x\r;  exit 1} 
     -re " Assigned SPK object ID:  (.*)\r\r\n \r\r\n Enter your" {
         scan $expect_out(1,string) "%i" spkid
         set local_file $spkid$ftp_sufx }
      } 
  }
#
# Process prompt for e-mail address
#
  expect {
     timeout       {puts "Horizons timed out (LEVEL=5). Try later or notify JPL." ; send x\r;  exit 1} 
    -re ".*address.*: $" {
        send [lindex $argv 4]\r }
     }
#
# Process e-mail confirmation
#
  expect { 
     timeout       {puts "Horizons timed out (LEVEL=6). Try later or notify JPL." ; send x\r;  exit 1} 
    -re ".*yes.*: $"  {
       send yes\r }
     }
#
# Process file type
#
  expect {
     timeout       {puts "Horizons timed out (LEVEL=7). Try later or notify JPL." ; send x\r;  exit 1} 
    -re ".*SPK file format.*: $"  {
       send $file_type\r }
    -re ".*YES.*: $"  {
       send $file_type\r }
     } 
#
# Set start date 
#
  expect {
     timeout       {puts "Horizons timed out (LEVEL=8). Try later or notify JPL." ; send x\r;  exit 1} 
    -re ".*START.*: $"  {
       send [lindex $argv 2]\r }
     } 
#
# Handle start date error or STOP date
#
  expect {
     timeout       {puts "Horizons timed out (LEVEL=9). Try later or notify JPL." ; send x\r;  exit 1} 
    -re ".*try.*: $" {
       send x\r
       puts "\nError in START date: [lindex $argv 2]"
       puts "Must be in 1900 to 2100 time span. Example: '2018-JAN-1'"
       puts " "
       exit 1 }
    -re ".*STOP.*: $" {
       send [lindex $argv 3]\r }
     }
#
# Handle stop date error
#
  set timeout -1
  expect {
     timeout       {puts "Horizons timed out (LEVEL=10). Try later or notify JPL." ; send x\r;  exit 1} 
    -re ".*large.*" {
       send x\r
       puts "\nError in STOP date: [lindex $argv 3]"
       puts "Stop date must not be more than 200 years after start."
       puts " "
       exit 1 }
    -re ".*try.*" {
       send x\r
       puts "\nError in STOP date: [lindex $argv 3]"
       puts "Must be in 1900 to 2100 time span. Example: '2006-JAN-1'"
       puts " "
       exit 1 }
    -re ".*time-span too small.*" {
       send x\r
       puts "\nError in requested length: [lindex $argv 2] to [lindex $argv 3]"
       puts "Time span of file must be >= 32 days."
       puts " "
       exit 1 }
    -re ".*YES.*" {
       send NO\r }
   }
#
# Pick out FTP file name
#
  set timeout 15
  expect { 
    timeout       {puts "Horizons timed out (LEVEL=11). Try later or notify JPL." ; send x\r;  exit 1} 
   -re "File name   : (.*)\r\r\n   File type" {
       set ftp_name $expect_out(1,string) }
       send "x\r"
    } 
#
# Retrieve file by anonymous FTP
#
  set timeout 30
  spawn ftp $horizons_machine
  expect {
     timeout {puts "Cancelled -- FTP server not responding."; exit 1 }
     -re "Name.*: $"
   } 
  send "anonymous\r"
  expect "Password:"
#
# Next bit is HP-UNIX work-around
#
  set oldpw [lindex $argv 4]
  if [regsub @ $oldpw '\134@' pw] {
    set newpw $pw
  } else {
    set newpw $oldpw
  }
  send $newpw\r
#
  expect {
    "Login failed." { 
       send "quit\r" 
       puts "\nFTP login failed -- must use full Internet e-mail address."
       puts "Example:  'joe@your.domain.name'"
       puts " "
       exit 1 }
    "ftp> " { send $ftp_type\r    } 
   }
  expect "ftp> " { send "cd pub/ssd\r" }
#
  set timeout -1
  expect "ftp> " { send "get $ftp_name $local_file\r" }
  expect {
    -re ".*No such.*" {
      puts "\nError -- cannot find $ftp_name on server."
      puts " "
      exit 1 }
    -re ".*425 Can't build data connection.*" {
      expect "ftp> " { send passive\r }
      expect "ftp> " { send "get $ftp_name $local_file\r" }
      exit 0 }
    "ftp> " { send "quit\r" }
   }
#
# Finished, set status code 
#
  exit 0
