require "neovim/api"
require "neovim/current"
require "neovim/session"
require "set"

module Neovim
  # Client to a running +nvim+ instance. The interface is generated at
  # runtime via the +nvim_get_api_info+ RPC call. Some methods return
  # +RemoteObject+ subclasses (i.e. +Buffer+, +Window+, or +Tabpage+),
  # which similarly have dynamically generated interfaces.
  #
  # The methods documented here were generated using NVIM v0.3.7
  #
  # @see Buffer
  # @see Window
  # @see Tabpage
  class Client
    attr_reader :session, :api

    def self.from_event_loop(event_loop, session=Session.new(event_loop))
      api = API.new(session.request(:nvim_get_api_info))
      event_loop.register_types(api, session)

      new(session, api)
    end

    def initialize(session, api)
      @session = session
      @api = api
    end

    def channel_id
      @api.channel_id
    end

    # Intercept method calls and delegate to appropriate RPC methods.
    def method_missing(method_name, *args)
      if (func = @api.function_for_object_method(self, method_name))
        func.call(@session, *args)
      else
        super
      end
    end

    # Extend +respond_to_missing?+ to support RPC methods.
    def respond_to_missing?(method_name, *)
      super || rpc_methods.include?(method_name.to_sym)
    end

    # Extend +methods+ to include RPC methods.
    def methods(*args)
      super | rpc_methods.to_a
    end

    # Access to objects belonging to the current +nvim+ context.
    #
    # @return [Current]
    # @example Get the current buffer
    #   client.current.buffer
    # @example Set the current line
    #   client.current.line = "New line"
    # @see Current
    def current
      @current ||= Current.new(@session)
    end

    # Evaluate the VimL expression (alias for +nvim_eval+).
    #
    # @param expr [String] A VimL expression.
    # @return [Object]
    # @example Return a list from VimL
    #   client.evaluate('[1, 2]') # => [1, 2]
    def evaluate(expr)
      @api.function_for_object_method(self, :eval).call(@session, expr)
    end

    # Display a message.
    #
    # @param string [String] The message.
    # @return [void]
    def message(string)
      out_write(string)
    end

    # Set an option.
    #
    # @overload set_option(key, value)
    #   @param [String] key
    #   @param [String] value
    #
    # @overload set_option(optstr)
    #   @param [String] optstr
    #
    # @example Set the +timeoutlen+ option
    #   client.set_option("timeoutlen", 0)
    #   client.set_option("timeoutlen=0")
    def set_option(*args)
      if args.size > 1
        @api.function_for_object_method(self, :set_option).call(@session, *args)
      else
        @api.function_for_object_method(self, :command).call(@session, "set #{args.first}")
      end
    end

    def shutdown
      @session.shutdown
    end

    private

    def rpc_methods
      @rpc_methods ||=
        @api.functions_for_object(self).map(&:method_name).to_set
    end

    public

# The following methods are dynamically generated.
=begin
@method ui_attach(width, height, options)
  See +:h nvim_ui_attach()+
  @param [Integer] width
  @param [Integer] height
  @param [Hash] options
  @return [void]

@method ui_detach
  See +:h nvim_ui_detach()+
  @return [void]

@method ui_try_resize(width, height)
  See +:h nvim_ui_try_resize()+
  @param [Integer] width
  @param [Integer] height
  @return [void]

@method ui_set_option(name, value)
  See +:h nvim_ui_set_option()+
  @param [String] name
  @param [Object] value
  @return [void]

@method command(command)
  See +:h nvim_command()+
  @param [String] command
  @return [void]

@method get_hl_by_name(name, rgb)
  See +:h nvim_get_hl_by_name()+
  @param [String] name
  @param [Boolean] rgb
  @return [Hash]

@method get_hl_by_id(hl_id, rgb)
  See +:h nvim_get_hl_by_id()+
  @param [Integer] hl_id
  @param [Boolean] rgb
  @return [Hash]

@method feedkeys(keys, mode, escape_csi)
  See +:h nvim_feedkeys()+
  @param [String] keys
  @param [String] mode
  @param [Boolean] escape_csi
  @return [void]

@method input(keys)
  See +:h nvim_input()+
  @param [String] keys
  @return [Integer]

@method replace_termcodes(str, from_part, do_lt, special)
  See +:h nvim_replace_termcodes()+
  @param [String] str
  @param [Boolean] from_part
  @param [Boolean] do_lt
  @param [Boolean] special
  @return [String]

@method command_output(command)
  See +:h nvim_command_output()+
  @param [String] command
  @return [String]

@method eval(expr)
  See +:h nvim_eval()+
  @param [String] expr
  @return [Object]

@method execute_lua(code, args)
  See +:h nvim_execute_lua()+
  @param [String] code
  @param [Array] args
  @return [Object]

@method call_function(fn, args)
  See +:h nvim_call_function()+
  @param [String] fn
  @param [Array] args
  @return [Object]

@method call_dict_function(dict, fn, args)
  See +:h nvim_call_dict_function()+
  @param [Object] dict
  @param [String] fn
  @param [Array] args
  @return [Object]

@method strwidth(text)
  See +:h nvim_strwidth()+
  @param [String] text
  @return [Integer]

@method list_runtime_paths
  See +:h nvim_list_runtime_paths()+
  @return [Array<String>]

@method set_current_dir(dir)
  See +:h nvim_set_current_dir()+
  @param [String] dir
  @return [void]

@method get_current_line
  See +:h nvim_get_current_line()+
  @return [String]

@method set_current_line(line)
  See +:h nvim_set_current_line()+
  @param [String] line
  @return [void]

@method del_current_line
  See +:h nvim_del_current_line()+
  @return [void]

@method get_var(name)
  See +:h nvim_get_var()+
  @param [String] name
  @return [Object]

@method set_var(name, value)
  See +:h nvim_set_var()+
  @param [String] name
  @param [Object] value
  @return [void]

@method del_var(name)
  See +:h nvim_del_var()+
  @param [String] name
  @return [void]

@method get_vvar(name)
  See +:h nvim_get_vvar()+
  @param [String] name
  @return [Object]

@method get_option(name)
  See +:h nvim_get_option()+
  @param [String] name
  @return [Object]

@method out_write(str)
  See +:h nvim_out_write()+
  @param [String] str
  @return [void]

@method err_write(str)
  See +:h nvim_err_write()+
  @param [String] str
  @return [void]

@method err_writeln(str)
  See +:h nvim_err_writeln()+
  @param [String] str
  @return [void]

@method list_bufs
  See +:h nvim_list_bufs()+
  @return [Array<Buffer>]

@method get_current_buf
  See +:h nvim_get_current_buf()+
  @return [Buffer]

@method set_current_buf(buffer)
  See +:h nvim_set_current_buf()+
  @param [Buffer] buffer
  @return [void]

@method list_wins
  See +:h nvim_list_wins()+
  @return [Array<Window>]

@method get_current_win
  See +:h nvim_get_current_win()+
  @return [Window]

@method set_current_win(window)
  See +:h nvim_set_current_win()+
  @param [Window] window
  @return [void]

@method list_tabpages
  See +:h nvim_list_tabpages()+
  @return [Array<Tabpage>]

@method get_current_tabpage
  See +:h nvim_get_current_tabpage()+
  @return [Tabpage]

@method set_current_tabpage(tabpage)
  See +:h nvim_set_current_tabpage()+
  @param [Tabpage] tabpage
  @return [void]

@method create_namespace(name)
  See +:h nvim_create_namespace()+
  @param [String] name
  @return [Integer]

@method get_namespaces
  See +:h nvim_get_namespaces()+
  @return [Hash]

@method subscribe(event)
  See +:h nvim_subscribe()+
  @param [String] event
  @return [void]

@method unsubscribe(event)
  See +:h nvim_unsubscribe()+
  @param [String] event
  @return [void]

@method get_color_by_name(name)
  See +:h nvim_get_color_by_name()+
  @param [String] name
  @return [Integer]

@method get_color_map
  See +:h nvim_get_color_map()+
  @return [Hash]

@method get_mode
  See +:h nvim_get_mode()+
  @return [Hash]

@method get_keymap(mode)
  See +:h nvim_get_keymap()+
  @param [String] mode
  @return [Array<Hash>]

@method get_commands(opts)
  See +:h nvim_get_commands()+
  @param [Hash] opts
  @return [Hash]

@method get_api_info
  See +:h nvim_get_api_info()+
  @return [Array]

@method set_client_info(name, version, type, methods, attributes)
  See +:h nvim_set_client_info()+
  @param [String] name
  @param [Hash] version
  @param [String] type
  @param [Hash] methods
  @param [Hash] attributes
  @return [void]

@method get_chan_info(chan)
  See +:h nvim_get_chan_info()+
  @param [Integer] chan
  @return [Hash]

@method list_chans
  See +:h nvim_list_chans()+
  @return [Array]

@method call_atomic(calls)
  See +:h nvim_call_atomic()+
  @param [Array] calls
  @return [Array]

@method parse_expression(expr, flags, highlight)
  See +:h nvim_parse_expression()+
  @param [String] expr
  @param [String] flags
  @param [Boolean] highlight
  @return [Hash]

@method list_uis
  See +:h nvim_list_uis()+
  @return [Array]

@method get_proc_children(pid)
  See +:h nvim_get_proc_children()+
  @param [Integer] pid
  @return [Array]

@method get_proc(pid)
  See +:h nvim_get_proc()+
  @param [Integer] pid
  @return [Object]

=end
  end
end
