# Initialization script run for the ruby (JRuby) engine.
# Performs additional initialization that's easier to do in straight
# Ruby than through the Java scripting API.

require 'java'

java_import org.jsoar.util.events.SoarEventListener
java_import org.jsoar.kernel.io.InputWmes
java_import org.jsoar.kernel.symbols.Symbols
java_import org.jsoar.kernel.rhs.functions.RhsFunctionHandler

class SoarWmes

  def initialize(agent)
    @agent = agent  
  end
  
  def new_id(letter)
    @agent.symbols.create_identifier(letter[0])
  end
  
  def add(id, attr, value = nil)
    io = @agent.input_output
    if value.nil?
      value = attr
      attr = id
      id = io.input_link
    end
    InputWmes.add(io, id, attr, value)
  end
  
  def update(wme, value)
    InputWmes.update(wme, value)
  end
  
  def to_ruby(root)
    result = {}
    i = root.wmes
    while i.has_next()
      wme = i.next()
      attr = Symbols.value_of(wme.attribute)
      value = wme.value
      if value.as_identifier().nil?
        result[attr] = Symbols.value_of(value)
      else
        result[attr] = to_ruby(value)
      end
    end
    result
  end
  
  def from_ruby(root, letter = "Z")
    id = new_id(letter)
    root.each do |k,v|
      type = v.class
      if type == String || type == Fixnum || type == Float || type.respond_to?(:java_class)
        add(id, k, v)
      elsif v.is_a?(Hash)
        add(id, k, from_ruby(v, k[0,1]))
      elsif v.is_a?(Enumerable)
        v.each do |av|
          if av.is_a?(Hash)
            add(id, k, from_ruby(av))
          else
            add(id, k, av)
          end
        end
      end
    end
    id
  end
end

class RubyRhsFunction < RhsFunctionHandler.class
  def initialize(options, &handler)
    @options = options
    @handler = handler
  end
  def getName() 
    @options[:name] 
  end
  def getMinArguments()
    @options[:min_arguments] || 0
  end
  def getMaxArguments()
    @options[:max_arguments] || java.lang.Integer::MAX_VALUE
  end
  def mayBeStandalone()
    @options[:may_be_standalone].nil? ? true : @options[:may_be_standalone]
  end
  def mayBeValue()
    @options[:may_be_value].nil? ? true : @options[:may_be_value]
  end
  def execute(context, args)
    result = @handler.call(context, args)
    if !result.nil?
      Symbols.create(context.symbols, result)
    else
      nil
    end
  end
end
    
class Soar
  attr_reader :agent
  attr_reader :wmes
  
  def initialize(agent)
    @agent = agent
    @wmes = SoarWmes.new(agent)
    @output_handlers = {}
    @disposers = []
    
    on_output {|e| dispatch_output_commands(e) }
  end
  
  def pwd
    agent.interpreter.eval("pwd")
  end
  
  def print(message)
    @agent.printer.print(message)
  end
  
  def on_event(className, &handler)
    event_class = java.lang.Class.forName(className)
    @agent.events.add_listener(event_class, handler)
    
    d = lambda { || @agent.events.remove_listener(event_class, handler) }
    @disposers << d
    d
  end
  
  def for_one_event(event_method, &handler)
    em = method(event_method)
    cleanup = nil
    cleanup = em.call() do |e|
      begin
        handler.call(e)
      ensure
        cleanup.call
      end
    end
    cleanup
  end
  
  def on_input(&handler)
    on_event("org.jsoar.kernel.events.InputEvent", &handler)
  end
  
  def on_output(&handler)
    on_event("org.jsoar.kernel.events.OutputEvent", &handler)
  end
  
  def on_output_command(name, &handler)
    @output_handlers[name] = handler
  end
  
  def on_init_soar(&handler)
    on_event("org.jsoar.kernel.events.BeforeInitSoarEvent", &handler)
  end
  
  def on_dispose(&handler)
    @disposers << handler
  end
  
  def rhs_function(options, &handler)
    agent.rhs_functions.register_handler(RubyRhsFunction.new(options, &handler))
    
    d = lambda { || @agent.rhs_functions.unregister_handler(options[:name]) }
    @disposers << d
    d
  end
  
  def _dispose()
    @disposers.each do |d|
      d.call()
    end
  end
private

  def dispatch_output_commands(e)
    it = e.input_output.pending_commands.iterator()
    while it.has_next()
      command = it.next()
      handler = @output_handlers[command.attribute.to_string]
      if handler
        result = handler.call(wmes.to_ruby(command.value)) || "complete"
        wmes.add(command.value, "status", result)
      end
    end
  end
end

# Replace default context variable with ours...
$soar = Soar.new($_soar.agent)

def soar_dispose()
  $soar._dispose
end
