#--
#******************************************************************************
#* 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

  def initialize (iterations=1)
    @record_time     = $DEFAULT_RECORD_TIME
    @record_interval = $DEFAULT_RECORD_INTERVAL
    @thr   = 0
    @pct   = 0
    @abort = false
    @action = "Measure:Initialized"
    @site_number = 1
    @site_log_dir = "site" + @site_number.to_s

    if iterations < 1
      syslog("! Measure: Iterations must be at least 1, not #{iterations}")
      @iterations = 1
    else
      @iterations = iterations
    end
    cm = Rover.instance.cm

    if (ci = cm.get("RecordingDuration"))
      @record_time = ci.to_f
    end
    if (ci = cm.get("RecordingInterval"))
      @record_interval = ci.to_f
    end
    syslog("_ Measure: Number of iterations is #{@iterations}")
  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
    end
    eventlog("_ Measure #{newPct} percent complete")
  end
  
  # Perform the incubation iterations and collect the data
  # 
  def acquireOptodeData
    relay  = Rover.instance.relay   # Handy reference
    
    ref_optode = Rover.instance.ref_optode
    s_optode = Rover.instance.starboard_optode
    p_optode = Rover.instance.port_optode

    # Kind of a kludge, but we're placing optode data at different
    # sites into different log directories
    #
    ref_optode.set_log_directory(@site_log_dir)
    s_optode.set_log_directory(@site_log_dir)
    p_optode.set_log_directory(@site_log_dir)

    #Begin stirring action
    relay.s_stirOn
    relay.p_stirOn

    # Do one or more recording sessions (iterations) with
    # some time in between (intervals)
    #
    iter = @iterations
    eventlog("_ commencing #{iter} iterations")
    while (true)
      currentIter = 1 + (@iterations - iter)
      @action = "Measure:Acquiring Data (#{currentIter} of #{@iterations})"
      
      # Start recording
      #
      syslog("_ turning optodes/acm on and recording data")
      relay.s_optodeOn      # turns on both starboard and reference optodes
      relay.p_optodeOn
      s_optode.run
      p_optode.run
      ref_optode.run

      # Record current data as well
      relay.acmOn
      sleep(3)
      Rover.instance.acm.run
      Rover.instance.rt.rSleep(@record_time*60)
      
      # Stop oxygen and current data collection
      #
      syslog("_ turning optodes/acm off")
      s_optode.stop
      p_optode.stop
      ref_optode.stop
      Rover.instance.acm.stop_thread

      # Turn off the devices
      relay.s_optodeOff    # turns off both starboard and reference optodes
      relay.p_optodeOff
      relay.acmOff

      eventlog("_ Finished recording #{currentIter} of #{@iterations}")

      iterPct = (70.0 / @iterations.to_f)
      incrPercentage(iterPct)
      
      # Are we done now?
      #
      iter -= 1
      if (iter == 0 || self.abort?)
        eventlog("_ All iterations complete")
        break
      end
      
      if(Rover::TEST)
        syslog("_ Sleeping #{$SHORT_RECORD_INTERVAL} minutes until next recording session")
        Rover.instance.rt.rSleep($SHORT_RECORD_INTERVAL*60)
      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
  
  def reinit_optodes
    Rover.instance.starboard_optode = Aanderaa.new nil, "/dev/starboard_optode"
    Rover.instance.port_optode = Aanderaa.new nil, "/dev/port_optode"
    Rover.instance.ref_optode = Aanderaa.new nil, "/dev/ref_optode"
  end

  # Capture an image of the sediment measure area
  #
  def image_capture name = "test"
      # Turn on video server and capture an image of the measurement site
      #
      boot_wait_time = 30
      if (ci = Rover.instance.cm.get("VideoBootTime"))
        boot_wait_time = ci.to_f
      end
      
      syslog("_ turning on video, waiting #{boot_wait_time} sec")
      Rover.instance.relay.videoOn
      Rover.instance.relay.camerasOn
      Rover.instance.rt.rSleep(30)               # 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.relay.camerasOff
      Rover.instance.relay.videoOff
  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(p_or_s)
    # We're going to flush the chambers while the rack is moving back
    # to the home position, by giving it 20 seconds to lift the
    # out of the sediment before closing the valves and turning
    # on the cleaning pump
    #
    
    cthread = RoverThread.new do   # Start of separate thread code
      sleep(30)

      # Close valve and switch on cleaning pump to replace
      # cloudy water volume with surrounding water.
      #
      syslog("_ Flushing chamber")
      if ($PORT_RACK == p_or_s)
        Rover.instance.relay.p_valveOff
        Rover.instance.relay.p_cleanOn
      elsif ($STBD_RACK == p_or_s)
        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
  
      
  # Run the measure process
  # If abort signal is received we simply jump down to raising the rack
  # and 100% complete
  #
  def run(site_num=1)
  
    # Use site number to log device data in separate directory
    #
    @site_number = site_num
    @site_log_dir = "site" + @site_number.to_s

    # Initialize some internal values
    #
    @abort = false


    @thr = RoverThread.new do
    
      @pct = 0
      
      relay = Rover.instance.relay
      rack  = Rover.instance.rack
      
        
      # Take image before measurement
      @action = "Measure:Taking First Image"
      self.image_capture("before")

      # Open chamber valves and lower chamber into sediment
      # FIXME: We could save power by waiting before opening valves
      #
      unless abort?
        @action = "Measure:Lowering Starboard Chamber"
        eventlog("_ Inserting Starboard 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
      
        relay.s_valveOff
        relay.s_rackOff
        incrPercentage(5)
      end

      # Now port chamber
      unless abort?
        @action = "Measure:Lowering Port Chamber"
        eventlog("_ Inserting port 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

        relay.p_valveOff
        relay.p_rackOff
        incrPercentage(5)
      end

      unless abort?
        # Take another image of the measurement site
        #
        @action = "Measure:Taking Second Image"
        self.image_capture("inserted")
      end
      
      unless abort?
        # Measure the oxygen in the chamber
        acquireOptodeData
        self.image_capture("after")
      end
      
      if abort?
        @action = "Measure:Aborting - Homing Chambers"
      else
        @action = "Measure:Homing Chambers"
      end

      ###############################################################
      #        Move chambers back to home position
      ###############################################################
  
      # 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?
      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.alive?
      relay.p_cleanOff
      relay.p_valveOff
      relay.p_rackOff
      incrPercentage(5)

      if abort?
        @action = "Measure:Aborted"
      else
        @action = "Measure:Initialized"
      end
      @pct = 100

    end      # thread
  end        # run

  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
  
private 
  
  def percentComplete=p
#    self.synchronize do
      op = @pct
      @pct = p
      return op
 #   end
  end
  
end        # class Measure
