# Copyright 2006-2009 Scott Horowitz <stonecrest@gmail.com>
# Copyright 2009-2014 Jonathan Ballet <jon@multani.info>
#
# This file is part of Sonata.
#
# Sonata is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Sonata is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Sonata.  If not, see <http://www.gnu.org/licenses/>.

"""
This module makes Sonata submit the songs played to a Last.fm account.

Example usage:
import scrobbler
self.scrobbler = scrobbler.Scrobbler(self.config)
self.scrobbler.import_module()
self.scrobbler.init()
...
self.scrobbler.handle_change_status(False, self.prevsonginfo)
"""

import logging
import os
import sys
import threading # init, np, post start threads init_thread, do_np, do_post
import time

audioscrobbler = None # imported when first needed

from sonata import mpdhelper as mpdh


class Scrobbler:

    def __init__(self, config):
        self.logger = logging.getLogger(__name__)
        self.config = config

        self.scrob = None
        self.scrob_post = None
        self.scrob_start_time = ""
        self.scrob_playing_duration = 0
        self.scrob_last_prepared = ""

        self.elapsed_now = None

    def import_module(self, _show_error=False):
        """Import the audioscrobbler module"""
        # We need to try to import audioscrobbler either when the app starts
        # (if as_enabled=True) or if the user enables it in prefs.
        global audioscrobbler
        if audioscrobbler is None:
            from sonata import audioscrobbler

    def imported(self):
        """Return True if the audioscrobbler module has been imported"""
        return audioscrobbler is not None

    def init(self):
        """Initialize the Audioscrobbler support if enabled and configured"""
        if audioscrobbler is not None and self.config.as_enabled and \
           len(self.config.as_username) > 0 and \
           len(self.config.as_password_md5) > 0:
            thread = threading.Thread(target=self.init_thread)
            thread.name = "Scrobbler"
            thread.daemon = True
            thread.start()

    def init_thread(self):
        if self.scrob is None:
            self.scrob = audioscrobbler.AudioScrobbler()
        if self.scrob_post is None:
            self.scrob_post = self.scrob.post(self.config.as_username,
                                              self.config.as_password_md5,
                                              verbose=True)
        else:
            if self.scrob_post.authenticated:
                return # We are authenticated
            else:
                self.scrob_post = self.scrob.post(self.config.as_username,
                                                  self.config.as_password_md5,
                                                  verbose=True)
        try:
            self.scrob_post.auth()
        except Exception as e:
            self.logger.error("Error authenticating audioscrobbler: %r", e)
            self.scrob_post = None
        if self.scrob_post:
            self.retrieve_cache()

    def handle_change_status(self, state, prevstate, prevsonginfo,
                             songinfo=None, mpd_time_now=None):
        """Handle changes to play status, submitting info as appropriate"""
        if prevsonginfo and 'time' in prevsonginfo:
            prevsong_time = prevsonginfo.time
        else:
            prevsong_time = None

        if state in ('play', 'pause'):
            elapsed_prev = self.elapsed_now
            self.elapsed_now, length = [float(c) for c in
                                        mpd_time_now.split(':')]
            current_file = songinfo.file
            if prevstate == 'stop':
                # Switched from stop to play, prepare current track:
                self.prepare(songinfo)
            elif (prevsong_time and
                  (self.scrob_last_prepared != current_file or
                   (self.scrob_last_prepared == current_file and
                    elapsed_prev and self.elapsed_now <= 1 and
                    self.elapsed_now < elapsed_prev and length > 0))):
                # New song is playing, post previous track if time criteria is
                # met. In order to account for the situation where the same
                # song is played twice in a row, we will check if previous
                # elapsed time was larger than current and we're at the
                # beginning of the same song now
                if self.scrob_playing_duration > 4 * 60 or \
                   self.scrob_playing_duration > int(prevsong_time) / 2:
                    if self.scrob_start_time != "":
                        self.post(prevsonginfo)
                # Prepare current track:
                self.prepare(songinfo)
            # Keep track of the total amount of time that the current song
            # has been playing:
            now = time.time()
            if prevstate != 'pause':
                self.scrob_playing_duration += now - self.scrob_prev_time
            self.scrob_prev_time = now
        else: # stopped:
            self.elapsed_now = 0
            if prevsong_time:
                if self.scrob_playing_duration > 4 * 60 or \
                   self.scrob_playing_duration > int(prevsong_time) / 2:
                    # User stopped the client, post previous track if time
                    # criteria is met:
                    if self.scrob_start_time != "":
                        self.post(prevsonginfo)

    def auth_changed(self):
        """Try to re-authenticate"""
        if self.scrob_post:
            if self.scrob_post.authenticated:
                self.scrob_post = None

    def prepare(self, songinfo):
        if audioscrobbler is not None:
            self.scrob_start_time = ""
            self.scrob_last_prepared = ""
            self.scrob_playing_duration = 0
            self.scrob_prev_time = time.time()

            if self.config.as_enabled and songinfo:
                # No need to check if the song is 30 seconds or longer,
                # audioscrobbler.py takes care of that.
                if 'time' in songinfo:
                    self.np(songinfo)

                    self.scrob_start_time = str(int(time.time()))
                    self.scrob_last_prepared = songinfo.file

    def np(self, songinfo):
        thread = threading.Thread(target=self.do_np, args=(songinfo,))
        thread.name = "ScrobblerNowPlaying"
        thread.daemon = True
        thread.start()

    def do_np(self, songinfo):
        self.init()
        if self.config.as_enabled and self.scrob_post and songinfo:
            if 'artist' in songinfo and \
               'title' in songinfo and \
               'time' in songinfo:
                album = songinfo.get('album', '')
                tracknumber = songinfo.get('track', '')
                try:
                    self.scrob_post.nowplaying(songinfo.artist,
                                               songinfo.title,
                                               songinfo.time,
                                               tracknumber,
                                               album)
                except:
                    self.logger.exception(
                        "Unable to send 'now playing' data to the scrobbler")
        time.sleep(10)

    def post(self, prevsonginfo):
        self.init()
        if self.config.as_enabled and self.scrob_post and prevsonginfo:
            if 'artist' in prevsonginfo and \
               'title' in prevsonginfo and \
               'time' in prevsonginfo:
                album = prevsonginfo.get('album', '')
                tracknumber = prevsonginfo.get('track', '')
                try:
                    self.scrob_post.addtrack(
                        prevsonginfo.artist,
                        prevsonginfo.title,
                        prevsonginfo.time,
                        self.scrob_start_time,
                        tracknumber,
                        album)
                except:
                    self.logger.critical("Unable to add track to scrobbler")

                thread = threading.Thread(target=self.do_post)
                thread.name = "ScrobblerPost"
                thread.daemon = True
                thread.start()
        self.scrob_start_time = ""

    def do_post(self):
        for _i in range(0, 3):
            if not self.scrob_post:
                return
            if len(self.scrob_post.cache) == 0:
                return
            try:
                self.scrob_post.post()
            except audioscrobbler.AudioScrobblerConnectionError as e:
                self.logger.exception(
                    "Error while posting data to the scrobbler")
            time.sleep(10)

    def save_cache(self):
        """Save the cache in a file"""
        filename = os.path.expanduser('~/.config/sonata/ascache')
        if self.scrob_post:
            self.scrob_post.savecache(filename)

    def retrieve_cache(self):
        filename = os.path.expanduser('~/.config/sonata/ascache')
        if self.scrob_post:
            self.scrob_post.retrievecache(filename)
