require 'httpclient'
require 'json'
 
# JSONClient auto-converts Hash <-> JSON in request and response.
# * For PATCH, POST or PUT request, convert Hash body to JSON String with 'application/json; charset=utf-8' header.
# * For response, convert JSON String to Hash when content-type is '(application|text)/(x-)?json'
class JSONClient < HTTPClient
  CONTENT_TYPE_JSON_REGEX = /(application|text)\/(x-)?json/i
  CONTENT_TYPE_JSON = 'application/json; charset=utf-8'

  attr_reader :content_type_json_request
  attr_reader :content_type_json_response_regex
 
  def initialize(*args)
    super
    @content_type_json_request = CONTENT_TYPE_JSON
    @content_type_json_response_regex = CONTENT_TYPE_JSON_REGEX
  end
 
  def patch(uri, *args, &block)
    request(:patch, uri, argument_to_hash_for_json(args), &block)
  end
 
  def post(uri, *args, &block)
    request(:post, uri, argument_to_hash_for_json(args), &block)
  end
 
  def put(uri, *args, &block)
    request(:put, uri, argument_to_hash_for_json(args), &block)
  end

  def request(method, uri, *args, &block)
    res = super
    if @content_type_json_response_regex =~ res.content_type
      res = wrap_json_response(res)
    end
    res
  end
 
private

  def argument_to_hash_for_json(args)
    hash = argument_to_hash(args, :body, :header, :follow_redirect)
    if hash[:body].is_a?(Hash) || hash[:body].is_a?(Array)
      hash[:header] = json_header(hash[:header])
      hash[:body] = JSON.generate(hash[:body])
    end
    hash
  end

  def json_header(header)
    header ||= {}
    if header.is_a?(Hash)
      header.merge('Content-Type' => @content_type_json_request)
    else
      header + [['Content-Type', @content_type_json_request]]
    end
  end

  def wrap_json_response(original)
    res = ::HTTP::Message.new_response(JSON.parse(original.content))
    res.http_header = original.http_header
    res.previous = original
    res
  end
end
