#!/usr/bin/python

# Audio Tools, a module and set of tools for manipulating audio data
# Copyright (C) 2007-2014  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, unsupported_types, messenger):
    directories = [p for p in paths if os.path.isdir(p)]
    files = [p for p in paths if os.path.isfile(p)]

    for f in audiotools.open_files(files,
                                   messenger=messenger,
                                   warn_duplicates=True,
                                   opened_files=processed_filenames,
                                   unsupported_formats=unsupported_types):
        yield f

    for directory in directories:
        for (d, ds, fs) in os.walk(directory):
            for f in audiotools.open_files(
                    [os.path.join(d, f) for f in fs],
                    messenger=messenger,
                    warn_duplicates=True,
                    opened_files=processed_filenames,
                    unsupported_formats=unsupported_types):
                yield f


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

    temp_track_f = tempfile.NamedTemporaryFile(suffix="." + track.SUFFIX)
    try:
        # perform cleanup on a temporary track
        fixes = track.clean(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
        fixes = track.clean(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)

            # 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()

            # 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__'):
    import argparse

    parser = argparse.ArgumentParser(description=_.DESCRIPTION_TRACKLINT)

    parser.add_argument("--version",
                        action="version",
                        version="Python Audio Tools %s" % (audiotools.VERSION))

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

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

    parser.add_argument("--db",
                        dest="db",
                        help=_.OPT_TRACKLINT_DB)

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

    parser.add_argument("filenames",
                        metavar="FILENAME",
                        nargs="+",
                        help=_.OPT_INPUT_FILENAME)

    options = parser.parse_args()

    msg = audiotools.Messenger("tracklint", options)

    processed_filenames = set()
    unsupported_types = set()

    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(options.filenames,
                                        processed_filenames,
                                        unsupported_types,
                                        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 as 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(options.filenames,
                                    processed_filenames,
                                    unsupported_types,
                                    messenger=msg):
                try:
                    update_without_backup(track, msg)
                except IOError:
                    msg.error(_.ERR_ENCODING_ERROR %
                              (audiotools.Filename(track.filename),))
                    sys.exit(1)
                except ValueError as 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(options.filenames,
                                    processed_filenames,
                                    unsupported_types,
                                    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(options.filenames,
                                processed_filenames,
                                unsupported_types,
                                messenger=msg):
            display_messages(msg, track, track.clean())
