#!/usr/bin/python
import os,sys,string,re,fnmatch
import eyeD3

from pysqlite2 import dbapi2 as sqlite

from optparse import OptionParser

parser = OptionParser()
parser.add_option("-d", "--database",
                  dest="database",
                  help="write ID3 navigation to DATABASE", metavar="DATABASE")
parser.add_option("-c", "--collect",
                  dest="music",
                  help="Recurse music directory and collect ID3 information")
parser.add_option("-n", "--navigation",
                  action="store_true",
                  dest="navigation",
                  default=False,
                  help="Create navigation")
parser.add_option("-s", "--show",
                  dest="show",
                  help="Show menu starting at PATH", metavar="PATH")
parser.add_option("-l", "--limit",
                  dest="limit",
                  type="int",
                  default=-1,
                  help="Limit depth for menu show")
parser.add_option("-r", "--report",
                  action="store_true",
                  dest="report",
                  default=False,
                  help="Report statistics")
parser.add_option("-o", "--orphans",
                  action="store_true",
                  dest="orphans",
                  default=False,
                  help="Report menu integrity")

(options, args) = parser.parse_args()

if len(args) != 0:
   parser.error("Unexpected parameters")

if (not options.database):
   parser.error("No database specified")

if not options.report and not options.music and not options.navigation and options.show is None and not options.orphans:
   parser.error("Nothing to do")

db = sqlite.connect(options.database)

cursor = db.cursor()

def report():
   cursor.execute('select count(*) from nav')
   (nav, )=cursor.fetchone()
   cursor.execute('select count(*) from track')
   (track, )=cursor.fetchone()
   cursor.execute('select count(distinct genre) from track')
   (genres, )=cursor.fetchone()
   cursor.execute('select count(distinct artist) from track')
   (artists, )=cursor.fetchone()
   cursor.execute('select count(distinct album) from track')
   (albums, )=cursor.fetchone()
   print "%d tracks" % (track,)
   print "%d genres" % (genres,)
   print "%d albums" % (albums,)
   print "%d artists" % (artists,)
   print "%d navigation entries" % (nav,)

def showMenu(parent, indent, todo):
   if todo == 0: return

   c = db.cursor()

   if parent == '/':
      prefix = '/'
   else:
      prefix = parent + '/'

   c.execute("""select n.name, n.track, COALESCE(t.album, '') as album, COALESCE(t.trackno, 0) as trackno, COALESCE(t.genre, '')
               from nav n
               left join track t on n.track = t.filename
               where parent=?
               order by n.rank, album, trackno, n.name""", (parent,))
   for (name, track, album, trackno, genre) in c:
      name = name.encode('utf-8')
      genre = genre.encode('utf-8')
      if track: track = track.encode('utf-8')
      #print '%s %s' % ('+' * indent, row[1].encode('utf-8'))
      if track:
         if genre: genre = ': %s' % (genre,)
         print '%s%s [%s%s]' % (prefix, name, track, genre)
      else:
         print '%s%s' % (prefix, name)
         showMenu(prefix + name, indent + 1, todo - 1)
   
def dropTable(table):
   try:
      cursor.execute('drop table ' + table)
   except:
      pass

def dropIndex(index):
   try:
      cursor.execute('drop index ' + index)
   except:
      pass

def createMenu():
   cursor.execute('select count(*) from track')
   (tracks, )=cursor.fetchone()

   if tracks == 0:
      print "No tracks, so no navigation to rebuild!"
      return

   dropTable('nav')
   cursor.execute('create table nav (rank, parent COLLATE NOCASE, name COLLATE NOCASE, track)')

   dropIndex('idx_track_filename')
   dropIndex('idx_track_album')
   dropIndex('idx_track_genre')
   dropIndex('idx_track_artist')
   dropIndex('idx_nav_parent')
   dropIndex('idx_nav_name')

   cursor.execute('create index idx_track_filename on track (filename)')
   cursor.execute('create index idx_track_album on track (album)')
   cursor.execute('create index idx_track_genre on track (genre)')
   cursor.execute('create index idx_track_artist on track (artist)')
   cursor.execute('create index idx_nav_parent on nav (parent)')
   cursor.execute('create index idx_nav_name on nav (name)')

   # music: is implied

   # music.artists.[artist=*].[album=*].tracks
   cursor.execute("insert into nav (rank, parent, name) values (0, '/', 'Artists')")
   cursor.execute("insert into nav (rank, parent, name) values (0, '/Artists', 'All artists')")
   cursor.execute("insert into nav (rank, parent, name) values (0, '/Artists/All artists', 'All albums')")
   cursor.execute("insert into nav (rank, parent, name, track) select distinct 1, '/Artists/All artists/All albums', title, filename from track")

   # music.artists.[artist=*].[album=%album%].tracks
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 1, '/Artists/All artists', album from track where not album is null""")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Artists/All artists/' || album, title, filename from track where not album is null""")

   # music.artists.[artist=%artist%].[album=*].tracks
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 1, '/Artists', artist from track where not artist is NULL""")
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 0, '/Artists/' || artist, 'All albums' from track where not artist is NULL""")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Artists/' || artist || '/All albums', title, filename from track where not artist is NULL""")

   # music.artists.[artist=%artist%].[album=%album%].tracks
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 1, '/Artists/' || artist, album from track where not (artist is NULL or album is NULL)""")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Artists/' || artist || '/' || album, title, filename from track where not (artist is NULL or album is NULL)""")

   # music.albums.[album=*].tracks
   cursor.execute("insert into nav (rank, parent, name) values (0, '/', 'Albums')")
   cursor.execute("insert into nav (rank, parent, name) values (0, '/Albums', 'All albums')")
   cursor.execute("insert into nav (rank, parent, name, track) select distinct 1, '/Albums/All albums', title, filename from track")

   # music.albums.[album=%album%].tracks
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 1, '/Albums', album from track where not album is NULL""")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Albums/' || album, title, filename from track where not album is NULL""")

   # music.songs.tracks
   cursor.execute("insert into nav (rank, parent, name) values (0, '/', 'Songs')")
   cursor.execute("insert into nav (rank, parent, name, track) select distinct 1, '/Songs', title, filename from track")

   # music.genres.[genre=*].[artist=*].[album=*].tracks
   cursor.execute("insert into nav (rank, parent, name) values (0, '/', 'Genre')")
   cursor.execute("insert into nav (rank, parent, name) values (0, '/Genre', 'All genres')")
   cursor.execute("insert into nav (rank, parent, name) values (0, '/Genre/All genres', 'All artists')")
   cursor.execute("insert into nav (rank, parent, name) values (0, '/Genre/All genres/All artists', 'All albums')")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Genre/All genres/All artists/All albums', title, filename from track""")

   # music.genres.[genre=*].[artist=*].[album=%album%].tracks
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 1, '/Genre/All genres/All artists', album from track where not album is NULL""")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Genre/All genres/All artists/' || album, title, filename from track where not album is NULL""")

   # music.genres.[genre=*].[artist=%artist%].[album=*].songs
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 1, '/Genre/All genres', artist from track where not artist is NULL""")
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 0, '/Genre/All genres/' || artist, 'All albums' from track where not artist is NULL""")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Genre/All genres/' || artist || '/All albums', title, filename from track where not artist is NULL""")

   # music.genres.[genre=*].[artist=%artist%].[album=%album%].songs
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 0, '/Genre/All genres/' || artist, album from track where not (artist is NULL or album is NULL)""")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Genre/All genres/' || artist || '/' || album, title, filename from track""")

   # music.genres.[genre=%genre%].[artist=*].[album=*].songs
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 1, '/Genre', genre from track where not genre is NULL""")
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 0, '/Genre/' || genre, 'All artists' from track where not genre is NULL""")
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 0, '/Genre/' || genre || '/All artists', 'All albums' from track where not genre is NULL""")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Genre/' || genre || '/All artists/All albums', title, filename from track""")

   # music.genres.[genre=%genre%].[artist=*].[album=%album%].songs
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 1, '/Genre/' || genre || '/All artists', album from track where not (genre is NULL or album is NULL)""")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Genre/' || genre || '/All artists/' || album, title, filename from track
                     where not (genre is NULL or album is NULL)""")

   # music.genres.[genre=%genre%].[artist=%artist%].[album=*].songs
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 1, '/Genre/' || genre, artist from track where not (genre is NULL or artist is NULL)""")
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 0, '/Genre/' || genre || '/' || artist, 'All albums' from track where not (genre is NULL or artist is NULL)""")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Genre/' || genre || '/' || artist || '/All albums', title, filename from track
                     where not (genre is NULL or artist is NULL)""")

   # music.genres.[genre=%genre%].[artist=%artist%].[album=%album%].songs
   cursor.execute("""insert into nav (rank, parent, name)
                     select distinct 1, '/Genre/' || genre || '/' || artist, album from track
                     where not (genre is NULL or artist is NULL or album is NULL)""")
   cursor.execute("""insert into nav (rank, parent, name, track)
                     select distinct 1, '/Genre/' || genre || '/' || artist || '/' || album, title, filename from track
                     where not (genre is NULL or artist is NULL or album is NULL)""")
   db.commit()
   cursor.execute("ANALYZE")

def inspect(mp3):
   tag = eyeD3.Tag()
   tag.link(mp3)

   try:
      title = tag.getTitle()
      if title is None:
         title = ''
   except:
      title = ''

   try:
      album = tag.getAlbum()
      if album == '':
         album = None
   except:
      album = None

   try:
      artist = tag.getArtist()
      if artist == '':
         artist = None
   except:
      artist = None

   try:
      genre = tag.getGenre().getName()
      if genre == '':
         genre = None
   except:
      genre = None

   try:
      (track, tracks) = tag.getTrackNum()
      if track is None:
         track = ''
   except:
      track = ''

   if title == '': print 'WARNING! %s has no title' % (mp3,)

   if title: title = title.replace('/', '%')
   if album: album = album.replace('/', '%')
   if artist: artist = artist.replace('/', '%')
   if genre: genre = genre.replace('/', '%')

   cursor.execute('insert into track (filename, title, album, trackno, artist, genre) values (?, ?, ?, ?, ?, ?)', (mp3, title, album, track, artist, genre))

def collect(path):
   dropTable('track')
   cursor.execute("""create table track (
      filename NOT NULL PRIMARY KEY,
      title COLLATE NOCASE,
      album COLLATE NOCASE,
      trackno,
      artist COLLATE NOCASE,
      genre COLLATE NOCASE)""")
   collectID3(path)

   cursor.execute("update track set title = ' ' where title is NULL or title = ''")
   db.commit()
   cursor.execute("ANALYZE")
   
def collectID3(path):
   try:
      if path.encode('ascii') != path:
         print 'PROBLEM: %s is not ascii' % (path,)
   except:
      print 'PROBLEM: %s is not ascii' % (path,)

   if os.path.isdir(path):
      for name in os.listdir(path):
         collectID3(os.path.normpath(os.path.join(path, name)))
   else:
      if os.path.isfile(path) and fnmatch.fnmatch(path, '*.mp3'):
         inspect(path)

   db.commit()

def reportOrphans():
   cursor.execute("select n.parent, n.name from nav n left join nav p on n.parent = p.parent || '/' || p.name where p.parent is NULL")
   for row in cursor:
      print '%s [%s]' % (row[0], row[1])

if options.music: collect(options.music)
if options.navigation: createMenu()
if not options.show is None: showMenu(options.show, 0, options.limit)
if options.orphans: reportOrphans()
if options.report: report()
