#!/usr/bin/python

#Audio Tools, a module and set of tools for manipulating audio data
#Copyright (C) 2007-2012  Brian Langenberger

#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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

import sys
import os
import os.path
import tempfile
import audiotools
import audiotools.delta
import audiotools.text as _

try:
    from hashlib import sha1
except ImportError:
    from sha import sha as sha1


def checksum(path):
    f = open(path, "rb")
    c = sha1("")
    try:
        audiotools.transfer_data(f.read, c.update)
        return c.hexdigest()
    finally:
        f.close()


def display_messages(messenger, track, messages):
    for message in messages:
        messenger.info(_.LAB_TRACKLINT_MESSAGE %
                       {"filename": audiotools.Filename(track.filename),
                        "message": message})


def audiofiles(paths, processed_filenames, messenger):
    directories = [p for p in paths if os.path.isdir(p)]
    files = [p for p in paths if os.path.isfile(p)]

    for p in paths:
        if (os.path.isfile(p)):
            filename = audiotools.Filename(p)
            try:
                if (filename not in processed_filenames):
                    processed_filenames.add(filename)
                    yield audiotools.open(str(filename))
            except audiotools.UnsupportedFile:
                pass
            except IOError, err:
                messenger.warning(_.ERR_OPEN_IOERROR % (filename,))
            except audiotools.InvalidFile, err:
                messenger.error(unicode(err))
        elif (os.path.isdir(p)):
            for (d, ds, fs) in os.walk(p):
                for t in audiofiles([os.path.join(d, f) for f in fs],
                                    processed_filenames,
                                    messenger):
                    yield t


def update_without_backup(track, messenger):
    fixes = []

    temp_track_f = tempfile.NamedTemporaryFile(suffix="." + track.SUFFIX)
    try:
        #perform cleanup on a temporary track
        track.clean(fixes, temp_track_f.name)

        #if changes are made, copy temporary track over original
        if (len(fixes) > 0):
            input_f = open(temp_track_f.name, 'rb')
            output_f = open(track.filename, 'wb')
            audiotools.transfer_data(input_f.read, output_f.write)
            output_f.close()
            input_f.close()

            #then display output messages
            display_messages(messenger, track, fixes)
    finally:
        temp_track_f.close()


def update_and_backup(track, undo_db, messenger):
    fixes = []

    temp_track_f = tempfile.NamedTemporaryFile(suffix="." + track.SUFFIX)
    try:
        #perform cleanup on a temporary track
        track.clean(fixes, temp_track_f.name)

        #if changes are made
        if (len(fixes) > 0):
            #store undo information between old and new track
            undo_db.add(track.filename, temp_track_f.name)

            #then copy temporary track over original
            input_f = open(temp_track_f.name, 'rb')
            output_f = open(track.filename, 'wb')
            audiotools.transfer_data(input_f.read, output_f.write)
            output_f.close()
            input_f.close()

            #then display output messages
            display_messages(messenger, track, fixes)
    finally:
        temp_track_f.close()


def undo_from_backup(track, undo_db, messenger):
    if (undo_db.undo(track.filename)):
        messenger.info(_.LAB_TRACKLINT_RESTORED %
                       (audiotools.Filename(track.filename),))


if (__name__ == '__main__'):
    parser = audiotools.OptionParser(
        usage=_.USAGE_TRACKLINT,
        version="Python Audio Tools %s" % (audiotools.VERSION))

    parser.add_option('--fix',
                      action='store_true',
                      default=False,
                      dest='fix',
                      help=_.OPT_TRACKLINT_FIX)

    parser.add_option('--db',
                      action='store',
                      type='string',
                      dest='db',
                      help=_.OPT_TRACKLINT_DB)

    parser.add_option('--undo',
                      action='store_true',
                      default=False,
                      dest='undo',
                      help=_.OPT_TRACKLINT_UNDO)

    parser.add_option('-V', '--verbose',
                      action='store',
                      dest='verbosity',
                      choices=audiotools.VERBOSITY_LEVELS,
                      default=audiotools.DEFAULT_VERBOSITY,
                      help=_.OPT_VERBOSE)

    (options, args) = parser.parse_args()
    processed_filenames = set([])
    msg = audiotools.Messenger("tracklint", options)

    if (options.undo and (options.db is None)):
        msg.error(_.ERR_NO_UNDO_DB)
        sys.exit(1)

    if (options.fix):
        if (options.db is not None):
            #if we're fixing tracks and have an undo DB,
            #save undo information to it during the fixing process
            try:
                undo_db = audiotools.delta.open_db(options.db)
            except:
                msg.error(_.ERR_OPEN_IOERROR %
                          (audiotools.Filename(options.db),))
                sys.exit(1)
            try:
                for track in audiofiles(args,
                                        processed_filenames,
                                        messenger=msg):
                    try:
                        update_and_backup(track, undo_db, msg)
                    except IOError:
                        msg.error(_.ERR_ENCODING_ERROR %
                                  (audiotools.Filename(track.filename),))
                        sys.exit(1)
                    except ValueError, err:
                        msg.error(unicode(err))
                        sys.exit(1)
            finally:
                undo_db.close()
        else:
            #if we're fixing tracks and have no undo DB,
            #simply overwrite the track and track metadata directly
            #if changes have been made
            for track in audiofiles(args, processed_filenames, messenger=msg):
                try:
                    update_without_backup(track, msg)
                except IOError:
                    msg.error(_.ERR_ENCODING_ERROR %
                              (audiotools.Filename(track.filename),))
                    sys.exit(1)
                except ValueError, err:
                    msg.error(unicode(err))
                    sys.exit(1)
    elif (options.undo):
        try:
            undo_db = audiotools.delta.open_db(options.db)
        except:
            msg.error(_.ERR_OPEN_IOERROR %
                      (audiotools.Filename(options.db),))
            sys.exit(1)
        try:
            for track in audiofiles(args, processed_filenames, messenger=msg):
                try:
                    undo_from_backup(track, undo_db, msg)
                except IOError:
                    msg.error(_.ERR_ENCODING_ERROR %
                              (audiotools.Filename(track.filename),))
                    sys.exit(1)
        finally:
            undo_db.close()
    else:  # a dry-run of the fixing procedure, with no changes made
        for track in audiofiles(args, processed_filenames, messenger=msg):
            fixes = []
            track.clean(fixes)
            display_messages(msg, track, fixes)
