require 'rexml/document'
require 'cgi'
require 'open-uri'

module YoutubeSearch
  API_URL = 'http://gdata.youtube.com/feeds/api'

  class << self
    def search_page(page, query, options={})
      options = options_with_per_page_and_page(options)
      query = options.merge(:q => query).map{|k,v| "#{CGI.escape k.to_s}=#{CGI.escape v.to_s}" }.join('&')
      xml = open("#{page}?#{query}").read
      parse(xml)
    end

    def search(query, options={})
      search_page("#{API_URL}/videos", query, options)
    end

    def search_playlists(query, options={})
      search_page("#{API_URL}/playlists/snippets", query, options.merge(:v => 2))
    end

    def playlist_videos(playlist_id, options={})
      playlist_id = playlist_id.sub(/^PL/, "")
      videos "#{API_URL}/playlists/#{playlist_id}?v=2", options[:format]
    end

    def user_channel_videos(channel_id, options={})
      channel_id = channel_id.sub(/^UC/, "")
      videos "#{API_URL}/users/#{channel_id}/uploads?v=2", options[:format]
    end

    def single(video_id, options={})
      options = options.merge(:key => 'entry')
      videos "#{API_URL}/videos/#{video_id}", :xml, options
    end

    def parse(xml, options={})
      key = options[:key] || 'feed/entry'

      elements_in(xml, key).map do |element|
        entry = xml_to_hash(element)
        entry['video_id'] = if options[:type] == :playlist
          element.elements['*/yt:videoid'].text
        else
          entry['id'].split('/').last
        end

        duration = element.elements['*/yt:duration']
        entry['duration'] = duration.attributes['seconds'] if duration

        no_embed = element.elements['yt:noembed'] || element.elements['yt:private']
        entry['embeddable'] = !(no_embed)

        entry['raw'] = element

        entry
      end
    end

    private

    def videos(url, format, options = {})
      url += '&alt=json' if format == :json
      res = open(url).read
      if format == :json
        res
      else
        parse res, options
      end
    end

    def elements_in(xml, selector)
      entries = []
      doc = REXML::Document.new(xml)
      doc.elements.each(selector) do |element|
        entries << element
      end
      entries
    end

    def xml_to_hash(element)
      Hash[element.children.map do |child|
        [child.name, child.text]
      end]
    end

    def options_with_per_page_and_page(options)
      options = options.dup
      if per_page = options.delete(:per_page)
        options['max-results'] = per_page
      else
        per_page = options['max-results']
      end

      if per_page and page = options.delete(:page)
        options['start-index'] = per_page.to_i * ([page.to_i, 1].max - 1)
      end

      options
    end
  end
end
