# wrapping class to hold an flickr photo
class Flickr::Photos::Photo
  attr_accessor :id, :owner, :secret, :server, :farm, :title, :is_public, :is_friend, :is_family # standard attributes
  attr_accessor :license_id, :uploaded_at, :taken_at, :owner_name, :icon_server, :original_format, :updated_at, :geo, :tags, :machine_tags, :o_dims, :views, :media # extra attributes
  attr_accessor :comments # comment attributes
  
  # create a new instance of a flickr photo.
  # 
  # Params
  # * flickr (Required)
  #     the flickr object
  # * attributes (Required)
  #     a hash of attributes used to set the initial values of the photo object
  def initialize(flickr, attributes)
    @flickr = flickr
    attributes.each do |k,v|
      # necessary for anything that comes via 'extras' or of course
      # possible future changes in the api returns
      if ! respond_to?(k)
        self.class.send(:attr_accessor, k)
      end
      send("#{k}=", v)
    end
  end

  # returns an instance of Flickr::Photos::Size for the required size
  #
  # Params
  #  * size (Optional)
  #    the size of the size instance to return.  Optional sizes are:
  #       :square - square 75x75
  #       :thumbnail - 100 on longest side
  #       :small - 240 on longest side
  #       :medium - 500 on longest side
  #       :large - 1024 on longest side (only exists for very large original images)
  #       :original - original image, either a jpg, gif or png, depending on source format
  # Examples
  #       Photo.photo_size(:square).source
  #       Photo.photo_size(:large).width
  def photo_size(size = :medium)
    size_hash.fetch(size.to_s, size_hash['medium'])
  end

  # retreive the url to the image stored on flickr
  #
  # == Params
  # * size (Optional)
  #     the size of the image to return. Optional sizes are:
  #       :square - square 75x75
  #       :thumbnail - 100 on longest side
  #       :small - 240 on longest side
  #       :medium - 500 on longest side
  #       :large - 1024 on longest side (only exists for very large original images)
  #       :original - original image, either a jpg, gif or png, depending on source format
  #
  def image_url(size = :medium)
    # It turns out that flickr always stores all the sizes of the picture even when getSizes call returns otherwise.
    # Not calling getSizes is also very important for performance reasons.
    # Retrieving 30 search results means calling the API 31 times if you call getSizes every time.
    # Mind that you still need to call getSizes if you go out for the original image.
    if size == :original
      return original_url if respond_to?(:original_url) and !original_url.blank?
      size_hash[size.to_s].source if size_hash.has_key? size.to_s
    else
      key = "_#{size_key(size.to_sym)}"
      key = "" if key == "_"
      "http://farm#{farm}.static.flickr.com/#{server}/#{id}_#{secret}#{key}.jpg"
    end
  end
  alias :url :image_url

  def photopage_url
    # Keeping the same convention as image_url (foo_url)
    url_photopage
  end

  def video_url
    if size_hash['video player']
      size_hash['video player'].source
    end
  end

  # save the current photo to the local computer
  # 
  # == Params
  # * filename (Required)
  #     name of the new file omiting the extention (ex. photo_1)
  # * size (Optional)
  #     the size of the image to return. Optional sizes are:
  #       :small - square 75x75
  #       :thumbnail  - 100 on longest side
  #       :small      - 240 on longest side
  #       :medium     - 500 on longest side
  #       :medium_500 - 500 on the longest side
  #       :medium_640 - 640 on the longest side
  #       :large      - 1024 on longest side (only exists for very large original images)
  #       :original   - original image, either a jpg, gif or png, depending on source format
  # 
  def save_as(filename, size = :medium)
    format = size.to_sym == :original ? (self.original_format || 'jpg') : 'jpg'
    filename = "#{filename}.#{format}"

    if File.exists?(filename) or not self.url(size)
      false
    else
      f = File.new(filename, 'w+')
      f.puts open(self.url(size)).read
      f.close
      f
    end
  end
  
  # Add tags to a photo.
  # 
  # Params
  # * tags (Required)
  #     comma seperated list of tags
  # 
  def add_tags(tags)
    @flickr.send_request('flickr.photos.addTags', {:photo_id => self.id, :tags => tags}, :post)
    true
  end
  
  # Add comment to a photo as the currently authenticated user.
  #
  # Params
  # * message (Required)
  #     text of the comment
  #
  def add_comment(message)
    @flickr.send_request('flickr.photos.comments.addComment', {:photo_id => self.id, :comment_text => message}, :post)
    true
  end
  
  # Add a note to a photo. Coordinates and sizes are in pixels, based on the 500px image size shown on individual photo pages.
  # 
  # Params
  # * message (Required)
  #     The text of the note
  # * x (Required)
  #     The left coordinate of the note
  # * y (Required)
  #     The top coordinate of the note
  # * w (Required)
  #     The width of the note
  # * h (Required)
  #     The height of the note
  #     
  def add_note(message, x, y, w, h)
    @flickr.send_request('flickr.photos.notes.add', {:photo_id => self.id, :note_x => x, :note_y => y, :note_w => w, :note_h => h, :note_text => message}, :post)
    true
  end
  
  # Rotate a photo.
  # 
  # Params
  # * degrees (Required)
  #     The amount of degrees by which to rotate the photo (clockwise) from it's current orientation. Valid values are 90, 180 and 270.
  # 
  def rotate(degrees)
    @flickr.send_request('flickr.photos.transform.rotate', {:photo_id => self.id, :degrees => degrees}, :post)
    true
  end
  
  # return the license associated with the photo
  # 
  def license
    @flickr.photos.licenses[self.license_id]
  end

  # Returns the location of the photo (if available)
  # or nil if photo is not geo-tagged.
  def location
    begin
      @location ||= @flickr.photos.geo.get_location(self.id)
    rescue Flickr::Error => e
      if e.code == 2 # 2: Photo has no location information.
        return nil
      else
        raise e
      end
    end
  end

  def location= location
    if !location.nil?
      @flickr.photos.geo.set_location(self.id, location.latitude, location.longitude, location.accuracy)
    else
      @flickr.photos.geo.remove_location(self.id)
    end
    @location = location
  end
  
  # Sets the license for a photo.
  # 
  # Params
  # * license_id (Required)
  #     The license to apply, or 0 (zero) to remove the current license.
  def set_license(license_id)
    @flickr.send_request('flickr.photos.licenses.setLicense', {:photo_id => self.id, :license_id => license_id}, :post)
    true
  end

  def public?
    is_public == "1"
  end

  def friend?
    is_friend == "1"
  end

  def family?
    is_family == "1"
  end

  def comments # :nodoc:
    @comments ||= begin
      if @comment_count == 0
        self.comments = []
      else
        rsp = @flickr.send_request('flickr.photos.comments.getList', :photo_id => self.id)
        
        self.comments = []
        
        rsp.comments.comment.each do |comment|
          self.comments << Flickr::Photos::Comment.new(:id => comment[:id],
            :comment => comment.to_s,
            :author => comment[:author],
            :author_name => comment[:authorname],
            :permalink => comment[:permalink],
            :created_at => (Time.at(comment[:datecreate].to_i) rescue nil))
        end
      end

      self.comments
    end
  end
  
  def sizes # :nodoc:
    @sizes ||= begin
      rsp = @flickr.send_request('flickr.photos.getSizes', :photo_id => self.id)
      
      _sizes = []
      rsp.sizes.size.each do |size|
        _sizes << Flickr::Photos::Size.new(:label => size[:label], :width => size[:width],
          :height => size[:height], :source => size[:source], :url => size[:url])
      end
      _sizes
    end
  end

  def method_missing(method, *args, &block)
    if [:description, :original_secret, :owner_username, :owner_realname, :url_photopage, :notes].include?(method.to_sym)
      @attach_info ||= attach_info
      return @attach_info[method.to_sym]
    end
  end

  protected
  def size_hash
    @size_hash ||= begin
      hash = {}
      sizes.each do |size|
        hash[size.label.downcase] = size
      end
      hash
    end
  end

  private
  attr_accessor :comment_count
  
  # convert the size to the key used in the flickr url
  def size_key(size)
    case size.to_sym
    when :square then 's'
    when :thumb, :thumbnail then 't'
    when :small then 'm'
    when :medium then ''
    when :medium_500 then ''
    when :medium_640 then 'z'
    when :large then 'b'
    when :original then 'o'
    else ''
    end
  end

  # loads photo info when a field is requested that requires additional info
  def attach_info
    rsp = @flickr.send_request('flickr.photos.getInfo', :photo_id => self.id, :secret => self.secret)

    if rsp.photo.notes.note
      notes = rsp.photo.notes.note.collect do |note|
        Flickr::Photos::Note.new(:id => note[:id],
          :note => note.to_s,
          :author => note[:author],
          :author_name => note[:authorname],
          :x => note[:x],
          :y => note[:y],
          :width => note[:w],
          :height => note[:h])
      end
    end
    return {
      :description => rsp.photo.description.to_s.strip,
      :original_secret => rsp.photo[:originalsecret],
      :owner_username => rsp.photo.owner[:username],
      :owner_realname => rsp.photo.owner[:realname],
      :url_photopage => rsp.photo.urls.url.to_s,
      :comment_count => rsp.photo.comments.to_s.to_i,
      :notes => notes
    }
  end
end