require 'pathname'
require 'mime'

#
# == Synopsis
#
# MultipartFormData facilitates in building multipart/form-data encoded
# messages that can be POSTed using ThinHTTP. This class is a simple wrapper
# around the MIME::MultipartMedia::FormData class.
#
# == Caveats
#
# According to RFC 1867 and 2388, each field of the form is to be sent in the
# order in which it occurs in the form. However, the Hash containing the form
# fields inherently does not maintain order. This is probably not a problem.
#
# == Examples
# 
# === Simple usage of the class
#
# The following +params+ Hash:
#
#   params = {
#     'text_field' => 'this is some text',
#     'image_field' => Pathname.new('/tmp/pic.jpg')
#   }
#
# simulates the following HTML form:
#
#   <form>
#     <input type="text" name="text_field"/>
#     <input type="file" name="image_field"/>
#   </form>
#
# Creating and using a MultipartFormData instance initialized with +params+:
#
#   fd = MultipartFormData.new(params)
#   fd.content_type   # outputs the content type header including boundary
#   fd.content        # outputs the content (multipart/form-data encoded entities)
#
#
# === Constructing a multipart/form-data message and POSTing it via ThinHTTP
#
#   form_data = MultipartFormData.new(
#     :dog_owner => 'Mary Jane',
#     :pic_comment => 'These are my two black lab/pit mix puppies.',
#     :dog_pic1 => Pathname.new('/tmp/lexi.jpg'),
#     :dog_pic2 => Pathname.new('/tmp/simone.jpg')
#   )
#
#   th = ThinHTTP.new('petfotoz.com', 80)
#   th.post('/photo_album.cgi', form_data, form_data.content_type)
#
class MultipartFormData

  attr_reader :content_type

  #
  # Returns new MultipartFormData instance initialized with +params+. +params+
  # is a Hash of key/value pairs representing HTML input variable names and
  # values. For HTML file type inputs, use a Pathname object.
  #
  def initialize params
    @form_data = MIME::MultipartMedia::FormData.new
    @content_type = @form_data.content_type
    initialize_form_data(params)
  end

  #
  # Return the multipart/form-data content.
  #
  def to_s
    @form_data.body
  end
  alias :content :to_s

  private

  #
  # Add the +params+ to the form data.
  #
  def initialize_form_data params
    params.each do |name, value|
      @form_data.add_entity(media_type(value), name.to_s)
    end
  end

  #
  # Convert +param+, which can be a String or Pathname, to a MediaType.
  #
  def media_type param
    case param
    when String
      MIME::TextMedia.new(param)
    when Pathname
      path = param.to_s

      # return application/octet-stream if file content is unknown
      begin
        MIME::DiscreteMediaFactory.create(path)
      rescue MIME::UnknownContentError
        MIME::DiscreteMediaFactory.create(path, 'application/octet-stream')
      end
    else
      raise 'invalid parameter'
    end
  end

end
