#!/usr/local/bin/expect --
#
# Change the above pointer as necessary for the expect installation on the
# local machine (i.e., to path output by UNIX/Linux command 'which expect')
#
# Define path to settings input file; can be changed by user as desired. 
#
  set input_file ./obs_tbl.inp
# 
# Automate the Horizons session required to produce an OBSERVER TABLE for 
# a user-specified Horizons object. Generally, this script is suited for 
# situations where the same output quantities (i.e., defined by the input 
# file) are desired for a list of objects (specified one at a time on the 
# calling command-line).
#
# The latest version of this script (along with other scripts) is available at:
#
#   ftp://ssd.jpl.nasa.gov/pub/ssd/SCRIPTS/obs_tbl
#
# A corresponding example input file is at:
# 
#   ftp://ssd.jpl.nasa.gov/pub/ssd/SCRIPTS/obs_tbl.inp
#
# Version 2.0                                                    (Expect v5.45)
#
# Modification History:
#
#  DATE         Who  Change
#  -----------  ---  ----------------------------------------------------------
#  2003-Aug-19  JDG  V1.0 (derived from script "smb_spk").
#  2012-Sep-14  JDG  Fixed to allow passing negative station site codes.
#                    Fixed FTP dialogue case to match new server
#                    Changed handling of START_TIME dialogue
#                    Added support of ANG_FORMAT default over-ride
#                     as an example of changing the default settings.
#  2013-Feb-13  JDG  Added example to doc for specifying spacecraft 
#                     (i.e., passing negative ID numbers)
#  2015-Aug-28  JDG  Version 2.0:
#                     Add support for optionally changing all Observer-Table
#                     default settings. Modernized.
#  2017-Mar-13  JDG  Updated reference from "http:" to "https:"
#  2017-May-31  JDG  Added ANG_RATE_CUTOFF support
#
# Key:
#  JDG= Jon.D.Giorgini@jpl.nasa.gov
#
# BACKGROUND:
# -----------
#
#   This script ("obs_tbl") and companion input file ("obs_tbl.inp") allow a 
# user to type one command on a workstation and produce a numerically integrated
# Horizons ASCII observer-table file on that same machine for a specified 
# object.
#
#   The script offers network transparency by connecting to the JPL Horizons
# ephemeris system, automating the interaction with it, then transferring the 
# file back to the user's local machine using FTP.
#
# COMMAND LINE:
# -------------
#
#   obs_tbl [target] {output_file}
#
# EXPLANATION:
# ------------
#
#   [target]
#
#     A Horizons command specifying a single target (a planet, satellite,
#     asteroid, comet, or spacecraft).  REQUIRED.
#
#     Note that ID numbers provide unique matches, while name strings 
#     often do not. For example, string "Io" would match satellite Io 
#     and Iocaste (and some other cases), while "501" uniquely specifies 
#     the Jovian  satellite. Enclose string in quotes (and possibly also
#     braces if a negative number, such as spacecraft ID). See below for 
#     examples and Horizons documentation for details on specifying 
#     objects.
#
#            https://ssd.jpl.nasa.gov/?horizons
#
#       Examples: "DES= 1999 JM8;" (Asteroid with designation 1999 JM8) 
#                 "4179;"          (Numbered asteroid 4179 Toutatis)
#                 "433;"           (Numbered asteroid 433 Eros)
#                 "DES= 3102762;"  (Object w/SPICE-ID 3102762 (2002 AA29)
#                 "Golevka;"       (Named asteroid 6489 Golevka)
#                 "501"            (Jovian satellite Io)
#                 "Venus"          (Planet Venus)
#                 "Tethys"         (Saturnian satellite Tethys)
#                 "301"            (Earth's Moon)
#                 "399"            (Earth planet center)
#                 "3"              (Earth-Moon barycenter)
#                 "{-236}"         (MESSENGER spacecraft .. braces needed
#                                   to pass negative numbers)
#
#       Note that small-body designation look-ups are SPACE and CASE-SENSITIVE. 
#       Names are CASE-INSENSITIVE.
#
#   {output_file}
#
#     OPTIONAL name to give the output file on your system. If not
#     specified, the command-line [target] string is used to build
#     a local file name in the current directory using the template:
#               
#         [target].txt
#
#     ... though spaces in the [target] string are removed in the file name.
# 
# Command-line arguments require quotes if they contain spaces or a semi-colon. 
# The quotes guarantee the whole argument is passed literally to the script 
# without being improperly parsed into components. Instead of remembering this, 
# one could also just enclose all command-line arguments in quotes as routine 
# practice.
#
# Example command-line:
#
#   obs_tbl "2015 HM10;" 2015hm10.txt
#
#   If the local output file name wasn't specified, i.e.,
#
#   obs_tbl "2015 HM10;"
#
#   ... the default file name created would be "2015HM10.txt"
#
# SCRIPT REQUIREMENTS
# -------------------
#
# #1) This script looks for an input file whose pathname is defined above
# in the variable "input_file". This input file contains minimal control
# settings to define the Horizons request:
#
# Example input file:
#
#  set   EMAIL_ADDR           " "
#  set   CENTER               "@spitzer"
#  set   START_TIME           "2004-Jan-1 12:00"
#  set   STOP_TIME            "2004-Mar-7" 
#  set   STEP_SIZE            "1d"
#  set   QUANTITIES           "1,4,9,8"
#
# #2) "obs_tbl" is written in the Expect automation language. The Expect
# interpretation program must be present on any computer system before 
# obs_tbl can run.
#
#   The Expect language is available for download at the URL:
#
#                      http://expect.nist.gov/
#
#   Expect is an extension of the Tcl/Tk languages, which are also required.
# The web site provides appropriate links. 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 platforms, but versions for other 
# systems do exist. A useful 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/local/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 internet FTP.
#
# USAGE
# -----
#
# This script will handle typical errors and respond with an indicator 
# message, but is not necessarily bullet-proof, being intended mostly as 
# a functional example users can customize.
#
# See Horizons documentations for additional information on that program's
# operation.
#
# It could take a few seconds or longer to generate and transfer the table, 
# depending on how much numerical integration is required and the network 
# state. 
#
# The script returns a standard exit status code (0=OK, 1=FAILED) which 
# can be checked by calling programs. 
#
#------------------------------------------------------------------------------
# Establish defaults & turn debugging on or off
#
  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 mirroring (set quiet 0; set quiet 1 to observe process)
#
  set quiet 0
  log_user $quiet
#
# Initialize local script variables
  set start_flag 0
# 
# Set Horizons server constants
#
  set horizons_server   ssd.jpl.nasa.gov
  set horizons_ftp_dir  pub/ssd/
#
# Process script command-line values; check for input problems
#
  set argc [llength $argv]
  if {$argc < 1} {
   puts "\nMissing argument. Usage:"
   puts { obs_tbl [target] {output_file} }
   puts " "
   puts "Example --"
   puts { obs_tbl "2015 HM10;" 2015hm10.txt}
   puts " "
   exit 1 
} elseif {$argc > 2} {
   puts "\nToo many arguments. May need to use quotes.  Usage:"
   puts { obs_tbl [target] {output_file} }
   puts " "
   puts "Example --"
   puts { obs_tbl "2015 HM10;" 2015hm10.txt}
   puts " "
   exit 1 
} elseif {$argc == 1} {
   set local_file [join [lindex $argv 0] ""]
   set local_file [string trim $local_file]
   set local_file [string trimright $local_file ";"].txt
} elseif {$argc == 2} {
   set local_file [lindex $argv 1]
  }
#
# Initialize output table default over-rides to null
#
  set REF_SYSTEM           "" 
  set TIME_ZONE            ""
  set CAL_FORMAT           ""
  set TIME_DIGITS          "" 
  set ANG_FORMAT           ""
  set EXTRA_PREC           ""
  set APPARENT             ""
  set RANGE_UNITS          ""
  set SUPPRESS_RANGE_RATE  ""
  set ELEV_CUT             ""
  set AIRMASS              ""
  set R_T_S_ONLY           ""
  set SKIP_DAYLT           ""
  set SOLAR_ELONG          ""
  set LHA_CUTOFF           ""
  set ANG_RATE_CUTOFF      ""
  set CSV_FORMAT           ""
#
# Load script's observer-table run-control settings file 
#
  if [ file exists $input_file ] {
   source $input_file 
} else {
   puts "Missing input file $input_file"
   exit 1
  }
#
# Create concatenated string of default over-rides
#
 set DEFAULTS ""
 set DEFAULTS "$DEFAULTS$REF_SYSTEM$TIME_ZONE$CAL_FORMAT$TIME_DIGITS"
 set DEFAULTS "$DEFAULTS$ANG_FORMAT$EXTRA_PREC$APPARENT$RANGE_UNITS"  
 set DEFAULTS "$DEFAULTS$SUPPRESS_RANGE_RATE$ELEV_CUT$AIRMASS$R_T_S_ONLY"
 set DEFAULTS "$DEFAULTS$SKIP_DAYLT$SOLAR_ELONG$LHA_CUTOFF"
 set DEFAULTS "$DEFAULTS$ANG_RATE_CUTOFF$CSV_FORMAT"
#
# Connect to Horizons 
#
  spawn telnet $horizons_server 6775
#
# Get main Horizons prompt and proceed 
#
  expect { 
   timeout        {puts "No response from $horizons_server"; exit 1} 
   "unknown host" {puts "This system cannot find $horizons_server"; exit 1}
   "Horizons> "   {send PAGE\r} }
  set timeout 15
  expect { 
   "Horizons> "   { send [lindex $argv 0]\r} 
  }
#
# Handle prompt for search/select
#
  expect {
   -re ".*Continue.*: $"   { 
     send yes\r 
     expect {
      -re ".*PK.*: $"   { send E\r  }
      -re ".*lay.*: $"  { 
        send x\r 
        puts "\nCancelled -- unique object 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 E\r   }
  }
#
# Request observer table ephemeris type
#
  expect { 
   -re ".*Observe, Elements.*: $" {
        send O\r }
     } 
#
# Provide coordinate/observing center
#
  expect {
    -re ".*Coordinate center.*: $" {
        send -- $CENTER\r }
     }
#
# Handle coordinate/observing center error or confirmation 
#
  expect {
    -re ".*Cannot find central body.*: $" {
        send X\r  
        puts "\nCannot find CENTER given in $input_file (no match): CENTER= '$CENTER'"
        puts " "
        exit 1 }
    -re ".*Select.*<cr>: $" {
        send X\r  
        puts "\nNon-unique CENTER in $input_file (multiple matches): CENTER= '$CENTER'"
        puts " "
        exit 1 }
    -re ".*Coordinate center.*<cr>: $" {
        send X\r  
        puts "\nNon-unique CENTER in $input_file (multiple matches): CENTER= '$CENTER'"
        puts " "
        exit 1 }
    -re ".*Confirm selected.*> " {
        send Y\r }
    -re ".*Starting .*: $" {
        set start_flag 1
        send $START_TIME\r }
     }
#
# Specify start date 
#
  if { $start_flag < 1 } {
   expect {
     -re ".*Starting UT.*: $" {
         send $START_TIME\r }
      }
  }
#
# Handle start date error OR specify STOP date
#
  expect {
    -re ".*Cannot interpret.*: $" {
       send X\r
       puts "\nError in $input_file date format: START_TIME= '$START_TIME'"
       puts "See Horizons documentation for acceptable format."
       puts " "
       exit 1 }
    -re ".*No ephemeris.*: $" {
       send X\r
       puts "\nInput file $input_file START_TIME= '$START_TIME' prior to available ephemeris"
       puts " "
       exit 1 }
    -re ".*Ending.*: $" {
       send $STOP_TIME\r }
     }
#
# Handle stop date error OR specify step size
#
  expect {
    -re ".*Cannot interpret.*" {
       send X\r
       puts "\nError in $input_file date format: STOP_TIME= '$STOP_TIME'"
       puts "See Horizons documentation for acceptable format."
       puts " "
       exit 1 }
    -re ".*No ephemeris.*" {
       send X\r
       puts "\nInput file $input_file STOP_TIME= '$STOP_TIME' date beyond available ephemeris."
       puts " "
       exit 1 }
    -re ".*Output interval.*: $" {
       send $STEP_SIZE\r }
   }
#
# Handle step-size error or specify output QUANTITIES
#
  expect { 
   -re ".*Unknown.*\n" {
       send X\r
       puts [string trimright "$expect_out(0,string)" "\n"]
       puts "Input file $input_file STEP_SIZE= '$STEP_SIZE' error."
       exit 1 }
   -re "Projected.*\n" {
       puts [string trimright "$expect_out(0,string)" "\n"]
       puts "Input file $input_file STEP_SIZE= '$STEP_SIZE' error (or date range too big)."
       send X\r
       exit 1 }
   -re ".*Accept default.*: $" {
       if { [string length $DEFAULTS] > 0 } {
         send N\r 
        } else {
         send Y\r
        }
       }
    } 
#
# Set output quantities
#
  expect {
   -re ".*Select table quantities.*: $" {
       send $QUANTITIES\r }
   }
#
# Get errors on input QUANTITIES, or start through rest of default settings, 
# or match FTP request if no default changes
#
  expect {
    -re ".*Unknown quantity requested.*" {
        send X\r
        puts "\nUnknown value in $input_file QUANTITIES list."
        puts " "
        exit 1 }
    -re ".*Must specify QUANTITIES to output.*" {
        send X\r
        puts "\nMust assign non-null value in $input_file QUANTITIES list."
        puts " "
        exit 1 }
    -re ".*Select.*: $" {
        send F\r }
    -re ".*] : $" {
        send \r }
   }
#
# Change defaults if requested 
#
  if { [string length $DEFAULTS] > 0 } {
   while 1 {
    expect { 
     -re "(Cannot interpret.*\r)" {
        send X\r
        puts " "
        puts "Error in $input_file"
        puts "$expect_out(0,string)" 
        puts "See Horizons documentation for acceptable values."
        puts " "
        exit 1 }
     -re ".*frame.*] : $"               { send $REF_SYSTEM\r          }
     -re ".*zone.*] : $"                { send $TIME_ZONE\r           }
     -re ".*time format.*] : $"         { send $CAL_FORMAT\r          }
     -re ".*time digits.*] : $"         { send $TIME_DIGITS\r         }
     -re ".*R.A. format.*] : $"         { send $ANG_FORMAT\r          }
     -re ".*high precision.*] : $"      { send $EXTRA_PREC\r          }
     -re ".*APPARENT.*] : $"            { send $APPARENT\r            }
     -re ".*units for RANGE.*] : $"     { send $RANGE_UNITS\r         }
     -re ".*Suppress RANGE_RATE.*] : $" { send $SUPPRESS_RANGE_RATE\r }
     -re ".*Minimum elevation.*] : $"   { send $ELEV_CUT\r            }
     -re ".*Maximum air.*] : $"         { send $AIRMASS\r             }
     -re ".*rise-transit-set.*] : $"    { send $R_T_S_ONLY\r          }
     -re ".*daylight.*] : $"            { send $SKIP_DAYLT\r          }
     -re ".*elongation cut-off.*] : $"  { send $SOLAR_ELONG\r         }
     -re ".*ngle cut-off.*] : $"        { send $LHA_CUTOFF\r          }
     -re ".*ngular rate cut-off.*] : $" { send $ANG_RATE_CUTOFF\r     }
     -re ".*CSV format.*] : $"          { send $CSV_FORMAT\r          }
     -re ".*] : $"                      { send \r                     } ;# Skip unknown (new?) prompt
     -re ".*Select.*: $"                break                           ;# Done with default overrides
    }
   }
  }
#
# Observer-table output has been generated. Now sitting at post-output prompt. 
# Initiate FTP file transfer.
#
  send F\r 
#
# Pick out FTP file name
#
  expect {
   -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_server
  expect {
     timeout {puts "Cancelled -- FTP server not responding."; exit 1 }
     -re "Name.*: $"
   } 
  send "anonymous\r"
  expect "Password:"
#
# Next bit supports HP-UNIX work-around
#
  set oldpw $EMAIL_ADDR
  if [regsub @ $oldpw '\134@' pw] {
    set newpw $pw
  } else {
    set newpw $oldpw
  }
  send $newpw\r
#
# Process FTP connection errors OR transfer output file to local machine
#
  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 ascii\r    } 
   }
  expect "ftp> " { send "cd pub/ssd\r" }
#
# set timeout -1   ;# This would disable timeout during actual transfer
#
  expect "ftp> " { send "get $ftp_name $local_file\r" }
  expect {
     -re ".*No such.*" {
       puts "\nError -- cannot find $ftp_name on server."
       puts " "
       exit 1 }
     "ftp> " { send "quit\r" }
   }
#
# Finished, set status code 
#
  exit 0
