#--
#******************************************************************************
#* Copyright 1990-2007 MBARI
#* MBARI Proprietary Information. All rights reserved.
#******************************************************************************
#* Summary  : Contain the rover software component objects
#* Filename : measure.rb
#* Author   : Henthorn
#* Project  : Benthic Rover
#* Version  : 
#* Created  : May-06   Initial functionality
#* Modified : 2007     Gradual polishing (2 rack assemblies, improved operation)
#******************************************************************************
#++

require "rover"
require "rover_thread"

# Class Measure attempts to encapsulate the data and operations needed to
# operate the Rover during the measurement phases. Measure runs in its own
# thread.
#
class Measure < Monitor
  include LogHelper
  
  $DEFAULT_RECORD_TIME = "1"  	  # Time to record data from optode, in minutes
  $DEFAULT_RECORD_INTERVAL = "14" # Time in minutes to wait between optode reads
  $SHORT_RECORD_INTERVAL = "1"    # Time to wait on "short" mission cycle
  
  attr_reader :action, :thr, :record_time, :record_interval, :current_iter

  #========================================================
  def initialize (iterations=1)
    @thr   = 0
    @pct   = 0
    @abort = false
    @saved_iter = 0
    @current_iter = 1
    @action = "Measure:Initialized"
    @site_number = 1

    if iterations < 1
      syslog("! Measure: Iterations must be at least 1, not #{iterations}")
      @iterations = 1
    else
      @iterations = iterations
    end

    cm = Rover.instance.cm
    @record_time = cm.load_config_item("RecordingDuration", \
				       $DEFAULT_RECORD_TIME).to_f
    @record_interval = cm.load_config_item("RecordingInterval", \
				       $DEFAULT_RECORD_INTERVAL).to_f

    ci = cm.load_config_item("PowerOffSleeps", "False")
    @power_off_on = (ci == "True")

    # Load the state of the mission before board was put to sleep
    #
    @saved_iter = cm.load_config_item("SavedIteration").to_i
    @current_iter = @saved_iter if @power_off_on

    syslog("_ Measure: Number of iterations is #{@iterations}")
  end

  #========================================================
  # Reset the measure object for fresh set of iterations
  # (i.e., after a move)
  #
  def reset()
    @current_iter = 1
  end
  

  #========================================================
  # Run the measure process
  # If abort signal is received we simply jump down to homing the racks
  # and 100% complete
  #
  def run(site_num=1)
    @abort = false
  
    # Use site number to log device data in separate directory
    #
    @site_number = site_num
    @site_log_dir = "site" + @site_number.to_s

    Rover.instance.ref_optode.set_log_directory(@site_log_dir)
    Rover.instance.starboard_optode.set_log_directory(@site_log_dir)
    Rover.instance.port_optode.set_log_directory(@site_log_dir)

    # Start the measurement thread
    #
    @thr = RoverThread.new do
      Thread.current["name"] = "measure"
      set_percentage(0)
      if (@saved_iter > 1)
        pct = (96.0/@iterations)*(@saved_iter-1)
        set_percentage(pct)
      end
      
      # Capture images and lower the chambers before the
      # first iteration only
      #
      if (@saved_iter == 1)
        # Take image before measurement
        @action = "Measure:Taking First Image"
        self.image_capture("before")

	insert_chambers()

	# Take another image of the measurement site
	#
	@action = "Measure:Taking Second Image"
	self.image_capture("inserted")
      end

      # Acquire optode data
      # All iterations are executed here, as
      # are the sleep/power-off decisions
      #
      unless abort?
        acquireOptodeData
        self.image_capture("after")
      end

      # Home the chambers
      #
      home_chambers()

      @action = "Measure:Initialized"
      @pct = 100

    end      # thread
  end        # run


  #========================================================
  # Perform the incubation iterations and collect the data
  # 
  def acquireOptodeData
    relay  = Rover.instance.relay   # Handy reference
    
    # Begin stirring action
    #
    relay.s_stirOn
    relay.p_stirOn

    # Do one or more recording sessions (iterations) with
    # some time in between (intervals)
    #
    while (@current_iter <= @iterations)
      eventlog("_ commencing iteration #{@current_iter} of #{@iterations}")
      @action = "Measure:Acquiring Data (#{@current_iter} of #{@iterations})"
      
      start_recording()
      Rover.instance.rt.rSleep(@record_time*60)
      stop_recording()
      eventlog("_ Finished recording #{@current_iter} of #{@iterations}")

      iterPct = (70.0 / @iterations.to_f)
      incrPercentage(iterPct)
      
      # Are we done now?
      #
      if (@current_iter == @iterations || self.abort?)
        eventlog("_ All iterations complete")
        reset()   # reinit @current_iter
        break
      end
      
    	@current_iter += 1
      if(@power_off_on)
      	syslog("_ Exiting measure thread - will pick-up again in iteration #{@current_iter}")
    	  Thread.current.exit
      else
      	syslog("_ Sleeping #{@record_interval} minutes until next recording session")
        Rover.instance.rt.rSleep(@record_interval*60)
      end
    end

    # Stop stirring
    relay.s_stirOff
    relay.p_stirOff
  end
  

  #========================================================
  # Record data using all optodes
  #
  def start_recording()
    syslog("_ turning optodes/acm on and recording data")
    Rover.instance.relay.optodesOn
    Rover.instance.starboard_optode.run
    Rover.instance.port_optode.run
    Rover.instance.ref_optode.run

    # Record current data as well
    Rover.instance.relay.acmOn
    Rover.instance.rt.rSleep(3)
    Rover.instance.acm.run
  end


  #========================================================
  # Stop data recording threads and turn off optodes
  #
  def stop_recording()
    syslog("_ turning optodes/acm off")
    Rover.instance.starboard_optode.stop
    Rover.instance.port_optode.stop
    Rover.instance.ref_optode.stop
    Rover.instance.acm.stop_thread

    # Turn off the devices
    Rover.instance.relay.optodesOff
    Rover.instance.relay.acmOff
  end


  #========================================================
  # Capture an image of the sediment measure area
  #
  def image_capture name = "test"
    # Turn on video server
    #
    boot_wait_time = Rover.instance.cm.load_config_item("VideoBootTime", "30").to_f
    syslog("_ turning on video, waiting #{boot_wait_time} sec")
    Rover.instance.ethernet(true)
    Rover.instance.relay.videoOn
    Rover.instance.relay.camerasOn
    sleep(boot_wait_time) # Wait for server to boot

    # Use video server to grab an image and name the file. The
    # video driver ensures that the filenames are unique.
    #
    sname = @site_log_dir + "star_" + name
    unless (Rover.instance.video.capture_image($STBD_CAMERA, sname))
      eventlog("! Unable to capture starboard image of sediment")
    end
      
    pname = @site_log_dir + "port_" + name
    unless (Rover.instance.video.capture_image($PORT_CAMERA, pname))
      eventlog("! Unable to capture port image of sediment")
    end
      
    # Turn things off
    syslog("_ turning off video")
    Rover.instance.ethernet(false) if (!Clo.instance.sim && !Clo.instance.warp)
    Rover.instance.relay.camerasOff
    Rover.instance.relay.videoOff
  end


  #========================================================
  def insert_chambers()
    relay = Rover.instance.relay
    rack  = Rover.instance.rack
    relay.optodesOn

    # starboard chamber
    @action = "Measure:Lowering Chambers"
    eventlog("_ Inserting Starboard chamber")

    # Sample oxygen content during insertion
    #
    Rover.instance.starboard_optode.run
    Rover.instance.ref_optode.run

    # Lower the chamber
    #
    rack.reinit($STBD_RACK)
    relay.s_valveOn
    result = rack.move_to_insert($STBD_RACK)
          
    unless (result)
      eventlog("! Unable to insert starboard chamber in sediment")
      self.abort
    end

    # Turn appliances off
    #
    Rover.instance.starboard_optode.stop
    Rover.instance.ref_optode.stop
    relay.s_valveOff
    relay.s_rackOff
    incrPercentage(5)

    # Now port chamber
    eventlog("_ Inserting port chamber")

    # Sample oxygen content during insertion
    #
    Rover.instance.port_optode.run

    # Lower the chamber
    #
    rack.reinit($PORT_RACK)
    relay.p_valveOn
    result = rack.move_to_insert($PORT_RACK)
          
    unless (result)
      eventlog("! Unable to insert port chamber in sediment")
      self.abort
    end

    # Turn appliances off
    #
    Rover.instance.port_optode.stop
    relay.p_valveOff
    relay.p_rackOff
    incrPercentage(5)

    relay.optodesOff
  end


  #========================================================
  # Move chambers back to home position
  def home_chambers()
    ct = nil
    relay = Rover.instance.relay
    rack  = Rover.instance.rack
    @action = "Measure:Homing Chambers"

    # Move starboard chamber out of sediment to home position.
    # Open valves first to avoid creating a thick sediment cloud.
    #
    syslog("_ Home stbd chamber")
    relay.s_valveOn
    sleep(2)
    rack.reinit($STBD_RACK)
    ct = start_flush($STBD_RACK)
    rack.move_home($STBD_RACK)
  
    # Kill cleaning thread and turn off starboard equipment
    #
    ct.kill if ct.alive?
    ct.join
    relay.s_cleanOff
    relay.s_valveOff
    relay.s_rackOff
    incrPercentage(5)

    # Move port rack out of sediment
    #
    syslog("_ Home port chamber")
    relay.p_valveOn
    sleep(2)
    rack.reinit($PORT_RACK)
    ct = start_flush($PORT_RACK)
    rack.move_home($PORT_RACK)

    # Kill cleaning thread and turn off port side equipment
    #
    ct.kill if (ct && ct.alive?)
    ct.join
    relay.p_cleanOff
    relay.p_valveOff
    relay.p_rackOff
    incrPercentage(5)
  end


  #========================================================
  # This method is used to flush a chamber while it is being moved home.
  # The idea is to start a thread that simply waits about 20 seconds
  # before closing the valve and starting the cleaning pump.
  # The thread ends after turning on the pump, so it is the responsibility
  # of the caller to turn off the pump.
  #
  def start_flush(this_rack)
    cthread = RoverThread.new do   # Start of separate thread code
      Thread.current["name"] = "measure flush"
      sleep(20)

      # Close valve and switch on cleaning pump to replace
      # cloudy water volume with surrounding water.
      #
      syslog("_ Flushing chamber")
      if ($PORT_RACK == this_rack)
        Rover.instance.relay.p_valveOff
        Rover.instance.relay.p_cleanOn
      elsif ($STBD_RACK == this_rack)
        Rover.instance.relay.s_valveOff
        Rover.instance.relay.s_cleanOn
      else
        syslog("! start_flush must be told which chamber to flush")
      end
    end                            # End of separate thread code
    
    return cthread     # returned immediately, main thread continues
  end


  #========================================================
  # Other objects would like to know how much longer until I'm done, so
  # we use a percentage complete system to keep track.
  #
  def incrPercentage(inc)
    newPct = inc + @pct
    if (newPct < 100)
      @pct = newPct
    else
      @pct = 95
    end
    eventlog("_ Measure #{newPct} percent complete")
  end


  #========================================================
  def set_percentage(val)
    if (val < 100)
      @pct = val
    else
      @pct = 95
    end
  end


  #========================================================
  def abort?
    return @abort
  end

  #========================================================
  # Abort the measure process.
  #
  def abort
    eventlog("! Measure: Received abort command")
    @abort = true
    @action = "Measure:Aborting"
    
    # Kill the measure thread
    @thr.kill if @thr.alive?

    # Halt motors and turn everything oo
    Rover.instance.motors.halt
    Rover.instance.relay.allOff
    
    # Now, ensure racks are in safe position
    #
    Rover.instance.relay.s_valveOn
    Rover.instance.relay.s_rackOn
    sleep(2)
    Rover.instance.rack.reinit($STBD_RACK)
    Rover.instance.rack.move_home($STBD_RACK)
    Rover.instance.relay.s_valveOff
    Rover.instance.relay.s_rackOff

    Rover.instance.relay.p_valveOn
    Rover.instance.relay.p_rackOn
    sleep(2)
    Rover.instance.rack.reinit($PORT_RACK)
    Rover.instance.rack.move_home($PORT_RACK)
    Rover.instance.relay.p_valveOff
    Rover.instance.relay.p_rackOff

    return
  end
    
  #========================================================
  def percentComplete
  #  self.synchronize do
      return @pct
   # end
  end

  #========================================================
  def currentAction
    return @action
  end
  
  #========================================================
  def percentComplete=p
#    self.synchronize do
      op = @pct
      @pct = p
      return op
 #   end
  end
  
end        # class Measure
