# 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,
    :owner_realname, 
    :icon_server, 
    :original_format, 
    :updated_at, 
    :geo, 
    :tags, 
    :machine_tags, 
    :o_height, 
    :o_width, 
    :o_dims, 
    :views, 
    :media, 
    :rotation # extra attributes
  attr_accessor :info_added, 
    :description, 
    :original_secret, 
    :owner_username, 
    :url_photopage, 
    :notes # info 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|
      send("#{k}=", v)
    end
  end
  def self.create_attributes(photo)
    {:id => photo['id'], 
      :secret => photo['secret'], 
      :server => photo['server'], 
      :farm => photo['farm'],
      :license_id => photo['license'],
      :rotation => photo['rotation'],
      :uploaded_at => (Time.at(photo['dateuploaded'].to_i) rescue nil),
      :owner => photo.owner['nsid'],
      :owner_username => photo.owner['username'],
      :owner_realname => photo.owner['realname'],
      :icon_server => photo.owner['iconserver'],
      :title => (photo.title.text rescue ''),
      :description => photo.at_xpath("description").text, # description is a method of XML::Node, so we use at_xpath instead
      :original_secret => photo['originalsecret'],
      :original_format => photo['originalformat'],


      :is_public => photo.visibility['ispublic'].to_i, 
      :is_friend => photo.visibility['isfriend'].to_i, 
      :is_family => photo.visibility['isfamily'].to_i,

      :taken_at => (Time.parse(photo.dates['taken']) rescue nil),
      :updated_at => (Time.at(photo.dates['lastupdate'].to_i) rescue nil),
      :tags => (photo.tags.xpath('tag') || []).map { |t|
        { :id => t['id'], :author => t['author'], 
          :raw => t['raw'], :machine_tag => t['machine_tag'],
          :text => t.text }},

      :url_photopage => photo.urls.url.text
   }
  end
  # Alias to image_url method
  def url(size = :medium)
    image_url(size)
  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
  #       :large_square - square 150x150
  #       :thumbnail - 100 on longest side
  #       :small - 240 on longest side
  #       :small_320 - 320 on longest side
  #       :medium - 500 on longest side
  #       :medium_640 - 640 on longest side
  #       :medium_800 - 800 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
  #       :large_square - square 150x150
  #       :thumbnail - 100 on longest side
  #       :small - 240 on longest side
  #       :small_320 - 320 on longest side
  #       :medium - 500 on longest side
  #       :medium_640 - 640 on longest side
  #       :medium_800 - 800 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
	  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

  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:
  #       :square - square 75x75
  #       :large_square - square 150x150
  #       :thumbnail - 100 on longest side
  #       :small - 240 on longest side
  #       :small_320 - 320 on longest side
  #       :medium - 500 on longest side
  #       :medium_640 - 640 on longest side
  #       :medium_800 - 800 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 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
 
  # Delete this photo
  #
  def delete()
    @flickr.send_request('flickr.photos.delete', {:photo_id => self.id}, :post)
    true
  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 description # :nodoc:
    attach_info
    @description
  end

  def original_secret # :nodoc:
    attach_info
    @original_secret
  end

  def owner_username # :nodoc:
    attach_info
    @owner_username
  end

  def owner_realname # :nodoc:
    attach_info
    @owner_realname
  end

  def url_photopage # :nodoc:
    attach_info
    @url_photopage
  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].to_i,
          :height => size[:height].to_i, :source => size[:source], :url => size[:url])
      end
      _sizes
    end
  end

  def notes # :nodoc:
    attach_info
    @notes
  end

  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 :large_square then 'q'
    when :thumb, :thumbnail then 't'
    when :small then 'm'
    when :small_320 then 'n'
    when :medium then ''
    when :medium_640 then 'z'
    when :medium_800 then 'c'
    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
    unless self.info_added
      rsp = @flickr.send_request('flickr.photos.getInfo', :photo_id => self.id, :secret => self.secret)

      self.info_added = true

      ::Flickr::Photos::Photo.create_attributes(rsp.photo).each do |k,v|
       send("#{k}=", v)
      end

      self.notes = []

      rsp.photo.notes.each do |note|
        self.notes << 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
  end
end