# Copyright (C) 2009-2010 Gloobus Developers
#
#This program 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 2
#of the License, or (at your option) any later version.
#
#This program 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 this program; if not, write to the Free Software
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

# todo: window constant size ???
# todo:  change 'Go to webpage' to 'Add lyrics' when lyrics not found
#        for sites that support adding lyrics

import gobject
import os
import re
import string
import sys
import threading

import common
import widgets

from common import c
from common import _
from widgets import LyricWindow

gobject.threads_init()

# ============================= LYRICS SEARCH ================================ #

class LyricSearch:
    """
        Lyrics/tabs searching and dispalying class
    """
    def __init__(self, config):
        self.lyric_engines = {}
        self.tab_engines = {}
        self.windows = {
            'lyrics'  : None,
            'tabs'   : None,
            'shared' : None
        }
        self.set_config(config)
        self._load_engines()
        c.debug('LYRICS: init done')

    def _load_engines(self):
        """
            Loads all installed lyrics and tabs engines
        """
        c.debug('LYRICS: loading engines')
        lyric_engines = common.get_lyric_engines()
        for name in lyric_engines:
            exec "from " + name + " import *"
            exec "engine = " + name + "()"
            self.lyric_engines[name] = engine

        tab_engines = common.get_tab_engines()
        for name in tab_engines:
            exec "from " + name + " import *"
            exec "engine = " + name + "()"
            self.tab_engines[name] = engine

    # ============================ PUBLIC METHODS =========================== #

    def set_config(self, config):
        """
            Parses configuration and applys required changes to windows
        """
        c.debug('LYRICS: setting configuration')
        self.config = config
        self.lyrics_source = config.get("Lyrics","name")
        self.tabs_source = config.get("Tabs","name")
        self.conf_window = config.get("Lyrics","window")
        if self.conf_window == 'new':
            for n, w in self.windows.items():
                if w: w.destroy()
        elif self.conf_window == 'personal':
            if self.windows['shared']:
                self.windows['shared'].destroy()
        elif self.conf_window == 'shared':
            if self.windows['lyrics']:
                self.windows['lyrics'].destroy()
            if self.windows['tabs']:
                self.windows['tabs'].destroy()
        self.try_all = config.getboolean("Lyrics","try_all")
        self.widget = config.getboolean('Lyrics', 'widget')
        for n, w in self.windows.items():
            if w: w.set_widget(self.widget)
        self.refresh = config.getboolean('Lyrics', 'refresh')
        self.cache = config.getboolean('Lyrics', 'cache')
        #self.cache_time = config.getint('Lyrics', 'cache_time')

    def song_changed(self, track):
        """
            If windows are open load new lyrics/tabs
        """
        if self.refresh:
            if self.windows['lyrics']:
                self.load('lyrics', track)
            if self.windows['tabs']:
                self.load('tabs', track)

    def load_lyrics(self, track):
        self.load('lyrics', track)

    def load_tabs(self, track):
        self.load('tabs', track)

    def load(self, target, track):
        """
            Loads lyrics/tabs, creates a new window if needed,
            creates a new thread for every lyrics/tabs
        """
        c.debug('LYRICS: loading ' + target)
        if self.conf_window == 'new':
            window = LyricWindow()
            window.connect('destroy', self.remove_window, None)
        elif self.conf_window == 'separate':
            if not self.windows[target]:
                self.windows[target] = LyricWindow()
            window = self.windows[target]
            window.connect('destroy', self.remove_window, target)
        elif self.conf_window == 'shared':
            if not self.windows['shared']:
                self.windows['shared'] = LyricWindow()
            window = self.windows['shared']
            window.connect('destroy', self.remove_window, 'shared')

        window.set_widget(self.widget)
        if window.loader:
            self.remove_window(window,None)

        if target == 'lyrics':
            not_found = _('No lyrics found')
            window.set_title(_('CoverGloobus Lyrics'))
            engines = self.lyric_engines
            source = self.lyrics_source
        elif target == 'tabs':
            not_found = _('No tabs found')
            window.set_title(_('CoverGloobus Tabs'))
            engines = self.tab_engines
            source = self.tabs_source
        
        if track.title != '' and track.artist != '':
            window.set_text(_('Searching ...'), common.GLOOBUS_URL)
            loader = LyricLoader(track,window, engines,
                source, not_found, self.try_all, self.cache, target)
            window.set_loader(loader)
            loader.start()
        else:
            window.set_text(_('No song playing'), common.GLOOBUS_URL)
        window.show_all()

    def remove_window(self, widget, target):
        """
            When window is closed stops its thread
            and assigns its pointer to None
        """
        widget.loader.stop()
        if target:
            self.windows[target] = None

# ============================== LYRIC LOADER ================================ #

class LyricLoader(threading.Thread):
    """
        Threaded lyrics/tabs loader. After downloading, places the result
        in its window.
    """
    def __init__(self, track, window, engines, source, not_found, try_all, cache, target):
        threading.Thread.__init__(self)
        self._num = common.THREAD_COUNTER
        common.THREAD_COUNTER += 1
        self.track = track
        self.engines = engines
        self.source = source
        self.window = window
        self.not_found = not_found
        self.try_all = try_all
        self.cache = cache
        #self.cache_time = cache_time
        self.target = target

        self.load = True
        self.load_lock = threading.Lock()
        self._cached = False

    def run(self):
        """
            Main thread function
        """
        c.debug('LYRIC: thread ' + str(self._num) + ' start')
        data = self.check_cache()
        if data:
            source = data[0]
            url = data[1]
            text = data[2]
        else:
            text = None
            url = common.GLOOBUS_URL
        if not text:
            c.debug('LYRIC: trying to load from ' + self.source)
            try:
                text = self.engines[self.source].get_data(self.track.artist,
                    self.track.title)
                url = self.engines[self.source].get_webpage()
            except Exception as e:
                text = None
                c.error('LYRICS: error while downloading ' + self.target)
                c.debug('LYRICS: got an exception - ' + str(type(e)) + ', '
                    + str(e))
                url = self.engines[self.source].get_webpage()
                
        
        if not text and self.try_all:
            self.check_load()
            for name, engine in self.engines.items():
                if name != self.source:
                    c.debug('LYRIC: trying to load from ' + name)
                    try:
                        text = engine.get_data(self.track.artist,
                            self.track.title)
                        url = engine.get_webpage()
                    except Exception as e:
                        text = None
                        c.error('LYRIC: error while downloading ' + self.target)
                        c.debug('LYRICS: got an exception - ' + str(type(e))
                        + ', ' + str(e))
                        url = self.engines[self.source].get_webpage()
                    if text:
                        break
        if not text:
            c.debug('LYRIC: lyric not found')
            text = self.not_found
        elif not self._cached:
            self.save_cache(text, url, self.source)
        
        self.check_load()
        gobject.idle_add(self.window.set_text, text, url)
        c.debug('LYRIC: thread ' + str(self._num) + ' end')

    def check_load(self):
        """
            Checks if thread should continue its work
            @return: boolean
        """
        self.load_lock.acquire()
        if not self.load:
            c.debug('LYRIC: thread ' + str(self._num) + ' exit')
            sys.exit()
        self.load_lock.release()

    def check_cache(self):
        """
            Checks if song lyrics/tabs are in cache

            @return: None if nothing found, a string otherwise
        """
        if self.cache:
            c.debug('LYRIC: checking cache')
            PATH = common.CACHE_PATH + self.target + '/'
            filename = self.prepare(self.track.artist, True) + ' - ' + self.prepare(self.track.title)
            if os.path.exists(PATH+filename):
                f = open(PATH+filename, 'r')
                source = f.readline()
                url = f.readline()
                data = f.read()
                f.close()
                self._cached = True
                return (source,url,data)
        return None

    def save_cache(self, data, url, source):
        """
            Saves songs lyrics/tabs in cache

            @param data: string with lyrics/tabs to save
        """
        if self.cache:
            c.debug('LYRIC: saving cache')
            PATH = common.CACHE_PATH + self.target + '/'
            if not os.path.exists(PATH):
                os.mkdir(PATH)
            filename = self.prepare(self.track.artist, True) + ' - ' + self.prepare(self.track.title)
            f = open(PATH+filename, 'w')
            data = source + '\n' + url + '\n' + data
            f.write(data)
            f.close()
            self._cached = True

    def prepare(self, str, artist=False):
        """
            Prepares song title and artist name for cache file name.

            @param str: string - title or artist
            @param artist: boolean - True if str is artist name
            @return: string - prepared artist or title name
        """
        s = str.decode('utf-8')
        s = s.lower()
        if artist:
            s = re.sub('^the\s*', '', s)
        s = re.sub('[\[\(]\s*(live|bonus|feat|ft).*?[\]\)]', '', s)
        s = s.encode('utf-8')
        s = s.translate(string.maketrans('',''), string.punctuation + ' -')
        return s

    def stop(self):
        """
            Stops the thread
        """
        self.load_lock.acquire()
        if self.load:
            c.debug('LYRIC: stopping thread ' + str(self._num))
            self.load = False
        self.load_lock.release()
