# Based on code from the Poltergeist project
# https://github.com/jonleighton/poltergeist
# 
# Copyright (c) 2011 Jonathan Leighton
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

module Terminus
  module Connector
    
    class Server
      RECV_SIZE    = 1024
      BIND_TIMEOUT = 5
      
      def initialize(browser, timeout = BIND_TIMEOUT)
        @browser = browser
        @skips   = 0
        @server  = start_server
        @timeout = timeout
        reset
      end
      
      def reset
        @closing = false
        @env     = nil
        @handler = nil
        @parser  = Http::Parser.new
        @socket  = nil
      end
      
      def connected?
        not @socket.nil?
      end
      
      def port
        @server.addr[1]
      end
      
      def request(message)
        @browser.debug(:send, @browser.id, message)
        accept unless connected?
        @socket.write(@handler.encode(message))
        true while @closing && receive
        result = receive
        @browser.debug(:recv, @browser.id, result)
        reset if result.nil?
        result
      rescue Errno::ECONNRESET, Errno::EPIPE, Errno::EWOULDBLOCK
        reset
        nil
      end
      
      def drain_socket
        @closing = true if @socket
      end
      
    private
      
      def start_server
        time = Time.now
        TCPServer.open(0)
      rescue Errno::EADDRINUSE
        if (Time.now - time) < BIND_TIMEOUT
          sleep(0.01)
          retry
        else
          raise
        end
      end
      
      def accept
        @skips.times { @server.accept.close }
        @socket = @server.accept
        while line = @socket.gets
          @parser << line
          break if line == "\r\n"
        end
        if line.nil?
          accept
          @skips += 1
        else
          @handler = SocketHandler.new(self, env)
          @socket.write(@handler.handshake_response)
          @browser.debug(:accept, @browser.id, @handler.url)
        end
      end
      
      def env
        @env ||= begin
          env = {'REQUEST_METHOD' => @parser.http_method}
          @parser.headers.each do |header, value|
            env['HTTP_' + header.upcase.gsub('-', '_')] = value
          end
          if env['HTTP_SEC_WEBSOCKET_KEY1']
            env['rack.input'] = StringIO.new(@socket.read(8))
          end
          env
        end
      end
      
      def receive
        @browser.debug(:receive, @browser.id)
        start = Time.now
        
        until @handler.message?
          raise Errno::EWOULDBLOCK if (Time.now - start) >= @timeout
          IO.select([@socket], [], [], @timeout) or raise Errno::EWOULDBLOCK
          data = @socket.recv(RECV_SIZE)
          break if data.empty?
          @handler << data
          break if @handler.nil?
        end
        @handler && @handler.next_message
      end
      
      def close
        [server, socket].compact.each do |s|
          s.close_read
          s.close_write
        end
      end
    end
    
  end
end