=begin
 ******************************************************************************
 * Copyright 1990-2010 MBARI
 * MBARI Proprietary Information. All rights reserved.
 ******************************************************************************
 * Summary  : Benthic Rover remote server
 * Filename : remote_server.rb
 * Author   : Henthorn
 * Project  : Benthic Rover
 * Version  : 1
 * Created  : March 2010
 * Modified : 
 ******************************************************************************
=end

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

#

# Now use subdirectory name for remaining rover-code require statements.
# Example:
# require 'utils/datalog'
#

require 'utils/datalog'

##########
# RemoteServer for the Rover supervisor. Intended to be used with the
# RemoteClient processes that acts as the remote communications conduit
# to the rover control system. Designed to be used as a Comms object
# within the Supervisor object.
#
class RemoteServer

  attr_reader :port, :server, :default

  #####
  # We have a server.
  #
  def initialize(port)
    @port = port
    @server = nil
    @default = nil
    @log = nil
  end
  #####

  #####
  # Initialize the UDP server
  #
  def init(logfile="#{ENV['ROVER_HOME']}/logs/server.log")
    @log = DataLog.new(logfile)
    @log.write("RemoteServer(#{@port}) initializing")

    @server = UDPSocket.new()
    @server.bind(nil,@port)
  end
  #####


  #####
  # Close the server port. Let a default client know
  # that we're closing the server.
  #
  def close()
    if (@server)
      self.send_msg("close") if (@default)
      @server.close_read
      @server.close_write
    end
    @server = nil
  end
  #####


  #####
  # Send a string message to a client. Returns nil if
  # the message was not sent. Otherwise returns the number
  # of bytes sent. Send to default_client if client is nil (allows
  # a server can send unsolicited updates to a special client).
  # 
  def send_msg(msg, client=nil)
    return nil unless (msg)
    self.init() unless (@server)
    return nil unless (@server)

    @log.write("RemoteServer(#{@port}),Send","#{msg}")
    begin
      unless (client)
        @server.send(msg,0,@default[2],@default[1]) if (@default)
      else
        @server.send(msg,0,client[2],client[1])
      end
    rescue Exception => e
      puts(e)
      return nil
    end
  end
  #####


  #####
  # Get a message from the port. Non-Blocking read on the port.
  # Returns "external" message string to the caller, or nil.
  # The caller should just check for but ignore nil messages and call
  # get_msg() again (to avoid recursive get_msg() calls).
  #
  def get_msg(timeout=0.1)

    ##
    # Initialize the UDP socket if needed
    #
    unless (@server)
      self.init()
    end
    return nil unless (@server)

    ##
    # Return unless there is data waiting on the socket
    #
    return nil unless (self.data?(timeout))

    ##
    # Read a message packet
    #
    begin
      msg = @server.recvfrom(100)
    rescue Exception => e
      @log.write("RemoteServer(#{@port})", e)
      self.close
      return nil
    end

    ##
    # A nil value means the client disconnected abruptly
    #
    unless (msg)
      @log.write("RemoteServer(#{@port}),client disconnect")
      self.close
      return nil
    end

    ##
    # Process the message. "Internal" messages are handled
    # in this class. "External" messages are returned for
    # the caller to handle.
    #
    @log.write("RemoteServer(#{@port}),Receive", "#{msg}")
    case msg[0].strip.downcase

    ##
    # Default client registration
    #
    when "default", "connect"
      @default = msg[1]
      send_msg("connect ack", msg[1])
      return nil

    ##
    # Internal message: Client ping. Acknowledge
    #
    when "ping"
      send_msg("ping ack", msg[1])
      return nil

    when "ping ack"
      return nil

    ##
    # Internal message: Default client is closing connection
    #
    when "close"
      @default = nil
      return nil

    ##
    # External messages are returned
    #
    else
      return msg
    end
  end
  #####


  #####
  # Read a message and compare it to the given string.
  # Return true on a positive match. Wait 10 seconds for
  # the message by default.
  #
  def confirm(match, timeout=5)
    timeout = 5 unless (timeout)
    return false unless (msg = self.get_msg(timeout))

    return false unless (msg[0].strip.downcase == match.strip.downcase)
    true
  end
  #####


  #####
  # Return true is data is pending on the port. Otherwise false.
  # Default timeout on the select is 0.5 seconds.
  #
  def data?(t_o = 0.1) 
    return false unless (@server)

    d = IO.select([@server], nil, nil, t_o)
    return false unless (d)
    return true
  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 including the command line options
# (e.g., "$ ruby my_class.rb --sim").
#
if __FILE__ == $0
  # Test code here.
  # MyClass.test
  port = 6600
  port = ARGV[0].to_i if (ARGV[0])
  rs = RemoteServer.new(port)
  while (true)
    msg = rs.get_msg()
    next unless (msg)

    msg[0].strip!
    case msg[0].downcase
    when "quit!"
      rs.close()
      break

    else
      rs.send_msg("#{msg[0]} ack",msg[1])
    end
  end

end
