require "bixby-common/websocket/async_response"

require "api-auth"
require "eventmachine"

module Bixby
  module WebSocket

    # WebSocket API channel
    #
    # Implements a simple request/response interface over a WebSocket channel.
    # Requests can be sent in either direction, in a sync or async manner.
    class APIChannel < Bixby::APIChannel

      attr_reader :ws

      def initialize(ws, handler)
        @ws = ws
        @handler = handler
        @responses = {}
        @connected = false
      end

      # Perform RPC

      # Execute the given request (synchronously)
      #
      # @param [JsonRequest] json_request
      #
      # @return [JsonResponse] response
      def execute(json_request)
        fetch_response( execute_async(json_request) )
      end

      # Execute the given request (asynchronously)
      #
      # @param [JsonRequest] json_request
      #
      # @return [String] request id
      def execute_async(json_request, &block)
        logger.debug { "execute_async:\n#{json_request.to_s}" }

        if json_request.kind_of? Request then
          id, request = json_request.id, json_request
        else
          request = Request.new(json_request)
          id = request.id
        end
        @responses[id] = AsyncResponse.new(id, &block)

        EM.next_tick {
          ws.send(request.to_wire)
        }
        id
      end

      # Fetch the response for the given request
      #
      # @param [String] request id
      #
      # @return [JsonResponse]
      def fetch_response(id)
        res = @responses[id].response
        @responses.delete(id)
        res
      end


      # Handle channel events

      def connected?
        @connected
      end

      # Open
      def open(event)
        @connected = true
      end

      # Close
      #
      # Can be fired either due to disconnection or failure to connect
      def close(event)
        if @connected then
          @connected = false
          @handler.new(nil).disconnect(self)
        end
      end

      # Message
      #
      # Fired whenever a message is received on the channel
      def message(event)
        req = Message.from_wire(event.data)
        logger.debug { "new '#{req.type}' message" }

        if req.type == "rpc" then
          # Execute the requested method and return the result
          json_req = req.json_request
          logger.debug { json_req.to_s }
          json_response = @handler.new(req).handle(json_req)

          # result = { :type => "rpc_result", :id => req.id, :data => json_response }
          # ws.send(MultiJson.dump(result))
          ws.send(Response.new(json_response, req.id).to_wire)

        elsif req.type == "rpc_result" then
          # Pass the result back to the caller
          res = req.json_response
          logger.debug { res.to_s }
          @responses[req.id].response = res

        elsif req.type == "connect" then
          ret = @handler.new(req).connect(req.json_request, self)
          if ret.kind_of? JsonResponse then
            ws.send(Response.new(ret, req.id).to_wire)
          else
            ws.send(Response.new(JsonResponse.new("success"), req.id).to_wire)
          end

        end
      end

    end

  end
end