=begin
 ******************************************************************************
 * Copyright 1990-2009 MBARI
 * MBARI Proprietary Information. All rights reserved.
 ******************************************************************************
 * Summary  : Implemetation of the Rover OptodeMeasure behavior
 * Filename : optode_measure.rb
 * Author   : Henthorn
 * Project  : Benthic Rover
 * Version  : 1
 * Created  : Dec 08
 * Modified : Sep 10 - Using 'stir' program to control stir motors
 ******************************************************************************
=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'

##########
# OptodeMeasure class.
# Controls the optode measurement behavior of a Rover deployment.
#
class OptodeMeasure < Behavior
  include RoverTime

  attr_reader :name, :duration, :iterations, :current_iteration, :interval

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

    @iterations = 1          # Default iterations is 1
    @duration = 1            # Default duration is 1 minute
    @interval = 1            # Default interval is 1 minute
    @shutdown_interval = 3   # Minimum interval required to initiate shutdown
    @start_iteration = 1
    @current_iteration = 1
    @acquire_acm = true
    @acm_data = nil
    @acm_thread = nil
    @acm_log = nil
    @stirrers_on = true
    @take_images = false

    load_attrs(p_behavior_element)
  end
  #####


  #####
  # A crude documentation feature. Each subclass should provide
  # one that begins by calling this method.
  #
  def OptodeMeasure.usage
    u  = sprintf(Behavior.usage_format, \
                 "iterations","Number of measurements","Scalar","1")
    u += sprintf(Behavior.usage_format, \
                 "duration","Measurement duration","Minutes","1.0")
    u += sprintf(Behavior.usage_format, \
                 "interval","Interval between measurements","Minutes","1.0")
    u += sprintf(Behavior.usage_format, \
                 "stirrers_on","Stir motors on while measuring?","true/false","true")
    u += "\n<OptodeMeasure name=\"Example\" iterations=\"5\" duration=\"2\" "
    u += "interval=\"7.5\" "
    u += "comment=\"5 measurement iterations;2min x 7.5min\" />\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

        # Number of measurement iterations
        when "iterations"
        @iterations = attrs[k].to_i

        # Optode duration
        when "duration"
        @duration = attrs[k].to_f

        # Interval duration
        when "interval"
        @interval = attrs[k].to_f

        # Stir while measuring
        when "stir"
        @stirrers_on = attrs[k].to_s.downcase == "true"

        # Take images while measuring
        when "take_images"
        @take_images = attrs[k].to_s.downcase == "true"

        # Interval duration
        when "shutdown_interval"
        @shutdown_interval = 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
    @start_iteration = 1
    @state = READY
  end
  #####


  #####
  # Resume after hibernation
  #
  def resume_init()
    syslog( "_ resuming from hibernating #{@interval} minutes")

    ##
    # Get the saved state
    #
    bs = Supervisor.instance.deployment.dbif.load_behavior_data
    type = bs['type']
    unless (type == self.class.to_s)
      raise "Cannot resume! Mismatched Behavior types: #{type}!=#{self.class}"
    end

    name = bs['name']
    unless (name == @name)
      raise "Cannot resume! Mismatched Behavior names: #{name}!=#{@name}"
    end

    unless((si = bs['iteration']))
      raise "Cannot resume! No iteration data in behavior record"
    end

    ##
    # Refer to the existing data directory
    #
    DataLog.set_behavior_data_home = bs["log_dir"]

    @start_iteration = si
    console("_ Resuming OptodeMeasure @ iteration #{@start_iteration}")
    @state = READY
  end
  #####


  #####
  # Create directory to hold optode data files
  #
  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 + "-optodes"
  end
  #####


  #####
  # Capture images of the chambers
  #
  def chamber_images()
    ##
    # Capture images every tenth iteration
    #
    if ((@current_iteration % 50) == 0)
      ChamberImages.video_on()
      ChamberImages.capture_images("peek")
      ChamberImages.video_off()
      return true
    end
    return false
  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

      # Let's watch if we're on MARS
      #
      if (Supervisor.instance.deployment.mars)
        Rover.instance.ethernet(true)
        Rover.instance.relay.camerasOn
        Rover.instance.relay.videoOn
      end

      # Profile the current if configured
      #
      if (@acquire_acm)
        @acm_log = AcmRawLog.new(DataLog.behavior_data_home+"acm_raw.csv")
      end
      
      # Acquire_data - sleep or shutdown for the interval - repeat
      #
      begin_stirring()
      for @current_iteration in @start_iteration..@iterations
        syslog("_ iteration #{@current_iteration} of #{@iterations}")
        @b_status = "iteration #{@current_iteration} of #{@iterations}"

        ##
        # Save my state in case we crash during execution
        #
        save_behavior_state()

        ##
        # Capture images of chambers for visual evidence that
        # the stirring is actually occuring
        #
        chamber_images() if @take_images

        acquire_data()

        # No need to sleep after the last data acquisition
        #
        break if (@current_iteration >= @iterations)

        # Signal hibernation if configured to do so.
        # Otherwise just sleep
        #
        if (Supervisor.instance.deployment.hibernate && (@interval > 2.0))
          syslog("_ Signaling shutdown for #{@interval} minutes")
          # Save my state in the deployment database
          #
          syslog("_ initiating hibernation of #{@interval} minutes")
          save_behavior_state()

          # throw a hibernation exception
          raise Hibernate.new(@interval)

        else
          syslog("_ Simply wait here for #{@interval} minutes")
          r_sleep(@interval*60)
        end

      end  # iteration loop

      stop_stirring()
      @b_status = "done"

      self.clean_up
    ensure
      self.clean_up
    end
  end
  #####


  #####
  def save_behavior_state()
    bs = Hash["type", self.class.to_s,
      "name", @name,
      "log_dir", DataLog.behavior_data_home,
      "iteration", @current_iteration+1]
    Supervisor.instance.deployment.dbif.save_behavior_data(bs)
  end
  #####


  #####
  def begin_stirring
    return unless (@stirrers_on)
    system("stir ON")
  end
  #####


  #####
  def stop_stirring 
    return unless (@stirrers_on)
    system("stir OFF")
  end
  #####


  #####
  def acquire_data
    start_optodes()
    sleep(@duration*60)
    stop_optodes()
  end
  #####


  #####
  # Profile the current while accumulating optode data.
  # We do it here so the data resides with the optode data.
  #
  def acquire_acm_data(go)

    ##
    # Stop or go?
    #
    if (go)
      syslog("_ starting our own acm data stream")
      #
      # Worker thread acquires data from acm every 10 seconds
      # and writes to our log file.
      #
      @acm_thread = RoverThread.new do
        while (true)
          syslog("_ acquiring latest acm data")
      	  @acm_data = Rover.instance.acm.latest_data(@acm_data)
      	  @acm_log.write("#{@acm_data.date} #{@acm_data.time}",
      	                 @acm_data.head, @acm_data.volt, @acm_data.x,
      	                 @acm_data.y, @acm_data.north, @acm_data.east,
      	                 @acm_data.temp)
      	  sleep(10)
        end
      end

    # Stop acm data acquisition
    #
    else
      syslog("_ stopping our acm data stream")
      if (@acm_thread)
        @acm_thread.kill if (@acm_thread.alive?)
        @acm_thread = nil
      end
    end
  end
  #####


  #####
  # Start optode threads and turn on power
  #
  def start_optodes
    # Start Aanderaa drivers
    Rover.instance.port_ref_optode.run(DataLog.behavior_data_home)
    Rover.instance.port_optode.run(DataLog.behavior_data_home)
    Rover.instance.stbd_ref_optode.run(DataLog.behavior_data_home)
    Rover.instance.starboard_optode.run(DataLog.behavior_data_home)
    Rover.instance.relay.optodesOn
    Rover.instance.relay.s_refoptOn
    Rover.instance.relay.p_refoptOn
    
    if (@acquire_acm)
      Rover.instance.relay.acmOn
      sleep(1)
      Rover.instance.acm.run()
      acquire_acm_data(true)
    end
  end
  #####

  #####
  # Stop optode threads and turn off power
  #
  def stop_optodes
    # Stop Aanderaa drivers
    Rover.instance.relay.optodesOff
    Rover.instance.relay.s_refoptOff
    Rover.instance.relay.p_refoptOff
    Rover.instance.stbd_ref_optode.stop
    Rover.instance.starboard_optode.stop
    Rover.instance.port_optode.stop
    Rover.instance.port_ref_optode.stop
    
    if (@acquire_acm)
      acquire_acm_data(false)
      Rover.instance.relay.acmOff
      Rover.instance.acm.stop
    end
  end
  #####


  #####
  # Clean-up steps in the case of an exception or an abort
  #
  def clean_up
    stop_optodes()
    stop_stirring() unless (Supervisor.instance.deployment.hibernate)
    @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
  #####


  #####
  def OptodeMeasure.test
    require 'rexml/document'
    require 'device/rover'

    Rover.instance

    # Test code here
    # Straight ahead test
    puts("Monitor testlogs/syslog.csv")
    puts("Straight ahead test")
    measure_xml = REXML::Document.new(File.new(Misc.find_plan("test_measure.xml")))
    om = OptodeMeasure.new(measure_xml.root)
    om.init
    puts(om.dump)
    om.execute

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

  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(OptodeMeasure.usage)
    exit(0)
  end

  OptodeMeasure.test
  puts("Done")
end
