
# 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 gbl
from   MMA.common import *


grooveDB = []      # when filled in it becomes [['dir', dict-db], ..]
mmadir = ".mmaDB"    # constant, name of the lib database file
fileCount = 0
grooveCount = 0
gdDate = None
processedFiles = []
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 fileCount, gdDate, grooveDB, processedfiles
    dupMessage = []

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

    """ gbl.libPath points to one main directory tree. We create a separate
        .mmaDB file for each directory found in the main tree. IE, if we have 
        the directories stdlib and bvstuff we end up with stdlib/.mmaDB and
        bvstuff/.mmaDB.
    """

    for dir in os.listdir(gbl.libPath):
        libpath = os.path.join(gbl.libPath, dir)

        if not os.path.isdir(libpath):    # skip files, just process directories
            continue

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

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

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

        dolibupdate(libpath, '')  # update all files in this dir

        # Strip out defs of deleted (not found) files.

        db = grooveDB[0][1]

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

        try:
            outpath = file(os.path.join(libpath, mmadir), 'wb')
        except:
            error("Error creating lib-database file '%s'. " \
                      "Do you need to be root?" % libpath)

        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." % \
                                                  (libpath, 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 dupMessage:
        print "Warning: Duplicate groove definitions found."
        for a in dupMessage:
            print a

    sys.exit(0)


def dolibupdate(root, subdir):
    """ Recursive function to read groove files in a directory. """

    global fileCount, grooveCount, gdDate, grooveDB, processedFiles, mkGrooveList

    db = grooveDB[0][1]

    if subdir == '.':
        print "Skipping: '.'"
        return

    if subdir:
        print "     Processing library directory '%s'." % subdir


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

    p = os.path.join(root,subdir)
    dirfiles = os.listdir(p)

    if "MMAIGNORE" in dirfiles:
        print "Skipping: %s" % p
        return

    for fn in sorted(dirfiles):

        # Ignore hidden files and emacs auto-save and dead.

        if fn.startswith('.') or fn.startswith('#'):
            continue

        f=os.path.join(root, subdir, fn)      # Create full path name

        if os.path.isdir(f):
            dolibupdate(root, os.path.join(subdir,fn))    # recursive!

        elif f.endswith(gbl.ext):
            ename = os.path.join(subdir, fn)

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

            if ename 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[ename]=mkGrooveList

        else:
            if not f.endswith(mmadir):
                print "       Ignoring: %s" % f


def loadDB(dir):
    """ 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(gbl.libPath, dir, mmadir)
        f=file(infile, "rb")
        f.readline()    # Read/discard comment line
        g = pickle.load(f)
        f.close()
        return g
    except:
        pass

    return None


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


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

        The compilation of all the MMADIR files is stored in the
        list of dicts in grooveDir[].

        Check the each libpath directory for the MMADIR file. The
        names of the files and corresponding grooves are extracted.
        This is stored in a dictionary with the filename as the key
        and a list of grooves as the data.
    """

    global grooveDB


    """ If no existing DB we load them from each dir in libpath. 

    """

    if not grooveDB:
        grooveDB=[]
        for dir in gbl.autoLib:
            g=loadDB(dir)
            if g:
                grooveDB.append([dir, g])

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


    """ Search the dict for a match. 

        grooveDir[] structure ... [ [dirname, g], [] ]

        g ... is a dict. Key = filename, data = list of grooves

        RETURN: Lib-Filename if found
                None if not found
    """

    for dir, g in grooveDB:
        for filename, namelist in g.items():
            if targ in namelist:
                return os.path.join(dir,filename)

    return None



