module Thin
  # A response sent to the client.
  class Response
    class Stream
      def initialize(writer)
        @read_closed = true
        @write_closed = false
        @writer = writer
      end

      def read(length = nil, outbuf = nil)
        raise ::IOError, 'not opened for reading' if @read_closed
      end

      def write(chunk)
        raise ::IOError, 'not opened for writing' if @write_closed

        @writer.call(chunk)
      end

      alias :<< :write

      def close
        @read_closed = @write_closed = true

        nil
      end

      def closed?
        @read_closed && @write_closed
      end

      def close_read
        @read_closed = true

        nil
      end

      def close_write
        @write_closed = true

        nil
      end

      def flush
        self
      end
    end

    CONNECTION     = 'Connection'.freeze
    CLOSE          = 'close'.freeze
    KEEP_ALIVE     = 'keep-alive'.freeze
    SERVER         = 'Server'.freeze
    CONTENT_LENGTH = 'Content-Length'.freeze

    PERSISTENT_STATUSES  = [100, 101].freeze

    #Error Responses
    ERROR            = [500, {'Content-Type' => 'text/plain'}, ['Internal server error']].freeze
    PERSISTENT_ERROR = [500, {'Content-Type' => 'text/plain', 'Connection' => 'keep-alive', 'Content-Length' => "21"}, ['Internal server error']].freeze
    BAD_REQUEST      = [400, {'Content-Type' => 'text/plain'}, ['Bad Request']].freeze

    # Status code
    attr_accessor :status

    # Response body, must respond to +each+.
    attr_accessor :body

    # Headers key-value hash
    attr_reader   :headers

    def initialize
      @headers    = Headers.new
      @status     = 200
      @persistent = false
      @skip_body  = false
    end

    # String representation of the headers
    # to be sent in the response.
    def headers_output
      # Set default headers
      @headers[CONNECTION] = persistent? ? KEEP_ALIVE : CLOSE unless @headers.has_key?(CONNECTION)
      @headers[SERVER]     = Thin::NAME unless @headers.has_key?(SERVER)

      @headers.to_s
    end

    # Top header of the response,
    # containing the status code and response headers.
    def head
      "HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status.to_i]}\r\n#{headers_output}\r\n"
    end

    if Thin.ruby_18?

      # Ruby 1.8 implementation.
      # Respects Rack specs.
      #
      # See http://rack.rubyforge.org/doc/files/SPEC.html
      def headers=(key_value_pairs)
        key_value_pairs.each do |k, vs|
          vs.each { |v| @headers[k] = v.chomp } if vs
        end if key_value_pairs
      end

    else

      # Ruby 1.9 doesn't have a String#each anymore.
      # Rack spec doesn't take care of that yet, for now we just use
      # +each+ but fallback to +each_line+ on strings.
      # I wish we could remove that condition.
      # To be reviewed when a new Rack spec comes out.
      def headers=(key_value_pairs)
        key_value_pairs.each do |k, vs|
          next unless vs
          if vs.is_a?(String)
            vs.each_line { |v| @headers[k] = v.chomp }
          else
            vs.each { |v| @headers[k] = v.chomp }
          end
        end if key_value_pairs
      end

    end

    # Close any resource used by the response
    def close
      @body.close if @body.respond_to?(:close)
    end

    # Yields each chunk of the response.
    # To control the size of each chunk
    # define your own +each+ method on +body+.
    def each(&block)
      yield head

      unless @skip_body
        if @body.is_a?(String)
          yield @body
        elsif @body.respond_to?(:each)
          @body.each { |chunk| yield chunk }
        else
          @body.call(Stream.new(block))
        end
      end
    end

    # Tell the client the connection should stay open
    def persistent!
      @persistent = true
    end

    # Persistent connection must be requested as keep-alive
    # from the server and have a Content-Length, or the response
    # status must require that the connection remain open.
    def persistent?
      (@persistent && @headers.has_key?(CONTENT_LENGTH)) || PERSISTENT_STATUSES.include?(@status)
    end

    def skip_body!
      @skip_body = true
    end
  end
end
