# Copyright (c) 2008-2009 Pablo Flouret <quuxbaz@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met: Redistributions of
# source code must retain the above copyright notice, this list of conditions and
# the following disclaimer. Redistributions in binary form must reproduce the
# above copyright notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the distribution.
# Neither the name of the software nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
import threading
 
import urwid
 
import commands
import config
import listbox
import lyricwiki
import signals
import widgets
import xmms
 
class FetcherThread(threading.Thread):
  def __init__(self, lyrics, info, url=None):
    self.lyrics = lyrics
    self.info = info
    self.url = url
    self.abort = False
 
    super(FetcherThread, self).__init__()
    self.setDaemon(True)
 
  def save_lyrics(self, lyrics):
    self.lyrics.xs.medialib_property_set(
        self.info['id'], 'lyrics', lyrics, 'client/generic', sync=False)
 
  def from_url(self):
    self.lyrics.set_info("fetching lyrics...")
 
    lyrics = lyricwiki.get_lyrics(self.url)
 
    if self.abort:
      return
 
    if lyrics:
      self.save_lyrics(lyrics)
      self.lyrics.set_lyrics(lyrics)
    else:
      self.lyrics.set_info("some kind of error occurred while fetching the lyrics, try again!")
 
  def run(self):
    if self.url:
      self.from_url()
      return
 
    self.lyrics.set_info("searching for lyrics...")
 
    artist, title = self.info.get('artist'), self.info.get('title')
 
    if not artist or not title:
      self.lyrics.set_info("artist or title not set, not enough info to search for lyrics")
      return
 
    lw = lyricwiki.LyricWiki(artist, title, self.info.get('album'), self.info.get('tracknr'))
    lyrics = lw.get()
 
    if lyrics:
      self.save_lyrics(lyrics)
    else:
      self.lyrics.set_info("no direct match, searching for results...")
      results = lw.get_song_results()
 
    if self.abort:
      return
 
    if lyrics:
      self.lyrics.set_lyrics(lyrics)
    else:
      self.lyrics.show_results(results)
 
 
class ResultsFetcherThread(threading.Thread):
  def __init__(self, lyrics, query):
    self.lyrics = lyrics
    self.query = query
    self.abort = False
 
    super(ResultsFetcherThread, self).__init__()
    self.setDaemon(True)
 
  def run(self):
    self.lyrics.set_info("searching...")
    signals.emit('need-redraw')
 
    results = lyricwiki.get_google_results(self.query)
 
    if self.abort:
      return
 
    self.lyrics.show_results(results)
 
 
class LyricsListBox(urwid.ListBox):
 
  def set_rows(self, rows):
    self.body = urwid.SimpleListWalker(rows)
    urwid.connect_signal(self.body, "modified", self._invalidate)
    self._invalidate()
 
  def keypress(self, size, key):
    k = self.__super.keypress(size, key)
    if k in ('up', 'down'):
      # don't let a focus change happen in the pile if up or down are unhandled
      return None
    return k
 
 
class ResultsListBox(LyricsListBox, listbox.AttrListBox):
 
  def __init__(self, lyrics, body):
    self.lyrics = lyrics
    self.__super.__init__(body, attr='default', focus_attr='focus', focus_str='-focus')
 
  def cmd_activate(self, args):
    f = self.get_focus()
    if f and f[0]:
      self.lyrics.fetch_lyrics(f[0].url)
 
 
class Lyrics(urwid.Pile):
  context_name = 'lyrics'
 
  def __init__(self, app):
    self.app = app
    self.xs = xmms.get()
 
    self.on_display = False
    self.info = None
    self.fetcher_thread = None
    self.results_fetcher_thread = None
    self.lock = threading.RLock()
 
    self.input = widgets.InputEdit(caption='search lyricwiki.org > ')
    urwid.connect_signal(self.input, 'done', self.search)
 
    self.info_w = urwid.Text('')
 
    self.llb = LyricsListBox([])
    self.llbw = urwid.Padding(self.llb, 'center', ('relative', 95))
    self.rlb = ResultsListBox(self, [])
 
    blank = urwid.Text('')
    self.__super.__init__([('flow', self.input),
                           ('flow', self.info_w),
                           ('flow', blank),
                           self.llbw], 3)
 
    signals.connect('xmms-playback-current-info', self.on_xmms_playback_current_info)
 
  def on_xmms_playback_current_info(self, info):
    self.info = info
    if self.on_display:
      self.fetch_lyrics()
 
  def search(self, widget, query):
    if self.results_fetcher_thread:
      self.results_fetcher_thread.abort = True
    self.results_fetcher_thread = ResultsFetcherThread(self, query)
    self.results_fetcher_thread.start()
 
  def fetch_lyrics(self, url=None):
    try:
      self.lock.acquire()
 
      self.set_lyrics('')
 
      if self.fetcher_thread:
        self.fetcher_thread.abort = True
 
      if not url:
        lyrics = self.info.get('lyrics')
 
        s = "%s %s" % (self.info.get('artist', ''), self.info.get('title', ''))
        self.input.set_edit_text(s)
        self.input.edit_pos = len(s)
 
        if lyrics:
          self.set_lyrics(lyrics)
          return
      self.fetcher_thread = FetcherThread(self, self.info, url)
      self.fetcher_thread.start()
    finally:
      self.lock.release()
 
  def set_lyrics(self, lyrics):
    try:
      self.lock.acquire()
 
      in_list_w = self.widget_list[-1]
      if in_list_w != self.llbw:
        self.widget_list[-1] = self.llbw
        if self.focus_item == in_list_w:
          self.set_focus(self.llbw)
 
      if not self.info.get('lyrics'):
        self.info[('client/generic', 'lyrics')] = lyrics
 
      self.llb.set_rows([urwid.Text(l) for l in lyrics.split('\n')])
      self.set_info()
      self._invalidate()
      signals.emit('need-redraw')
    finally:
      self.lock.release()
 
  def show_results(self, results):
    try:
      self.lock.acquire()
 
      if self.widget_list[-1] != self.rlb:
        self.widget_list[-1] = self.rlb
        self.set_focus(self.rlb)
 
      if results:
        self.rlb.set_rows([widgets.LyricResultWidget(r[0], r[1]) for r in results])
        self.set_info()
      else:
        self.set_info("no results found :/")
 
      self._invalidate()
      signals.emit('need-redraw')
    finally:
      self.lock.release()
 
  def set_info(self, msg=""):
    self.info_w.set_text(msg)
    self._invalidate()
    signals.emit('need-redraw')
 
  def cmd_cycle(self, args=None):
    cur = self.widget_list.index(self.focus_item)
    n = len(self.widget_list)
    i = (cur + 1) % n
    while i != cur and not self.widget_list[i].selectable():
      i = (i + 1) % n
    self.set_focus(i)
 
  def tab_loaded(self):
    self.on_display = True
 
    if not self.info:
      self.on_xmms_playback_current_info(self.xs.playback_current_info())
 
    self.fetch_lyrics()
 
  def tab_unloaded(self):
    self.on_display = False
 
  def get_contexts(self):
    return [self, self.widget_list[-1]]