require 'rubygems'
require 'rspec'
require 'em-spec/rspec'
require 'pp'
require 'em-http'

require 'em-websocket'

RSpec.configure do |c|
  c.mock_with :rspec
end

class FakeWebSocketClient < EM::Connection
  attr_reader :handshake_response, :packets

  def onopen(&blk);     @onopen = blk;    end
  def onclose(&blk);    @onclose = blk;   end
  def onerror(&blk);    @onerror = blk;   end
  def onmessage(&blk);  @onmessage = blk; end

  def initialize
    @state = :new
    @packets = []
  end

  def receive_data(data)
    # puts "RECEIVE DATA #{data}"
    if @state == :new
      @handshake_response = data
      @onopen.call if @onopen
      @state = :open
    else
      @onmessage.call(data) if @onmessage
      @packets << data
    end
  end

  def send(data)
    send_data("\x00#{data}\xff")
  end

  def unbind
    @onclose.call if @onclose
  end
end

class Draft03FakeWebSocketClient < FakeWebSocketClient
  def send(application_data)
    frame = ''
    opcode = 4 # fake only supports text frames
    byte1 = opcode # since more, rsv1-3 are 0
    frame << byte1

    length = application_data.size
    if length <= 125
      byte2 = length # since rsv4 is 0
      frame << byte2
    elsif length < 65536 # write 2 byte length
      frame << 126
      frame << [length].pack('n')
    else # write 8 byte length
      frame << 127
      frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
    end

    frame << application_data

    send_data(frame)
  end
end

class Draft07FakeWebSocketClient < FakeWebSocketClient
  def send(application_data)
    frame = ''
    opcode = 1 # fake only supports text frames
    byte1 = opcode | 0b10000000 # since more, rsv1-3 are 0
    frame << byte1

    length = application_data.size
    if length <= 125
      byte2 = length # since rsv4 is 0
      frame << byte2
    elsif length < 65536 # write 2 byte length
      frame << 126
      frame << [length].pack('n')
    else # write 8 byte length
      frame << 127
      frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
    end

    frame << application_data

    send_data(frame)
  end
end


# Wrap EM:HttpRequest in a websocket like interface so that it can be used in the specs with the same interface as FakeWebSocketClient
class Draft75WebSocketClient
  def onopen(&blk);     @onopen = blk;    end
  def onclose(&blk);    @onclose = blk;   end
  def onerror(&blk);    @onerror = blk;   end
  def onmessage(&blk);  @onmessage = blk; end

  def initialize
    @ws = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get(:timeout => 0)
    @ws.errback { @onerror.call if @onerror }
    @ws.callback { @onopen.call if @onopen }
    @ws.stream { |msg| @onmessage.call(msg) if @onmessage }
    @ws.disconnect { @onclose.call if @onclose }
  end

  def send(message)
    @ws.send(message)
  end

  def close_connection
    @ws.close_connection
  end
end

def format_request(r)
  data = "#{r[:method]} #{r[:path]} HTTP/1.1\r\n"
  header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
  data << [header_lines, '', r[:body]].join("\r\n")
  data
end

def format_response(r)
  data = r[:protocol] || "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
  header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
  data << [header_lines, '', r[:body]].join("\r\n")
  data
end

RSpec::Matchers.define :send_handshake do |response|
  match do |actual|
    actual.handshake.lines.sort == format_response(response).lines.sort
  end
end
