require 'net/https'
require 'cgi'

class HttpServer
  attr_accessor :response_error_checker, :handle_errors, :headers
  attr_reader :last_response
  
  def initialize(server_config, timeout)
    @server_url, @port, @username, @password = server_config['server_url'], server_config['port'], server_config['username'], server_config['password']
    @timeout = timeout
    raise 'No configuration for timeout length' if @timeout.nil?

    @handle_errors = true
    @response_error_checker = Proc.new do |response, path|
      false
    end

    @headers = {
      'User-Agent'    => 'Mozilla/4.0',
      'Content-Type'  => 'application/x-www-form-urlencoded',
      'Connection'    => 'Keep-Alive',
      'X-API-VERSION' => RightScale::API_VERSION
    }
  end
  
  def delete(resource, &block)
    connect(resource, Net::HTTP::Delete, &block)
  end

  def get(resource, &block)
    connect(resource, Net::HTTP::Get, &block)
  end

  def get_with_params(resource, params={}, &block)
    connect(resource, Net::HTTP::Get, encode_params(params), &block)
  end

  def post(resource, params={}, &block)
    connect(resource, Net::HTTP::Post, encode_params(params), &block)
  end

  def put(resource, params={}, &block)
    connect(resource, Net::HTTP::Put, encode_params(params), &block)
  end

  def with_error_handling_disabled
    original_handle_errors = handle_errors
    self.handle_errors = false

    result = yield
  ensure
    self.handle_errors = original_handle_errors
  end

  private

  def connect(resource, request_object, *args, &block)
    uri = URI.parse url(resource)
    req = request_object.new(uri.path, @headers)
    req.basic_auth @username, @password if @username
    
    response_data = nil
    
    begin
      create_http(uri).start do |http|
        response, data = http.request(req, *args)
        
        block.call(self, response) if block
        
        @last_response = response_data = data_from(response)
      end
    rescue Timeout::Error
      raise "A timeout error occured when connecting to #{resource}.\nThe timeout is currently set to #{@timeout} seconds."
    rescue Errno::ECONNREFUSED
      raise "Could not connect when connecting to #{resource} - the server (#{@server_url}) is down."
    end
    
    check_response_for_errors(response_data, uri.path) if handle_errors
    
    response_data
  end
  
  def data_from(response)
    case response
    when Net::HTTPSuccess
      response.body
    when Net::HTTPRedirection
      response['location']
    end
  end
  
  def create_http(uri)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true if uri.port == 443
    http.read_timeout = @timeout
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    
    # http.set_debug_output $stderr
        
    http
  end
   
  def url(resource)
    "#{server_path}/#{resource.sub /^\//, ''}"
  end
  
  def server_path
    @port ? "#{@server_url}:#{@port}" : @server_url
  end

  def encode_params(hash)
    params = transform_params(hash)
    stringify_params(params)
  end

  def transform_params(hash)
    params = []

    hash.each do |key, value|
      if value.instance_of? Hash
        params.concat transform_params(value).each{|elements| elements[0].unshift key}
      else
        params.push [[key], value]
      end
    end

    params
  end

  def stringify_params(params)
    params.map do |keys, value|
      left = keys.join('[') + (']' * (keys.length - 1))
      "#{left}=#{CGI::escape(value.to_s)}"
    end.join('&')
  end
  
  def check_response_for_errors(response, path)
    if response && response.include?('No connection could be made')
      raise Errno::ECONNREFUSED
    elsif @response_error_checker.call(response, path)
      raise "An error occured requesting #{path} - please check the XML response:\n #{response}\nCurrent data:\n#{@data}"
    end
  end
end