require 'celluloid/autostart'
require 'celluloid/io'
require 'multi_json'

require 'artoo/robot_class_methods'
require 'artoo/basic'
require 'artoo/connection'
require 'artoo/adaptors/adaptor'
require 'artoo/device'
require 'artoo/drivers/driver'
require 'artoo/events'
require 'artoo/exceptions'
require 'artoo/api/api'
require 'artoo/master'
require 'artoo/port'
require 'artoo/utility'
require 'artoo/interfaces/interface'
require 'artoo/interfaces/ping'
require 'artoo/interfaces/rover'

module Artoo
  # The most important class used by Artoo is Robot. This represents the primary
  # interface for interacting with a collection of physical computing capabilities.
  #
  # This file contains the instance-level methods used by Artoo::Robot
  class Robot
    include Celluloid
    include Celluloid::Notifications
    extend Artoo::Basic
    extend Artoo::Robot::ClassMethods
    include Artoo::Utility
    include Artoo::Events

    attr_reader :connections, :devices, :name, :commands, :interfaces

    exclusive :execute_startup

    # Create new robot
    # @param [Hash] params
    # @option params [String]     :name
    # @option params [Collection] :connections
    # @option params [Collection] :devices
    def initialize(params={})
      @name = params[:name] || current_class.name || "Robot #{random_string}"
      @commands = params[:commands] || []
      @interfaces = {}
      initialize_connections(params[:connections] || {})
      initialize_devices(params[:devices] || {})
    end

    # @return [String] Name without spaces and downcased
    def safe_name
      name.gsub(' ', '_').downcase
    end

    # @return [String] Api Host
    def api_host
      self.class.api_host
    end

    # @return [String] Api Port
    def api_port
      self.class.api_port
    end

    # start doing the work
    def work
      Logger.info "Starting work..."
      execute_startup(connections) {|c| c.future.connect}
      execute_startup(devices) {|d| d.future.start_device}
      execute_working_code
    end

    # pause the work
    def pause_work
      Logger.info "Pausing work..."
      current_instance.timers.pause
    end

    # continue with the work
    def continue_work
      Logger.info "Continuing work..."
      current_instance.timers.continue
    end

    # Terminate all connections
    def disconnect
      connections.each {|k, c| c.async.disconnect}
    end

    # @return [Connection] default connection
    def default_connection
      connections.values.first
    end

    # @return [Collection] connection types
    def connection_types
      current_class.connection_types ||= [{:name => :passthru}]
    end

    # @return [Collection] device types
    def device_types
      current_class.device_types ||= []
      current_class.device_types
    end

    # @return [Proc] current working code
    def working_code
      current_class.working_code ||= proc {puts "No work defined."}
    end

    # @param [Symbol] period
    # @param [Numeric] interval
    # @return [Boolean] True if there is recurring work for the period and interval
    def has_work?(period, interval)
      current_instance.timers.find {|t| t.recurring == (period == :every) && t.interval == interval}
    end

    # @return [Hash] robot
    def to_hash
      {
        :name => name,
        :connections => connections.each_value.collect {|c|c.to_hash},
        :devices => devices.each_value.collect {|d|d.to_hash},
        :commands => commands
      }
    end

    # @return [JSON] robot
    def as_json
      MultiJson.dump(to_hash)
    end

    # @return [String] robot
    def inspect
      "#<Robot #{object_id}>"
    end

    # @return [Object] whatever result is passed back from the wrapped robot
    def command(method_name, *arguments, &block)
      t = interface_for_command(method_name)
      if t
        if arguments.first
          t.send(method_name, *arguments)
        else
          t.send(method_name)
        end
      else
        "Unknown Command"
      end
    rescue Exception => e
      Logger.error e.message
      Logger.error e.backtrace.inspect
      return nil
    end

    # @return [Boolean] True if command exists
    def own_command?(method_name)
      return commands.include?(method_name.intern)
    end

    def add_interface(i)
      @interfaces[i.interface_type.intern] = i
    end

    # @return [Boolean] True if command exists in any of the robot's interfaces
    def interface_for_command(method_name)
      return self if own_command?(method_name)
      @interfaces.each_value {|i|
        return i if i.commands.include?(method_name.intern)
      }
      return nil
    end

    # Sends missing methods to command
    def method_missing(method_name, *arguments, &block)
      command(method_name, *arguments, &block)
    end

    def respond_to_missing?(method_name, include_private = false)
      own_command?(method_name)|| interface_for_command(method_name)
    end

    private


    def initialize_connections(params={})
      @connections = {}
      connection_types.each {|type| initialize_connection(params, type, @connections)}
    end

    def initialize_devices(params={})
      @devices = {}
      device_types.each {|type| initialize_device(params, type, @devices)}
    end

    def initialize_connection(params, type, connections)
      connection = initialize_object(Connection, params, type)
      @connections[type[:name]] = connection
    end

    def initialize_device(params, type, devices)
      type = initialize_object(Device, params, type)
      instance_eval("def #{type.name}; return devices[:#{type.name}]; end")
      @devices[type.name.intern] = type
    end

    def initialize_object(klass, params, type)
      Logger.info "Initializing #{klass.to_s.downcase} #{type[:name].to_s}..."
      param = params[type[:name]] || {}
      klass.new(type.merge(param).merge(:parent => current_instance))
    end

    def execute_startup(things, &block)
      future_things = things.each_value.collect {|t| yield(t)}
      future_things.each {|v| result = v.value}
    end

    def execute_working_code
      current_instance.instance_eval(&working_code)
    rescue Exception => e
      Logger.error e.message
      Logger.error e.backtrace.inspect
    end
  end
end
