
# auto.py

"""
This module is an integeral part of the program
MMA - Musical Midi Accompaniment.

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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

Bob van der Poel <bob@mellowood.ca>

"""


import os
import sys
import pickle

import MMA.midi
import MMA.parse
import MMA.grooves
import MMA.swing

import MMA.paths

import gbl
from   MMA.common import *



grooveDB = []        # when filled in it becomes [['dir', dict-db], ..]
mmadir = ".mmaDB"    # constant, name of the lib database file
mkGrooveList = []    # a list of grooves defined in current file

def updateGrooveList(n):
    """ Called from parse when new grooves are defined in a -g. """

    global mkGrooveList

    mkGrooveList.append(n)


def libUpdate():
    """ Update the mma library database file(s) with -g or -G option.

        This is called from the main program after the initialization
        and other option parsing. No RETURN.
    """

    global grooveDB, mkGrooveList

    dupMessage = []
    processedFiles = []
    grooveCount = 0
    fileCount = 0
    noAccess = 0

    print "Creating MMA groove directory database(s). Standby..."

    """ MMA.paths.libDirs contains a complete list of all the directories in
        the libPath; we create a .mmaDB file for each one.
    """


    if not MMA.paths.libDirs:
        MMA.paths.expandLib()
    for lib in MMA.paths.libDirs:

        gdDate = None
        grooveDB = [[lib, {}]]

        # load up our database with this directory's DB file, skip if -G

        if gbl.makeGrvDefs == 1:
            g=loadDB(lib)
            if g:
                grooveDB=[[lib, g]]
                gdDate = os.path.getmtime(os.path.join(lib, mmadir))

        db = grooveDB[0][1]

        print "     Processing library directory '%s'." % lib

        """ Get a list of the files in this directory. If the list
            includes a file called 'MMAIGNORE' the entire directory
            is ignored. Otherwise, each file in the
            directory ending in 'mma' is parsed for groove defs.
        """

        dirfiles = os.listdir(lib)
        
        if "MMAIGNORE" in dirfiles:
            print "Skipping: %s" % p
            return

        for fn in sorted(dirfiles):  # sort is just to pretty up console display

            # Ignore hidden files, emacs auto-save and non-mma files

            if fn.startswith('.') or fn.startswith('#') or not fn.endswith(gbl.ext):
                continue

            f=os.path.join(lib, fn)      # Create full path name
            processedFiles.append(f)     # a list of ALL processed (complete paths)

            if gdDate and f in db and os.path.getmtime(f) < gdDate:
                print "       Existing: %s" % f
                grooveCount += len(db[f])
                continue

            if f in db:
                print "       Updating: %s" % f
            else:
                print "       Creating: %s" % f

            mkGrooveList = []
            MMA.grooves.grooveClear([])
            gbl.mtrks = {}
            MMA.swing.mode = 0
            for c in gbl.midiAssigns.keys():
                gbl.midiAssigns[c]=[]
            for a,v in enumerate(gbl.midiAvail):
                gbl.midiAvail[a]=0
            gbl.mtrks[0]=MMA.midi.Mtrk(0)

            gbl.tnames = {}

            MMA.parse.parseFile(f)    # read current file, grab grooves

            fileCount += 1            # just so we can report to user
            grooveCount += len(mkGrooveList)
            db[f]=mkGrooveList

        # Strip out defs of deleted (not found) files. Only on -g (update mode)

        if gbl.makeGrvDefs == 1:
            for f in db.keys():
                if f not in processedFiles:
                    print "       Deleting: %s" % f
                    del db[f]

        try:
            outpath = file(os.path.join(lib, mmadir), 'wb')
        except IOError:
            print "     Skipping '%s', no write access." % lib
            noAccess += 1
            continue

        outpath.write("### mmaDB ... AUTOGENERATED BINARY DATA. "
                      "DO NOT EDIT!!!\n")
        pickle.dump(db, outpath, pickle.HIGHEST_PROTOCOL )
        outpath.close()

        # check the database we just saved for duplicate entries.

        dprinted = None
        for f in db:
            for g in db[f]:
                for ff in db:
                    if f == ff:
                        continue
                    if g in db[ff]:
                        if not dprinted:
                            dupMessage.append("   Lib %s: %s & %s have dups." % \
                                                  (lib, f, ff))
                            dprinted=1
                    if dprinted:
                        break

    print
    print "Database update complete."
    print "     Files processed: %s" % fileCount
    print "     Total number of grooves: %s" % grooveCount
    print
    if noAccess:
        print "You probably need to be 'root' to properly update your libraries."

    if dupMessage:
        print "Warning: Duplicate groove definitions found."
        for a in dupMessage:
            print a

    sys.exit(0)


def loadDB(d):
    """ Read a database file into memory.

        We're assuming that not much goes wrong here...if we don't find
        the database we return a Null.
    """


    try:
        infile = os.path.join(d, mmadir)
        f=file(infile, "rb")
        f.readline()    # Read/discard comment line
        g = pickle.load(f)
        f.close()
    except IOError:
        g = {}


    # If we can't find the directory, advise user to recreate the DB
    # Note, not an error ... should it be? Note, the compile will probably
    # end fast anyway since mma won't be able to find the requested groove.

    dirs = [os.path.dirname(d) for d in g.keys()]

    for a in set(dirs):
        if not os.path.exists(a):
            warning("There appears to be an error in the database.\n"
                  "  Directory '%s' cannot be found.\n"
                  "  Please try to execute the command 'mma -G' as root." % a)

    return g


#################################################################


def findGroove(targ):
    """ Try to auto-load a groove from the library.

        The groove name (targ) can be a simple name (ie. swingsus) or
        an entended name which includes a file and directory (ie. swing:swingsus
        or stdlib/swing:swingsus).
    """

    global grooveDB
    ret = (None, targ.upper() )   # not found return

    # If no existing DB we load them from expanded libPath

    if not grooveDB:
        grooveDB=[]
        if not MMA.paths.libDirs:
            MMA.paths.expandLib()
        for d in MMA.paths.libDirs:
            g=loadDB(d)
            if g:
                grooveDB.append([d, g])

        if not grooveDB:   # BS value so we don't keep trying to load
            grooveDB = [['', {}]]


    # Check for extended notation. This is [DIR/DIR][FILE]:GROOVE

    # Split the filename from the groove name.
    # The complete spec for a groove name is dir/subdir/file:groove


    if ':' in targ:
        dirfile, targ = targ.split(':', 1)
        targ=targ.upper()

        # user is by-passing the default libs ... just pass the file/groovename back
        if dirfile.startswith('.') or os.path.isabs(dirfile):
            ret = ( dirfile, targ )


        else:
            # Does the library file exist? Check all the directories
            # set up from the expanded libPath. An example search
            # might be for "stdlib/swing:swingsus". The paths will
            # be:   1. stdlib  (no stdlib/swing.mma here)
            #      ..  lib   YES... found stdlib/swing.mma here

            fpath = MMA.paths.findLibFile(dirfile)
            if not fpath:
                error("Can't locate library file: %s, Groove: %s." % (dirfile, targ))

            if fpath.endswith(gbl.ext) and not dirfile.endswith(gbl.ext):
                dirfile += gbl.ext

            ret = ( dirfile, targ )

    else:
        # Just lookup a normal groove load from a file. We scan
        # the database and search for the groove name (targ).

        # Note the way we jump out the doubly nested loop. Yes, this is
        # pythonic and fast.

        targ=targ.upper()
        try:
            for dir, g in grooveDB:
                for filename, namelist in g.items():
                    if targ in namelist:
                        raise StopIteration

        except StopIteration:
            ret = ( os.path.join(dir, filename), targ.upper() )

    return ret
