#!/usr/local/bin/ruby -w

require "rover"
require "rover_utils"
require "mission"
#require "mission_list"

require "clo"             # Command-line option parser


# This class encapsulates the main program functionality of parsing the
# command line, starting the top-level threads, and running the mission.
#
# Methods
#  * public run() - Begin the Rover control system
#
#  * protected execute_mission_series(list_file.rb) - Load Ruby file that
#    contain the list of mission configuration files.
#
#  * protected comms_loop() - Kicks-off a communication loop used to send and receive
#    messages from 'home' (abort, execute new series, request for status, etc)
#
class Main
  include LogHelper

  # State variables to export
  #
  attr_reader :start_time

  #           **********  Public methods  **********
  #
  public

  #========================================================
  #
  def run()

    # Begin by parsing command line.
    # We won't catch any exceptions from clo at this point, ensuring that
    # the user must correct the errors evident from start-up
    #
    puts(@start_time = Time.now.gmtime)
    syslog("main: parsing command line..."); sleep(0.5)

    @clo = Clo.instance
    @clo.parse
    puts(@clo.to_s)

    # Instantiate the Rover object
    # TODO: Request the Rover to perform basic sanity tests
    #
    time_now = Time.now.to_s
    syslog("main: creating Rover object at #{time_now}..."); sleep(0.5)
    @rover = Rover.instance

    # Start comms loop
    # Listen for instrucions from 'home'
    # Periodically report status
    # 
    comms_loop()

    # Start series execution loop if one exists.
    # Iterate through the mission series, running each in turn.
    #
    
    # Log battery voltage during the deployment
    #
    @rover.battery.run
    
    run_mission_series(@clo.mission_list_file)
    syslog("main: let's wait for series to conclude")
    @series_thread.join

    # Join with comms thread. This effectively lets the
    # comms thread have a chance to dispatch instructions
    # from home since it can continue to run after the series
    # of missions is complete.
    #
    syslog("main: let's wait for the comms loop to conclude")
    @comms_thread.join if @comms_thread

    syslog("main: all done, quit or go to sleep")
  end



  #           **********  Protected methods  **********
  #
  protected

  #========================================================
  # Communications loop
  # Separate thread to monitor modem traffic.
  # Messages from 'home' may request to abort the current
  # mission series, and/or begin another series, ask for
  # current status, etc.
  #
  def comms_loop()
    # Begin comms thread
    #
    @comms_thread = RoverThread.new do

      syslog("main: comms_loop starting")
      Thread.current["name"] = "comms"
      # If this is a sim, don't bother to start a real
      # comms loop.
      #
      if (@clo.sim)
        sim_comms_loop()

      else
        # Check modem function before starting
        #
        @rover.modem.init
        @rover.modem.write("Mission running")
        while (true)
          begin
            sleep(30)
            
            # If data is present on the modem port, read it and process
            if @rover.modem.dataPresent?
              msg = @rover.modem.readData(1)
              if msg.length == 0
                syslog("No new data on modem")
              else
                msg.strip!
                len = msg.length
                syslog("_ Received data on modem: #{msg}, #{len}")
                t = Time.now().strftime("%Y%m%d-%H:%M:%S")
                @rover.modem.write("Rover copy message [#{msg}] at #{t}\n")
                dispatch_command(msg)
                msg = ""
              end
            else
#              syslog("No modem traffic")
            end

            
            rescue Exception
              syslog("main: unhandled exception in comms loop")
              syslog("      #{$!}")
              syslog("main: comms loop continuing...")
          end  # Try block

        end    # Comms loop

      end

    end        # Comms thread

  end          # method


  # Surface Command dispatcher
  #
  ABORT_CMD   = "BR$ABORT"
  NEXT_CMD    = "BR$NEXT"
  RESTART_CMD = "BR$RESTART"
  STATUS_CMD  = "BR$STATUS"
  KILL_CMD    = "BR$KILL"

  def dispatch_command(cmd)
    return unless cmd
    if cmd.eql?(NEXT_CMD)
      syslog("main: Received NEXT command")
      if (@mission && mission_running?)
        @mission.abort
      else
        @rover.modem.write("No mission running")
      end
    elsif cmd.eql?(ABORT_CMD)
      syslog("main: Received ABORT command")
      if (@mission && mission_running?)
        @mission.abort
        @abort_series = true
      else
        @rover.modem.write("No mission running")
      end
    elsif cmd.eql?(STATUS_CMD)
      if (@mission && mission_running?)
        @rover.modem.write(@mission.status)
      else
        @rover.modem.write("No mission running")
      end
    elsif cmd.eql?(RESTART_CMD)
      syslog("main: Received RESTART command")
      @rover.modem.write("Reinitializing config files and rebooting...")
      `cp /cf/rover/lib/configs/*.xml /cf/rover/lib/config_lib/.`
      sleep(2)
      `reboot`
    elsif cmd.eql?(KILL_CMD)
      syslog("main: Received KILL command")
      @rover.modem.write("Slaying rover processes - Goodbye cruel world")
      sleep(2)
      `/cf/rover/lib/slaym`  # This is Rovercide
    else

      @rover.modem.write("#{cmd}? No handler for that command")
    end    
  end
  
  #========================================================
  # Simulated comms loop()
  # Perhaps open a socket for comms, or catch a signal
  #
  def sim_comms_loop()
    syslog("main: comms_loop sim")
    sleep(20)

  end

  def mission_running?
    return (@mission && @mission.mission_thread &&
        @mission.mission_thread.alive? && !@mission.done?)
  end
  
  #========================================================
  # Run the series of missions in the mission list file
  # in a separate thread
  # Missions are represented by config files that are
  # enumerated in a MissionList object.
  #
  def run_mission_series(mission_list_file)
    
  begin
    # Run only if there is a mission file
    #
    unless (mission_list_file && File.file?(mission_list_file))
      syslog("main: No mission list file")
      return
    end

    # HACK: Sometimes the mission file loads OK but
    # MissionList is undefined??! If that happens
    # attempt to load the file again
    #
    for j in 1..3
      # Load the mission series
      #
      unless (load(mission_list_file))
        syslog("main: Failed to load #{mission_list_file}")
        return
      end
      break if (defined?(MissionList))
      
      syslog("main: #{mission_list_file} loaded but MissionList undefined")
      sleep(1)
    end
    return unless (defined?(MissionList))
    
    @series_thread = RoverThread.new do
      Thread.current["name"] = "series"

      # Run each mission in the series
      #
      syslog("main: executing missions listed in #{mission_list_file}")
      series = MissionList.new
      series.list.each do |mname|

      begin
      # Create a Mission object using this config file
      # and run it
      #
      syslog("main: running #{mname}")
      @mission = RoverMission.new(mname)
      @mission.run

      syslog("main: watch the mission")
      while(mission_running?)
        sleep(60)
        syslog("main: #{mname} still running")
      end
      syslog("main: done with #{mname}")
      
      if (@abort_series)
        syslog("main: aborting series")
        @abort_series = false
        break
      end
      
      rescue Exception
      exc = $!
        syslog("main: unhandled exception in run_mission_series")
        syslog("      #{exc}")
        syslog(exc.backtrace)
        syslog("main: continuing...")
        next

      end # Inner try block

    end   # series loop

    syslog("main: done with #{mission_list_file}")

  end # series_thread


    rescue Exception
      syslog("main: unhandled exception in run_mission_series")
      syslog("      #{$!}")
      syslog("main: returning...")
    end   # Outer try block

  end   # run_mission_series()

end

#
# Run main program
#
Thread.current["name"] = "main"
main = Main.new

main.run
