=begin
 ******************************************************************************
 * Copyright 1990-2010 MBARI
 * MBARI Proprietary Information. All rights reserved.
 ******************************************************************************
 * Summary  : Perform a vehicle checkup by running basic tests on subsystems
 * Filename : vehicle_checkup.rb
 * Author   : Henthorn
 * Project  : Benthic Rover
 * Version  : 1
 * Created  : Oct 2010
 * Modified :
 ******************************************************************************
=end

##
# Refer to Rover-code home directory as base
#
require "#{ENV['ROVER_HOME']}/utils/rover_environment"
#

require 'control/behavior'
require 'utils/misc'
require 'utils/datalog'
require 'device/rover'

##########
# 
class VehicleCheckup < Behavior
  include SyslogWriter

  attr_reader :name, :state

  def initialize(p_behavior_element)
    super
  end
  #####


  #####
  # A crude documentation feature. Each subclass should provide
  # one that begins by calling this method.
  #
  def VehicleCheckup.usage
    u = "\n<VehicleCheckup name=\"Example\" comment=\"Perform checkup\" />\n"
  end


  ##
  # Iterate over attributes in the element, extract parameters.
  #
  def load_attrs(p_behavior_element)
    super

    attrs = p_behavior_element.attributes
    attrs.keys.each do |k|

#      case k

#       else
#       puts("Unrecognized attr: #{k}")

#      end
 
    end

    # Option: Throw an exception if there any required attributes
    # are missing
    #
  end
  #####

  #####
  # Initialize behavior and make ready for execution.
  # Should be called just before executing the behavior.
  #
  def init(p_seq_number=1)   # Where in the plan behavior sequence I am
    syslog("_ #{@name} init")
    self.data_home = p_seq_number
    
    # Throw an exception if @my_thread != nil and @my_thread.alive?
    # Perform initialization procedures

    @my_thread = nil
    @state = READY
  end
  #####


  #####
  # Set the data directory to /tmp
  #
  def data_home=(p_seq_num)
#    mn_zero = p_seq_num > 9 ? "" : "0"
#    home_prefix = mn_zero + p_seq_num.to_s
#    DataLog.behavior_data_home = home_prefix + "-checkup"
    DataLog.set_behavior_data_home = "/tmp/"
  end
  #####


  #####
  # Clean-up method. Use this to restore state of vehicle
  # after behavior execution, whether successful or not.
  #
  def clean_up
    Rover.instance.ethernet(false)
    Rover.instance.relay.allOff
  end
  #####

  #####
  # Run the execute method in a separate thread, return the thread object
  #
  def run
    @dbif = Supervisor.instance.dbif
    self.execute
  end


  #####
  # General state retrieval method.
  #
  def get_component_state(name)
    state = @dbif.load_component_state(name)
    if (state)
      return state
    else
      syslog("! #{name} health record not found")
      return nil
    end
  end
  #####


  #####
  # General state update method.
  #
  def update_component_state(name, new_state)
    new_state[RoverStore::Date] = Time.now
    unless(@dbif.save_component_state(name, new_state))
      syslog("! #{name} health update failed")
    end
  end
  #####


  #####
  # Look at current health status of the device. Return the
  # status record if check-up should proceed. Otherwise, return nil.
  #
  def do_i_perform_check(component_name)
    state = get_component_state(component_name)
    return nil unless (state)

    ##
    # Return the state if the status is OK - that is, run the check-up
    #
    health = state[RoverStore::Health]
    return state unless (health)       # nil means OK

    ##
    # The default logic is:
    # If the health of a device was not-OK at the last
    # check-up, then it remains not-OK. That is, the device
    # cannot be relied upon if it fails a test even
    # once (e.g., Relays).
    #
    # However, some devices can exhibit spotty behavior
    # and it is all right to check them again and perhaps
    # update the status to OK. This is the spot to trap those
    # devices and conditions (e.g., Hesc)
    #
    if (RoverStore::Hesc == component_name)
      state[RoverStore::Health] = nil
      return state
    elsif (RoverStore::Acm == component_name)
      state[RoverStore::Health] = nil
      return state
    elsif (RoverStore::PRack == component_name || \
          (RoverStore::SRack == component_name))
      # The rack is still useable if the insert switch on the rack is bad
      return state if (RoverStore::InsertSwitch == health)
    end

    return nil
  end


  NumAttempts = 3

  #####
  # Run health check-ups on the optodes.
  #
  def check_devices()

    ##
    # Set-up some arrays to generalize the check-up procedure
    #
    rv = Rover.instance
    relay = rv.relay

    ##
    # These arrays must align properly for the check-ups to function
    # correctly.
    #
    op_name   = [RoverStore::Relays,   RoverStore::Hesc, \
                 RoverStore::POptode,  RoverStore::SOptode, \
                 RoverStore::PROptode, RoverStore::SROptode, \
                 RoverStore::Acm,      RoverStore::ChamCam, \
                 RoverStore::TransCam, RoverStore::FluoroCam, \
                 RoverStore::PRack,    RoverStore::SRack, \
                 RoverStore::Motors]
#                 RoverStore::Motors,   RoverStore::Modem]

    op_device = [:relay,           :hesc, \
                 :port_optode,     :starboard_optode, \
                 :port_ref_optode, :stbd_ref_optode, \
                 :acm,             :video, \
                 :transit_cam,     :fluoro_cam, \
                 :port_rack,       :stbd_rack, \
                 :motors]
#                 :motors,          :modem, \
#                 :hesc]

    on_relay  = [nil,         nil,         :optodesOn,  :optodesOn,
                 :p_refoptOn, :s_refoptOn, :acmOn,      :videoOn, \
                 :transitOn,  :fluoroOn,   :p_rackOn,   :s_rackOn, \
                 :motorsOn,   nil,         nil]

    off_relay = [nil,          nil,          :optodesOff, :optodesOff, \
                 :p_refoptOff, :s_refoptOff, :acmOff,     :videoOff, \
                 :transitOff,  :fluoroOff,   :p_rackOff,  :s_rackOff, \
                 :motorsOff,   nil,          nil]

    ##
    # Now iterate over the devices
    #
    op_name.each_index do |op|
      begin
        ##
        # Perform check-up on the device
        #
        next unless (state = do_i_perform_check(op_name[op]))

        for i in 1..NumAttempts     # Give it multiple tries
          t = nil
          relay.send(on_relay[op]) if (on_relay[op])
          sleep(1)
          syslog("_ #{op_name[op]} check_up attempt #{i}")

          ##
          # check_up method returns nil if OK. Otherwise, it
          # returns the cause of the failure.
          #
          unless (t = rv.send(op_device[op]).check_up)
            break   # Check-up passed
          end
          relay.send(off_relay[op]) if (off_relay[op])
        end
      rescue
        syslog("! caught exception during #{op_state[op]}check_up")
      ensure
        relay.send(off_relay[op]) if (off_relay[op])
      end

      ##
      # If the check-up failed, record the cause.
      #
      if (t)
        syslog("_ updating health to #{t}")
        state[RoverStore::Health] = t
        update_component_state(op_name[op], state)
        ##
        # If relay boards are messed-up, don't bother with the rest
        #
        if (op_name[op] == RoverStore::Relays)
          break
        end
      else
        ##
        # Otherwise, use the original state and just update the time
        #
        syslog("_ just updating timestamp")
        update_component_state(op_name[op], state)
      end
    end

  end
  #####

  
  ##
  # Execute the behavior
  #
  def execute
    # Execute if ready.
    # This need not be enforced by behaviors, just an example.
    # Alternatively, you could run init method if not ready.
    #
    unless (@state == READY)
      syslog("_ #{@name} not ready to execute")
      return
    end

    syslog("_ #{@name} execute")
    @state = EXECUTING

    begin
      Rover.instance.ethernet(true)
      check_devices()
      
      # Handle exceptions
    ensure
      self.clean_up
      @state = FINISHED
    end
  end
  #####


  ##
  # Abort the behavior
  #
  def abort
    syslog("_ #{@name} abort")
    if (@state == EXECUTING)
      @state = ABORTING 

      # Perform aborting procedure
      @my_thread.kill if @my_thread
    else
      syslog("_ #{@name} not executing, aborting...")
    end
    @state = FINISHED
  end
  #####


  #####
  def data_home
    return @data_home
  end
  #####

end


##
# Standalone unit test (recommended)
#
if __FILE__ == $0
  if "usage" == ARGV[0]
    puts(Behavior.usage)
    puts(VehicleCheckup.usage)
    exit(0)
  end

end
