=begin
 ******************************************************************************
 * Copyright 1990-2009 MBARI
 * MBARI Proprietary Information. All rights reserved.
 ******************************************************************************
 * Summary  : Behavior base class and factory method
 * Filename : behavior.rb
 * Author   : Henthorn
 * Project  : Benthic Rover
 * Version  : 1
 * Created  : Oct 2008
 * Modified :
 ******************************************************************************
=end

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

require 'utils/misc'
require 'utils/datalog'

##########
# Exceptions used by behaviors
#
class OkToContinue < Exception
end

class Hibernate < Exception
  attr_reader :duration

  def initialize(length_of_hibernation_in_minutes)
    @duration = length_of_hibernation_in_minutes
  end
end

class TimeOut < Exception
end

class AbortPlan < Exception
end

class AbortAllPlans < Exception
end

##########
# Behavior class contains state and default methods for the concept of
# a plan behavior.
# To create a new behavior, derive a new class from Behavior and replace the
# default methods with ones specific to your objectives. After creating a new
# type of behavior, you must add it to the BehaviorFactory class in order for
# it to be created from a plan or deployment file.
# 
class Behavior
  include SyslogWriter
  include Dump

  HERE = 0
  READY = 1
  EXECUTING = 2
  ABORTING = 3
  FINISHED = 4

  attr_reader :name, :state

  ##
  # Create Behavior husk
  #
  def initialize(p_behavior_element)

    ##
    # Deployment file attributes
    @name  = self.class.name
    @comment = ""
    ##

    @state = HERE
    @my_thread = nil
    @data_home = nil
    @b_status = false
  end
  #####

  #####
  # Use this method to save behavior state needed to resume
  # after hibernation or fatal exception.
  #
  def save_behavior_state()
    syslog("_ base class method: nothing to save")
  end
  #####

  #####
  # Standard usage format
  #
  def Behavior.usage_format
    "%16s  %32s  %10s  %16s\n"
  end

  #####
  # A crude documentation feature. Each subclass should provide
  # one that begins by calling this method. Make sure every
  # attribute available to the behavior is documented.
  #
  def Behavior.usage
    u  = sprintf("%-16s  %-32s  %-10s  %-16s\n", \
                 "Attribute Name","Description","Type","Default Value")
    u += sprintf("%-16s  %-32s  %-10s  %-16s\n", \
                 "----------------","--------------------------------", \
                 "----------","----------------")
    u += sprintf(Behavior.usage_format, \
                 "name","Behavior name","String","name of class")
    u += sprintf(Behavior.usage_format, \
                 "comment","Concise description","String","blank")
  end

  ##
  # Iterate over attributes in the element, extract parameters.
  #
  def load_attrs(p_behavior_element)
    attrs = p_behavior_element.attributes
    attrs.keys.each do |k|
      case k

        # Load base class attributes
        #
	when "name"
	@name =  attrs[k].to_s

	when "comment"
	@comment =  attrs[k].to_s

#       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")

    # Throw an exception if @my_thread != nil and @my_thread.alive?
    # Perform initialization procedures

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


  #####
  # Hibernating behaviors need a signal to load the previous
  # state.
  #
  def resume_init()
    raise "#{self.class} is unable to resume after hibernating"
  end
  #####


  #####
  # Behaviors report on their status.
  #
  def status()
    syslog(@b_status)
    return @b_status
  end
  #####


  #####
  # Clean-up method. Use this to restore state of vehicle
  # after behavior execution, whether successful or not.
  #
  def clean_up
  end
  #####

  #####
  # Run the execute method in a separate thread, return the thread object
  #
  def run
    self.execute
  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")
    end

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

    begin
      # Perform the behavior
    rescue
      # 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
  #####


  #####
  # My data files go in the plan data dir by default.
  # Specialized behaviors are free to point to new or other directories.
  #
  def data_home=(p_seq_num)
    @data_home = DataLog.plan_data_home
  end
  #####

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

end


##
# Standalone unit test (recommended)
#
if __FILE__ == $0

  if "usage" == ARGV[0]
    puts(Behavior.usage)
    exit(0)
  end

  require 'rexml/document'
  require 'control/sample_behavior'

  include SyslogWriter

  # Test code here
  # Typical behavior usage
  b = Behavior.new
  syslog("# Typical behavior usage")
  b.init
  b.execute
  b.abort
  sleep(1)

  # Typical behavior usage
  syslog("# Try to execute when not ready")
  b.execute
  b.abort
  sleep(1)

  # Make ready, then abort
  syslog("# Make ready, then abort")
  b.init
  b.init
  b.abort
  sleep(1)

  # Re-init and run again
  syslog("# Re-init and run again")
  b.init
  b.execute
  sleep(1)

  puts("Done")
end
