require 'json'
require 'http'
require 'launchy'
require 'digest/md5'
require 'uri'
require_relative 'lily/version'
require_relative 'lily/hash'

class Lily
  def initialize(*args)
    @args = args
    @keys = {
      "public" => "238b1080c5a14212261f2c963baaf2db",
      "private" => "85fbaa6644c31d879c3e004fbddb82bf"
    }
    @lily_json = self.get_lily_json
    @api_url = "http://ws.audioscrobbler.com/2.0/"
    @token ||= self.token
  end

  def get_lily_json
    begin # Make this more concise later
      content = File.read(ENV['HOME'] + '/.lily.json')
      lily_json = JSON.parse(content)

      # returns {sk, signature}
    rescue
      puts "Your settings file (~/.lily.json) appears to be empty, please initialize Lily."
    end
  end

  def format_params(params)
    path_arr = []
    sig_str = ""
    params = params.sort_by { |key, value| key }
    params.each do |key, value|
      path_arr.push("#{key}=" + URI.encode(value.to_s))
      sig_str += key + value.to_s if (key != "format")
    end
    sig_str += @keys.private
    sig = Digest::MD5.hexdigest(sig_str)
    path_arr.push("api_sig=#{sig}")
    path = path_arr.join("&")
    return path
  end
  
  def get_album_info(album, artist)
    url = "#{@api_url}?method=album.getinfo&api_key=#{@keys.public}&artist=#{URI.encode(artist)}&album=#{URI.encode(album)}&format=json"
    res = JSON.parse(HTTP.get(url))
    
    begin
      res.album
    rescue
      puts "That album does not exist."
    end
  end

  def token
    url = "http://ws.audioscrobbler.com/2.0/?method=auth.getToken&api_key=#{@keys.public}&api_sig=#{@keys.private}&format=json"
    res = JSON.parse(HTTP.get(url))
    # return res.token
    res.token
  end

  def auth(cmd)
    lastfmURL = "http://www.last.fm/api/auth/?api_key=#{@keys.public}&token=#{@token}"
    
    if cmd == "--no-open" then puts "Open this URL and sign in to give access: #{lastfmURL}" else
      Launchy.open(lastfmURL)
    end
    
    print "Press Enter once you've given access..."
    STDIN.gets
  end
  
  def reset
    # reset sk and config stuff
    File.delete(ENV["HOME"] + "/.lily.json")
    puts "Lily has been reset, please re-initialize."
  end

  def session_key
    sig = Digest::MD5.hexdigest("api_key#{@keys.public}methodauth.getSessiontoken#{@token}#{@keys.private}")
    session = "http://ws.audioscrobbler.com/2.0/?method=auth.getSession&api_key=#{@keys.public}&api_sig=#{sig}&token=#{@token}&format=json"
    res = JSON.parse(HTTP.get(session))
    sk = res.session['key']

    File.open(ENV['HOME'] + '/.lily.json', 'w+') do |file|
      file.write(JSON.pretty_generate({"session_key" => sk}))
    end
  end
  
  def get_dashes(string)
    # create dashes for cosmetic reasons
    string_array = string.split("")
    dashes = ""
    
    string_array.each do
      dashes += "-"
    end
    
    return dashes
  end
  
  def tags(toptags)
    tags = ""
    
    toptags.tag.each do |i|
      if i != toptags.tag[-1]
        tags += i.name + ", "
      elsif
        tags += i.name
      end
    end
    
    tags
  end
  
  def info
    # http://www.last.fm/api/show/user.getInfo
    command = @args.shift
    
    if command == "user"
      # user profile
      username = @args[0]
      info_url = "http://ws.audioscrobbler.com/2.0/?method=user.getinfo&user=" + username + "&api_key=" + @keys.public + "&format=json"
      top_artists_url = "http://ws.audioscrobbler.com/2.0/?method=user.gettopartists&user=" + username + "&api_key=" + @keys.public + "&format=json"
      recent_tracks_url = "http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=" + username + "&api_key=" + @keys.public + "&format=json"
      
      # get profile
      info = JSON.parse(HTTP.get(info_url))
      top_artists = JSON.parse(HTTP.get(top_artists_url))
      recent_tracks = JSON.parse(HTTP.get(recent_tracks_url))
      
      # create dashes for cosmetic reasons
      dashes = get_dashes(username)
      
      # figure out what to show
      information = [username, "#{dashes}\n\n", "profile link: #{info.user.url}", "songs played: #{info.user.playcount}", "top artist: #{top_artists.topartists.artist[0].name}, with #{top_artists.topartists.artist[0].playcount} total plays", "most recent track: #{recent_tracks.recenttracks.track[0].name} by #{recent_tracks.recenttracks.track[0].artist['#text']}"]
      
      if info.user.age != "0" then information.push("age: #{info.user.age}") end
      if info.user.country != "" then information.push("country: #{info.user.country}") end
      
      information.each do |i|
        puts i
      end
    elsif command == "album"
      # album info

      # we got eponymous albums covered :)
      if @args[1] == nil then album, artist = @args[0], @args[0] else album, artist = @args[0], @args[1] end

      album_info = get_album_info(album, artist)
      
      # dashes for cosmetic reasons
      title = album + " by " + artist
      dashes = get_dashes(title)
      
      # figure out what to show
      information = [title, "#{dashes}\n\n", "album link: #{album_info.url}", "played #{album_info.playcount} times by a total of #{album_info.listeners} listeners"]
      
      begin
        if album_info.tags != [] then information.push("tags: #{tags(album_info.tags)}") end
        if album_info.wiki.summary != "" then information.push("\nsummary:\n#{album_info.wiki.summary}") end
      rescue
        # puts "error"
      end
      
      information.each do |i|
        puts i
      end
    elsif command == "song"
      # song info
      track, artist = @args[0], @args[1]
      
      url = "http://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key=" + @keys.public + "&artist=" + artist + "&track=" + track + "&format=json"
      if @args[2]
        album = @args[2]
        url += "&album=#{album}"
      end # album name is optional
      
      song_info = JSON.parse(HTTP.get(url))["track"]
      
      # dashes for cosmetic reasons
      title = track + " by " + artist + " from the album " + song_info.album.title
      dashes = get_dashes(title)
      
      # figure out what to show
      information = [title, "#{dashes}\n\n", "song link: #{song_info.url}", "played #{song_info.playcount} times by a total of #{song_info.listeners} listeners"]
      
      begin
        if song_info.toptags != [] then information.push("top tags: #{tags(song_info.toptags)}") end
        if song_info.wiki.summary != "" then information.push("\nsummary:\n#{song_info.wiki.summary}") end
      rescue
        # nothing to see here!
      end
      
      information.each do |i|
        puts i
      end
    else
      puts "Huh, Lily didn't recognize that."
    end
  end

  def scrobble
    # last.fm/api/show/track.scrobble
    command = @args.shift
    timestamp = Time.now.utc.to_i # UTC time since unix epoch in seconds

    if command == "song"
      # song
      track, artist = @args[0], @args[1]

      if @args[2] then album = @args[2] end # album name is optional

      body = self.format_params({
        "artist" => artist,
        "method" => "track.scrobble",
        "sk" => @lily_json.session_key,
        "timestamp" => timestamp,
        "track" => track,
        "api_key" => @keys.public,
        "format" => "json",
        "album" => album
      })

      res = HTTP.post(@api_url, :body => body)

      puts "#{track} by #{artist} has been scrobbled."
    elsif command == "album"
      # album

      # we got eponymous albums covered :)
      if @args[1] == nil then album, artist = @args[0], @args[0] else album, artist = @args[0], @args[1] end

      album_info = get_album_info(album, artist)
      album_length = 0
      
      # get full length of album in seconds
      album_info.tracks.track.each do |i|
        album_length += i.duration.to_i
      end
        
      puts "Whole album length: #{album_length} seconds."
        
      # set time when album should have started (a.k.a. album length ago), or if the '--live' option is specified then just start it right now
      if @args[-1] == "--live" then time = timestamp else time = timestamp - album_length end

      puts "Scrobbling your album starting at " + Time.at(time).utc.strftime("%I:%M:%S %p") + " UTC"
        
      # begin scrobbling
      album_info.tracks.track.each do |i|
        time += i.duration.to_i
        
        # scrobble track
        body = self.format_params({
          "artist" => i.artist.name,
          "method" => "track.scrobble",
          "sk" => @lily_json.session_key,
          "timestamp" => time,
          "track" => i.name,
          "api_key" => @keys.public,
          "format" => "json",
          "album" => album_info.name
        })

        res = HTTP.post(@api_url, :body => body)
      end

      puts "#{album} by #{artist} has been scrobbled."
    else
      puts "Huh, Lily didn't recognize that."
    end
  end

  def love
    # last.fm/api/show/track.love
    command = @args.shift

    if command == "song"
      track, artist = @args[0], @args[1]
      if @args[2] then album = @args[2] end # album name is optional

      body = self.format_params({
        "track" => track,
        "artist" => artist,
        "method" => "track.love",
        "sk" => @lily_json.session_key,
        "api_key" => @keys.public,
        "format" => "json",
        "album" => album
      })

      res = HTTP.post(@api_url, :body => body)

      puts "#{track} by #{artist} has been loved ♥"
    elsif command == "album"
      # we've got you covered, eponymous albums
      if @args[1] == nil then album, artist = @args[0], @args[0] else album, artist = @args[0], @args[1] end

      album_info = get_album_info(album, artist)

      album_info.tracks.track.each do |i|
        body = self.format_params({
          "track" => i.name,
          "artist" => artist,
          "method" => "track.love",
          "sk" => @lily_json.session_key,
          "api_key" => @keys.public,
          "format" => "json",
          "album" => album
        })

        res = HTTP.post(@api_url, :body => body)
      end

      puts "#{album} by #{artist} has been loved ♥"
    else
      puts "Huh, Lily didn't recognize that."
    end
  end

  def unlove
    # last.fm/api/show/track.unlove
    command = @args.shift

    if command == "song"
      track, artist = @args[0], @args[1]
      if @args[2] then album = @args[2] end # album name is optional

      body = self.format_params({
        "track" => track,
        "artist" => artist,
        "method" => "track.unlove",
        "sk" => @lily_json.session_key,
        "api_key" => @keys.public,
        "format" => "json",
        "album" => album
      })

      res = HTTP.post(@api_url, :body => body)

      puts "#{track} by #{artist} has been unloved :("
    elsif command == "album"
      # we've got you covered, eponymous albums
      if @args[1] == nil then album, artist = @args[0], @args[0] else album, artist = @args[0], @args[1] end

      album_info = get_album_info(album, artist)

      album_info.tracks.track.each do |i|
        body = self.format_params({
          "track" => track.name,
          "artist" => artist,
          "method" => "track.unlove",
          "sk" => @lily_json.session_key,
          "api_key" => @keys.public,
          "format" => "json",
          "album" => album
        })

        res = HTTP.post(@api_url, :body => body)
      end

      puts "#{album} by #{artist} has been unloved :("
    else
      puts "Huh, Lily didn't recognize that."
    end
  end

  def run
    first = @args.shift

    case first
      when 'scrobble' then self.scrobble
      when 'init' then self.init
      when 'love' then self.love
      when 'unlove' then self.unlove
      when 'version', '--version' then puts self.version
      when 'info' then self.info
      when 'reset' then self.reset
      else self.help
    end
  end

  def help
    puts """
    Lily, the Last.fm CLI.

    Usage:
      General Commands:
        lily init [--no-open]
        lily help
        lily reset
        lily (version | --version)
        
      Scrobbling:
        lily scrobble song (<song> <artist>) [<album>]
        lily scrobble album (<album> <artist> | <eponymous album>) [--live]
        
      Loving/Unloving:
        lily (love | unlove) song (<song> <artist>) [<album>]
        lily (love | unlove) album (<album> <artist> | <eponymous album>)
        
      Information:
        lily info song (<song> <artist>) [<album>]
        lily info album (<album> <artist> | <eponymous album>)
        lily info user <username>
    """
  end

  def init
    auth(@args[-1])
    session_key
    puts "Initialized Lily successfully at file ~/.lily.json"
  end

  def version
    Version::STRING
  end
end
