#!/usr/bin/env python3
import curses, random, subprocess, argparse, re, json, pylast, datetime, mutagenx, mimetypes
from michimusemodels import *

class Player(object):
  def __init__(self, path):
    pass
  def play(self):
    raise NotImplementedError("Method play is not implemented for this player type")
  def pause(self):
    raise NotImplementedError("Method pause is not implemented for this player type")
  def toggle(self):
    raise NotImplementedError("Method toggle is not implemented for this player type")
  def time(self):
    raise NotImplementedError("Method time is not implemented for this player type")
  def destroy(self):
    raise NotImplementedError("Method destroy is not implemented for this player type")
  def is_over_yet(self):
    raise NotImplementedError("Method is_over_yet is not implemented for this player type")
  def is_playing(self):
    raise NotImplementedError("Method is_playing is not implemented for this player type")

class FFplayer(Player):
  def __init__(self, path):
    self.path = path
    self.process = subprocess.Popen(['ffplay','-nodisp','-loglevel','quiet','-autoexit',path])
  def destroy(self):
    if not self.is_over_yet():
      self.process.kill()
  def is_over_yet(self):
    if self.process.poll() is None:
      return False
    else:
      return True

class VLCplayer(Player):
  def __init__(self, path):
    self.path = path
    self.process = subprocess.Popen(['vlc', '--play-and-exit', '-q', '-I', 'rc', path], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL)
    self.playing = True
  def is_playing(self):
    return self.playing
  def command(self, cmd):
    self.process.stdin.write(('%s\n' % cmd).encode())
    self.process.stdin.flush()
  def toggle(self):
    self.command('pause')
    self.playing = False
  def play(self):
    if not self.playing:
      self.toggle()
  def pause(self):
    if self.playing:
      self.toggle()
  def destroy(self):
    if not self.is_over_yet():
      self.command('quit')
  def is_over_yet(self):
    if self.process.poll() is None:
      return False
    else:
      return True

class TiMidityplayer(Player):
  def __init__(self, path):
    self.path = path
    self.process = subprocess.Popen(['timidity', '-idqqq', path])
  def is_over_yet(self):
    if self.process.poll() is None:
      return False
    else:
      return True
  def destroy(self):
    if not self.is_over_yet():
      self.process.kill()

class NoahCurses(object):
  titlebar = "noahcurses"
  playa = None
  def __init__(self, args, conf):
    self.conf = conf
    if "lastfm" in self.conf:
      self.lastfm = pylast.LastFMNetwork(api_key=self.conf["lastfm"]["api_key"],api_secret=self.conf["lastfm"]["api_secret"],username=self.conf["lastfm"]["username"],password_hash=pylast.md5(self.conf["lastfm"]["password"]))
    else:
      self.lastfm = None
    if 'tag' in args:
      self.tag = re.compile(args.tag)
    self.current = None
  def draw(self):
    self.dims = self.screen.getmaxyx()
    self.screen.clear()
    self.screen.addstr(2, 2, "Now playing", curses.color_pair(2))
    self.screen.addstr(3, 4, self.current.get_title(), curses.color_pair(1))
    self.screen.addstr(4, 2, "by", curses.color_pair(3))
    self.screen.addstr(5, 4, self.current.get_artist(), curses.color_pair(1))
    self.screen.addstr(6, 2, "from", curses.color_pair(4))
    self.screen.addstr(7, 4, self.current.get_album(), curses.color_pair(1))
    self.screen.border()
    self.screen.addstr(0, round(self.dims[1]/2-len(self.titlebar)/2), self.titlebar, curses.color_pair(5))
    self.screen.refresh()
  def reloadtracks(self):
    self.tracks = []
    if self.tag is not None:
      for tag in Tag.select():
        if self.tag.search(tag.name):
          for tracktag in tag.marks:
            if tracktag.track not in self.tracks:
              self.tracks.append(tracktag.track)
    else:
      for track in Track.select():
        self.tracks.append(track)
    random.shuffle(self.tracks)
  def newtrack(self):
    self.lasttime = datetime.datetime.now()
    self.scrobbled = False
    if len(self.tracks) < 1:
      self.reloadtracks()
    if self.playa:
      self.playa.destroy()
    self.current = self.tracks.pop()
    self.mdat = mutagenx.File(self.current.path)
    if self.mdat and 'title' in self.mdat:
      self.mttitle = self.mdat['title'][0]
    else:
      self.mttitle = self.current.title
    if self.mdat and 'artist' in self.mdat:
      self.mtartist = self.mdat['artist'][0]
    else:
      self.mtartist = self.current.artist
    if self.mdat and 'album' in self.mdat:
      self.mtalbum = self.mdat['album'][0]
    else:
      self.mtalbum = self.current.album
    if self.mdat and 'musicbrainz_trackid' in self.mdat:
      self.mtid = self.mdat['musicbrainz_trackid'][0]
    else:
      self.mbtid = None
    mimetype = mimetypes.guess_type(self.current.path)[0]
    if 'midi' in mimetype:
      self.playa = TiMidityplayer(self.current.path)
    else:
      self.playa = VLCplayer(self.current.path)
    self.draw()
    if self.lastfm is not None:
      try:
        self.lastfm.update_now_playing(artist=self.mtartist, album=self.mtalbum, title=self.mttitle, mbid=self.mtid)
      except pylast.NetworkError:
        pass
  def main(self, screen):
    self.screen = screen
    curses.curs_set(0)
    curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
    curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
    curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK)
    curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK)
    curses.init_pair(5, curses.COLOR_BLACK, curses.COLOR_WHITE)
    self.screen.nodelay(1)
    self.reloadtracks()
    self.newtrack()
    while True:
      if self.playa.is_over_yet():
        self.newtrack()
      event = self.screen.getch()
      if event == ord("q"):
        self.playa.destroy()
        break
      if event == ord("n") or event == curses.KEY_RIGHT:
        self.playa.destroy()
        self.newtrack()
      if event == ord("s"):
        self.reloadtracks()
        self.newtrack()
      if event == ord("r") or self.screen.getmaxyx() != self.dims:
        self.draw()
      if (datetime.datetime.now() - self.lasttime).total_seconds() > 60 and self.lastfm is not None and not self.scrobbled:
        try:
          self.lastfm.scrobble(timestamp=round(self.lasttime.timestamp()), artist=self.mtartist, album=self.mtalbum, title=self.mttitle, mbid=self.mtid)
        except pylast.NetworkError:
          pass
        self.scrobbled = True

if __name__ == '__main__':
  parser = argparse.ArgumentParser()
  if not os.path.exists(os.path.expanduser('~/.config/michimuseconf.json')):
    with open(os.path.expanduser('~/.config/michimuseconf.json'),'w') as confile:
      confile.write(json.dumps({'regex':{'artist':'.+','album':'.+','title':'.+','path':'.+'}}, indent=4))
  with open(os.path.expanduser('~/.config/michimuseconf.json')) as confile:
    defreg = json.loads(confile.read())
  parser.add_argument('tag', type=str, help='regex to select tags',)
  args = parser.parse_args()
  curses.wrapper(NoahCurses(args, defreg).main)
