=begin
 ******************************************************************************
 * Copyright 1990-2009 MBARI
 * MBARI Proprietary Information. All rights reserved.
 ******************************************************************************
 * Summary  : Miscellaneous utilities
 * Filename : misc.rb
 * Author   : Henthorn
 * Project  : Benthic Rover
 * Version  : 1
 * Created  : Oct 2008
 * Modified : May 2014  RGH  Replaced HESC-SERD with Wakey
 ******************************************************************************
=end

require 'singleton'
require 'pstore'

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

##########
# Misc class - class to contain various helpful methods
#
class Misc

  ##
  # Return full path to plan directory
  #
  def Misc.plan_home
    if (lh = ENV['ROVER_PLANS'])
      return lh
    else
      return "#{ENV['ROVER_HOME']}/plans/"
    end
  end

  ##
  # Use search path to locate plan file with given 'basename'.
  # Return a File object if found, otherwise nil.
  # A basename is the simple file name.
  # 'planfile.xml' is a basename.
  # '/tmp/planfile.xml' and '../plans/planfile.xml' are not basenames
  #
  @@plan_path = ["./", Misc.plan_home, ""]
  def Misc.find_plan(p_basename)
    # Return first matching file
    @@plan_path.each do |p|
      file_path = p + "#{p_basename}"
      return file_path if (File.exist?(file_path))
    end
 
    # Return true if attempting to resume
    #
    if (p_basename.eql?("-r"))
      return true
    end

    nil   # Arrive here if file not found
  end

  #####
  # Use system calls to see if a process is currently running.
  # Return an array of arrays containing the process name and process
  # id of all processes that contain the query string. Returns nil if
  # no matches found.
  # Example:
  #    match_procs("drop") -> [["dropbear -i", 320], ["drop_test", 410]]
  #
  def Misc.match_procs(proc_name)
    return nil unless (proc_name)

    begin
      match_string = proc_name.to_s
    rescue Exception => e
      match_string = nil
    end

    return nil unless (match_string)

    # Get a list of processes
    #
    ps = `ps -a | grep #{match_string}`
#    puts(ps)
    ps = ps.split("\n")
#    puts(ps.size)

    all = []
    ps.each do |p|
      p = p.split
      a = [p[0],p.last]
      all.push(a)
    end
    return all

  end

  #####
  # Use 'ps' and 'grep' to find a running process with the given name.
  # Returns an array[] of PIDs if found, otherwise nil.
  #
  # The more you can distinguish the actual process name in the 'process'
  # argument, the less chance for false positives.
  # For example don't use "super" if the name of the running process is
  # "supervisor", since "super" may match some other process as well.
  #
  def Misc.running?(process)

    ##
    # Look for the process ignoring edit sessions.
    #
    res = %x{ps a|grep #{process}|grep -v grep|grep -v emacs|grep -v "vi "}

    ##
    # If found, slice off and return an array of pids
    #
    if (res && res.length > 0)
      pids = []

      # First split the result into single lines
      #
      lines = res.split("\n")

      # Then split each line into tokens.
      # Push th 1st token from each line onto the pid array
      #
      lines.each { |l|
        ls = l.split
        pid = ls[0]
        pids.push(pid)
      }
      return pids
    end

    ##
    # Not found
    #
    return nil
  end
  #####

end

##
# Warpable sleep function
#
module RoverTime
  def r_sleep(seconds)
    if (Options.instance.warp)
      sleep(1) if (seconds >= 1)
    else
      sleep(seconds)
    end
  end
end

module Dump
  ##
  # Generic to_s function
  #
  def dump
    s = ":dump:" + self.class.to_s
    vars = self.instance_variables
    vars.each do |v|
      s += " #{v}=" + eval(v).to_s
    end
    s
  end
end


##
# Interface to rover persistant storage
#
class RoverStore
  attr_reader :file, :store

  def initialize(file="#{ENV['ROVER_HOME']}/logs/rover.db")
    @file = file
    pre_exist = File.exists?(file)
    @store = PStore.new(file)

    # Initialize the root hierarchies if a new PStore file
    #
    unless (pre_exist)
      self.save_hibernate_data(nil)
      self.init_component_states()
      self.save_mission_params(nil)
    end
  end

  # Rover component name constants. For use with
  # component state access methods, e.g.
  #   relay_state = load_component_state(RoverStore::Relay)
  # The list much match the @@component_names array.
  #
  Relays   = "Relay boards"
  Acm      = "Current meter"
  PRack    = "Port rack"
  SRack    = "Stbd rack"
  Motors   = "Prop motors"
  ChamCam  = "Video server"
  TransCam = "Transit cam"
  FluoroCam= "Fluoro cam"
  POptode  = "Port optode"
  SOptode  = "Stbd optode"
  PROptode = "Port ref optode"
  SROptode = "Stbd ref optode"
  Modem    = "Acoustic modem"
  Hesc     = "HESC-SERD"
  Wakey    = "Wakey"
  @@component_names = [Relays, Acm, PRack, SRack, Motors, ChamCam, \
                       TransCam, FluoroCam, POptode, SOptode, PROptode, \
                       SROptode, Modem, Wakey]

  Comps = "components"  # Root name for master components hash
  Health= "health"      # Key for component health value
  Date  = "date"        # Key for timestamp of health value

  InsertSwitch = "insert switch"
  
  
  def RoverStore.component_names
    return @@component_names
  end
 
  #####
  # Initialize all the component states to OK.
  # A health value of nil means OK.
  # 
  def init_component_states

    ##
    # Create and initialize a master component hash
    #
    master = Hash[]
    @@component_names.each do |n|
      master[n] = {Health => nil, Date => "--- --- -- -------- --- ----"}
    end

    ##
    # Store it in the rover database
    #
    @store.transaction do
      @store[Comps] = master
    end
  end
  #####


  #####
  # Return a record of all the component states
  #
  def all_component_states
    ##
    # Retrieve the component record from the rover database
    #
    all = nil
    @store.transaction do
      all = @store[Comps]
    end
    all
  end
  #####


  #####
  # Return the component health record for the given component.
  # Return a value of nil indicates no record exists for that
  # component name.
  #
  def load_component_state(component_name)
#    puts("component name is nil") unless(component_name)
    state = nil
    @store.transaction do
      # Retrieve the master components hash table.
      # Then get the specific component hash from the master.
      master = @store[Comps]
      state = master[component_name]
    end

#    puts("unknown component: #{component_name}") unless(state)

    state
  end
  #####


  #####
  # Save the component health record for the given component.
  # The record must be non-nil.
  # A return value of true indicates success.
  # A retrun value of false indicates no recod with that name exists
  # in the components hash.
  #
  def save_component_state(component_name, new_state)
#    puts("component name is nil") unless(component_name)
    unless(new_state)
#      puts("nil new_state passed to save_component_state")
      return nil
    end

    ##
    # Retrieve the master components hash table.
    # Then save the specific component hash from the master.
    old_state = nil
    
    @store.transaction do
      master = @store[Comps]
      old_state = master[component_name]

      ##
      # Store updated information if record found.
      #
      if (old_state)
        master[component_name] = new_state
        @store[Comps] = master
      end
    end
#    puts("unknown component: #{component_name}") unless(old_state)

    return (old_state != nil)
  end

  def save_hibernate_data(stuff)
    puts("**** Saving hib data...")
    @store.transaction do
      @store['hibernate_data'] = stuff
    end
  end

  def load_hibernate_data()
    stuff = nil
    @store.transaction do
      stuff = @store['hibernate_data']
    end
    return stuff
  end

  def save_mission_params(stuff)
    puts("**** Saving params...")
    save_data("mission_params", stuff)
  end

  def load_mission_params()
    load_data("mission_params")
  end

  def save_data(key, stuff)
    @store.transaction do
      @store[key] = stuff
    end
  end

  def load_data(key)
    stuff = nil
    @store.transaction do
      stuff = @store[key]
    end
    return stuff
  end

end


##
# Interface to deployment persistant storage
#
class DeployStore

  attr_reader :file, :store

  def initialize(file="#{ENV['ROVER_HOME']}/logs/latest/deploy.db")
    @file = file
    pre_exist = File.exists?(file)
    @store = PStore.new(file)

    # Initialize the root hierarchies if a new PStore file
    #
    unless (pre_exist)
      self.save_deploy_data(nil)
      self.save_plan_data(nil)
      self.save_behavior_data(nil)
    end
  end

  def save_data(key, stuff)
    @store.transaction do
      @store[key] = stuff
    end
  end

  def load_data(key)
    stuff = nil
    @store.transaction do
      stuff = @store[key]
    end
    return stuff
  end

  #####
  # These methods save and load hash tables containing
  # information used when resuming hibernating deployments.
  #####
  
  def save_deploy_data(stuff)
    save_data("deploy_data", stuff)
  end

  def load_deploy_data()
    load_data("deploy_data")
  end

  def save_plan_data(stuff)
    save_data("plan_data", stuff)
  end

  def load_plan_data()
    load_data("plan_data")
  end

  def save_behavior_data(stuff)
    save_data("behavior_data", stuff)
  end

  def load_behavior_data()
    load_data("behavior_data")
  end

end

##
# Standalone unit test (recommended)
#
if __FILE__ == $0
  # Test code here
  f = Misc.find_plan("test_plan.xml")
  puts("Found test_plan.xml here: #{f}")
  f = Misc.find_plan("misc.rb")
  puts("Found misc.rb here: #{f}")
end
