=begin
 ******************************************************************************
 * Copyright 1990-2009 MBARI
 * MBARI Proprietary Information. All rights reserved.
 ******************************************************************************
 * Summary  : Implemetation of the Rover launch behavior
 * Filename : launch.rb
 * Author   : Henthorn
 * Project  : Benthic Rover
 * Version  : 1
 * Created  : Dec 08
 * Modified : 
 ******************************************************************************
=end

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

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

##########
# Launch class.
# Controls the launch behavior at the start of a Rover deployment. Usually the
# first behavior in a Deployment plan. Consists mostly of waiting around.
# The only tricky part is venting the chambers.
#
class Launch < Behavior
  include RoverTime

  #####
  # Accept a behavior element and extract parameters
  #
  def initialize(p_behavior_element)
    super

    @duration = 1     # Default Launch duration is 1 minute
    @vent_flag = true # Vent chambers by default
    @vent_dur = 5     # Minimum duration in order to vent chambers (minutes)
    @sat_delta = 4    # Default saturation delta

    load_attrs(p_behavior_element)
  end
  #####


  #####
  # A crude documentation feature. Each subclass should provide
  # one that begins by calling this method.
  #
  def Launch.usage
    u  = sprintf(Behavior.usage_format, \
                 "duration","Duration of launch dive","Minutes","1")
    u += sprintf(Behavior.usage_format, \
                 "vent_flag","Vent chambers during launch?","true/false","true")
    u += sprintf(Behavior.usage_format, \
                 "sat_delta","Saturation delta to open vents","% O2 sat","4")
    u += "\n<Launch name=\"Example\" duration=\"150\" sat_delta=\"2\" comment=\"2.5 hour dive;open vents when optode reads delta of 2%\" />\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

        # Total duration of the launch
        when "duration"
        @duration = attrs[k].to_f

        # Minimum duration needed for venting the chambers upon launch
        when "vent_duration"
        @vent_dur = attrs[k].to_f

        # Do not vent chambers if this attr is present (vent by default)
        when "no_vent"
        @vent_flag = false

        # When the saturation delta exceeds this amount, assume we're in water
        when "sat_delta"
        @sat_delta = attrs[k].to_f

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

      end
    end

    # Option: Throw an exception if there any required attributes
    # 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

    # Perform initialization procedures
    @state = READY
  end
  #####


  #####
  # Execute the behavior
  #
  def execute
    # Execute if ready.
    unless (@state == READY)
      syslog("! #{name} not in ready state")
      return
    end

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

    ##
    # Perform the behavior
    begin

      ##
      # Vent chambers if necessary
      #
      seconds_remaining = self.vent_chambers()
      syslog("_ venting complete w/remaining duration of #{seconds_remaining}")

      @b_status = "descending after venting chambers"

      ##
      # Sleep the rest of the way down
      r_sleep(seconds_remaining)
      syslog("_ #{@name} complete")
    ensure
      self.clean_up
    end
    @b_status = "done"
  end
  #####


  #####
  # Vent the chambers if the dive time is more the vent duration
  # by opening the chamber valves after we enter the water. Assume
  # we're on the deck to start with.
  #
  # Return the remaining seconds left in the total launch duration.
  #
  def vent_chambers()
    remaining = @duration*60

    ##
    # Behavior may be set for no venting.
    # Check if we have at least the minimum time for venting
    #
    return remaining unless (@vent_flag || (@duration > @vent_dur))

    @b_status = "waiting-to-vent-chambers"

    ##
    # We're on the deck, so start Aanderaa driver and take a
    # baseline optode reading in air.
    #
    self.start_optodes()
    r_sleep(12)
    remaining -= 12

    this_reading = initial_reading = Rover.instance.stbd_ref_optode.sat
    syslog("_ Optode baseline reading is #{initial_reading}")

    ##
    # We're in the water when the optode reading changes
    # by the threshold value.
    #
    # Monitor optode value, breaking
    # out of loop when threshold reached, or when we've
    # waited the maximum vent duration.
    # We've timed-out when the remaining time is less than times_up.
    #
    internal = 10  # seconds
    times_up = remaining - @vent_dur*60
    while ((initial_reading-this_reading).abs < @sat_delta)
      if (remaining <= times_up)
        syslog("_ vent duration exceeded")
        break
      end

      # Sleep, wakeup, take an optode reading on ref optode
      #
      r_sleep(internal)
      remaining -= internal
      this_reading = Rover.instance.stbd_ref_optode.sat
    end
    syslog("_ Optode threshold #{this_reading} or vent_duration reached")

    # Stop aanderaa drivers, open the chambers for a minute to
    # let trapped air escape.
    #
    stop_optodes()
    open_valves()

    @b_status = "venting-chambers"

    r_sleep(60)
    remaining -= 60

    # Close valves when finished
    #
    close_valves()

    remaining = 0 if (remaining < 0)
    return remaining
  end
  #####


  #####
  # Open chamber valves
  #
  def open_valves()
    syslog("_ Opening chamber valves")
    Rover.instance.relay.p_valveOn
    Rover.instance.relay.s_valveOn
  end

  #####
  # Close chamber valves
  #
  def close_valves()
    syslog("_ Closing chamber valves")
    Rover.instance.relay.p_valveOff
    Rover.instance.relay.s_valveOff
  end


  #####
  # Start optode threads and turn on power
  #
  def start_optodes
    # Start Aanderaa drivers
    #Rover.instance.port_optode.run
    #Rover.instance.starboard_optode.run
    Rover.instance.stbd_ref_optode.run
    Rover.instance.relay.s_refoptOn
  end
  #####

  #####
  # Stop optode threads and turn off power
  #
  def stop_optodes
    # Stop Aanderaa drivers
    Rover.instance.relay.s_refoptOff
    Rover.instance.stbd_ref_optode.stop
    #Rover.instance.port_optode.stop
    #Rover.instance.starboard_optode.stop
  end
  #####


  #####
  # Clean-up steps in the case of an exception or an abort
  #
  def clean_up
    stop_optodes
    # Close valves
    Rover.instance.relay.p_valveOff
    Rover.instance.relay.s_valveOff
    @state = FINISHED
  end
  #####

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

      # Kill the run thread, clean up
      @my_thread.kill if @my_thread
      clean_up
    else
      syslog("_ #{@name} not executing, aborting...")
    end
  end
  #####



end

##
# Standalone unit test (recommended)
# Include at the end of the file as a hook to run a unit test of
# the code from the command line (e.g., "$ ruby  my_class.rb")
#
if __FILE__ == $0
  if "usage" == ARGV[0]
    puts(Behavior.usage)
    puts(Launch.usage)
    exit(0)
  end

  require 'rexml/document'
  require 'device/rover'

  Rover.instance

  # Test code here
  # Straight ahead test
  puts("Monitor testlogs/syslog.csv")
  puts("Straight ahead test")
  launch_xml = REXML::Document.new(File.new(Misc.find_plan("test_launch.xml")))
  lb = Launch.new(launch_xml.root)
  lb.init
  puts(lb.dump)
  lb.execute

  # Abort test in thread
  puts("Abort test")
  lb.init
  t=lb.run
  sleep(2)
  puts("Aborting now...")
  lb.abort
  t.join

  # Add negative delta value to trigger venting
  puts("delta test")
  launch_xml.root.add_attribute("sat_delta","-1.0")
  lb = Launch.new(launch_xml.root)
  lb.init
  puts(lb.dump)
  lb.execute

  # Add no_vent option and run again
  puts("no-vent test")
  launch_xml.root.add_attribute("no_vent","")
  lb = Launch.new(launch_xml.root)
  lb.init
  t=lb.run
  sleep(2)
  puts("Aborting now...")
  lb.abort
  t.join

  puts("Done")
end
