#!/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 select
import cPickle
import audiotools
import audiotools.ui
import audiotools.text as _
import termios


MAX_CPUS = audiotools.MAX_JOBS


def convert(progress, source_audiofile, destination_filename,
            destination_class, compression,
            metadata):
    destination_audiofile = source_audiofile.convert(
        destination_filename,
        destination_class,
        compression,
        progress)

    if (metadata is not None):
        destination_audiofile.set_metadata(metadata)
    elif ((source_audiofile.track_number() is not None) or
          (source_audiofile.album_number() is not None)):
        destination_audiofile.set_metadata(
            audiotools.MetaData(
                track_number=source_audiofile.track_number(),
                album_number=source_audiofile.album_number()))
    else:
        #don't set any metadata
        pass

    existing_cuesheet = source_audiofile.get_cuesheet()
    if (existing_cuesheet is not None):
        destination_audiofile.set_cuesheet(existing_cuesheet)

    return destination_filename


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

    parser.add_option(
        '-I', '--interactive',
        action='store_true',
        default=False,
        dest='interactive',
        help=_.OPT_INTERACTIVE_OPTIONS)

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

    conversion = audiotools.OptionGroup(parser, _.OPT_CAT_CONVERSION)

    conversion.add_option(
        '-t', '--type',
        action='store',
        dest='type',
        choices=sorted(audiotools.TYPE_MAP.keys() + ['help']),
        help=_.OPT_TYPE)

    conversion.add_option(
        '-q', '--quality',
        action='store',
        type='string',
        dest='quality',
        help=_.OPT_QUALITY)

    conversion.add_option(
        '-d', '--dir',
        action='store',
        type='string',
        dest='dir',
        default='.',
        help=_.OPT_DIR)

    conversion.add_option(
        '--format',
        action='store',
        type='string',
        default=None,
        dest='format',
        help=_.OPT_FORMAT)

    conversion.add_option(
        '-o', '--output',
        action='store',
        dest='output',
        help=_.OPT_OUTPUT_TRACK2TRACK)

    conversion.add_option(
        '-j', '--joint',
        action='store',
        type='int',
        default=MAX_CPUS,
        dest='max_processes',
        help=_.OPT_JOINT)

    parser.add_option_group(conversion)

    lookup = audiotools.OptionGroup(parser, _.OPT_CAT_CD_LOOKUP)

    lookup.add_option(
        '-M', '--metadata-lookup', action='store_true',
        default=False, dest='metadata_lookup',
        help=_.OPT_METADATA_LOOKUP)

    lookup.add_option(
        '--musicbrainz-server', action='store',
        type='string', dest='musicbrainz_server',
        default=audiotools.MUSICBRAINZ_SERVER,
        metavar='HOSTNAME')
    lookup.add_option(
        '--musicbrainz-port', action='store',
        type='int', dest='musicbrainz_port',
        default=audiotools.MUSICBRAINZ_PORT,
        metavar='PORT')
    lookup.add_option(
        '--no-musicbrainz', action='store_false',
        dest='use_musicbrainz',
        default=audiotools.MUSICBRAINZ_SERVICE,
        help=_.OPT_NO_MUSICBRAINZ)

    lookup.add_option(
        '--freedb-server', action='store',
        type='string', dest='freedb_server',
        default=audiotools.FREEDB_SERVER,
        metavar='HOSTNAME')
    lookup.add_option(
        '--freedb-port', action='store',
        type='int', dest='freedb_port',
        default=audiotools.FREEDB_PORT,
        metavar='PORT')
    lookup.add_option(
        '--no-freedb', action='store_false',
        dest='use_freedb',
        default=audiotools.FREEDB_SERVICE,
        help=_.OPT_NO_FREEDB)

    lookup.add_option(
        '-D', '--default',
        dest='use_default', action='store_true', default=False,
        help=_.OPT_DEFAULT)

    parser.add_option_group(lookup)

    metadata = audiotools.OptionGroup(parser, _.OPT_CAT_METADATA)

    metadata.add_option(
        '--replay-gain',
        action='store_true',
        dest='add_replay_gain',
        help=_.OPT_REPLAY_GAIN)

    metadata.add_option(
        '--no-replay-gain',
        action='store_false',
        dest='add_replay_gain',
        help=_.OPT_NO_REPLAY_GAIN)

    parser.add_option_group(metadata)

    (options, args) = parser.parse_args()
    msg = audiotools.Messenger("track2track", options)

    #ensure interactive mode is available, if selected
    if (options.interactive and (not audiotools.ui.AVAILABLE)):
        audiotools.ui.not_available_message(msg)
        sys.exit(1)

    #if one specifies incompatible output options,
    #complain about it right away
    if (options.output is not None):
        if (options.dir != "."):
            msg.error(_.ERR_TRACK2TRACK_O_AND_D)
            msg.info(_.ERR_TRACK2TRACK_O_AND_D_SUGGESTION)
            sys.exit(1)

        if (options.format is not None):
            msg.warning(_.ERR_TRACK2TRACK_O_AND_FORMAT)

    #get the AudioFile class we are converted to
    if (options.type == 'help'):
        audiotools.ui.show_available_formats(msg)
        sys.exit(0)
    elif (options.output is None):
        if (options.type is not None):
            AudioType = audiotools.TYPE_MAP[options.type]
        else:
            AudioType = audiotools.TYPE_MAP[audiotools.DEFAULT_TYPE]
    else:
        if (options.type is not None):
            AudioType = audiotools.TYPE_MAP[options.type]
        else:
            try:
                AudioType = audiotools.filename_to_type(options.output)
            except audiotools.UnknownAudioType, exp:
                exp.error_msg(msg)
                sys.exit(1)

    #ensure the selected compression is compatible with that class
    if (options.quality == 'help'):
        audiotools.ui.show_available_qualities(msg, AudioType)
        sys.exit(0)
    elif (options.quality is None):
        options.quality = audiotools.__default_quality__(AudioType.NAME)
    elif (options.quality not in AudioType.COMPRESSION_MODES):
        msg.error(_.ERR_UNSUPPORTED_COMPRESSION_MODE %
                  {"quality": options.quality,
                   "type": AudioType.NAME})
        sys.exit(1)

    #grab the list of AudioFile objects we are converting from
    input_filenames = set([])
    try:
        audiofiles = audiotools.open_files(args,
                                           messenger=msg,
                                           no_duplicates=True,
                                           opened_files=input_filenames)
    except audiotools.DuplicateFile, err:
        msg.error(_.ERR_DUPLICATE_FILE % (err.filename,))
        sys.exit(1)

    if (len(audiofiles) < 1):
        msg.error(_.ERR_FILES_REQUIRED)
        sys.exit(1)

    if (options.max_processes < 1):
        msg.error(_.ERR_INVALID_JOINT)
        sys.exit(1)

    if ((options.output is not None) and (len(audiofiles) != 1)):
        msg.error(_.ERR_TRACK2TRACK_O_AND_MULTIPLE)
        sys.exit(1)

    if (options.output is None):
        #the default encoding method, without an output file

        previous_output_widget = None
        queue = audiotools.ExecProgressQueue(audiotools.ProgressDisplay(msg))

        #split tracks by album only if metadata lookups are required
        if (options.metadata_lookup):
            albums_iter = audiotools.iter_last(
                audiotools.group_tracks(audiofiles))
        else:
            albums_iter = iter([(True, audiofiles)])

        for (last_album, album_tracks) in albums_iter:
            format = (audiotools.FILENAME_FORMAT if
                      options.format is None else
                      options.format)

            track_metadatas = [f.get_metadata() for f in album_tracks]

            if (not options.metadata_lookup):
                #pull metadata from existing files, if any
                metadata_choices = [[f.get_metadata() for f in album_tracks]]
            else:
                #perform CD lookup for existing files
                metadata_choices = audiotools.track_metadata_lookup(
                    audiofiles=album_tracks,
                    musicbrainz_server=options.musicbrainz_server,
                    musicbrainz_port=options.musicbrainz_port,
                    freedb_server=options.freedb_server,
                    freedb_port=options.freedb_port,
                    use_musicbrainz=options.use_musicbrainz,
                    use_freedb=options.use_freedb)

                #and prepend metadata from existing files as an option, if any
                if (track_metadatas != [None] * len(track_metadatas)):
                    metadata_choices.insert(
                        0,
                        [(m if m is not None else audiotools.MetaData())
                         for m in track_metadatas])

            if (options.interactive):
                #pick options using interactive widget

                #if previous widget has been used,
                #pull default options from it
                if (previous_output_widget is None):
                    output_directory = options.dir
                    format_string = format
                    output_class = AudioType
                    quality = options.quality
                else:
                    output_directory = \
                        previous_output_widget.output_directory()
                    format_string = \
                        previous_output_widget.format_string()
                    output_class = \
                        previous_output_widget.output_class()
                    quality = \
                        previous_output_widget.quality()

                #execute output widget per album's worth of tracks
                output_widget = audiotools.ui.OutputFiller(
                    track_labels=[
                        unicode(audiotools.Filename(f.filename).basename())
                        for f in album_tracks],
                    metadata_choices=metadata_choices,
                    input_filenames=[audiotools.Filename(f.filename)
                                     for f in album_tracks],
                    output_directory=output_directory,
                    format_string=format_string,
                    output_class=output_class,
                    quality=quality,
                    completion_label=((_.LAB_TRACK2TRACK_APPLY if
                                       (len(album_tracks) != 1) else
                                       _.LAB_TRACK2TRACK_APPLY_1)
                                      if last_album
                                      else _.LAB_TRACK2TRACK_NEXT))

                loop = audiotools.ui.urwid.MainLoop(
                    output_widget,
                    audiotools.ui.style(),
                    unhandled_input=output_widget.handle_text,
                    pop_ups=True)
                try:
                    loop.run()
                    msg.ansi_clearscreen()
                except (termios.error, IOError):
                    msg.error(_.ERR_TERMIOS_ERROR)
                    msg.info(_.ERR_TERMIOS_SUGGESTION)
                    msg.info(audiotools.ui.xargs_suggestion(sys.argv))
                    sys.exit(1)

                previous_output_widget = output_widget

                #any cancellation quits the entire transcode
                if (not output_widget.cancelled()):
                    from itertools import izip

                    #merge input track metadata
                    #with new metadata returned from widget
                    output_tracks = []
                    for (current_metadata,
                         (output_class,
                          output_filename,
                          output_quality,
                          output_metadata)
                         ) in izip(iter(track_metadatas),
                                   output_widget.output_tracks()):
                        if (current_metadata is not None):
                            for attr in audiotools.MetaData.FIELDS:
                                original_value = getattr(current_metadata,
                                                         attr)
                                updated_value = getattr(output_metadata,
                                                        attr)
                                if (original_value != updated_value):
                                    setattr(current_metadata,
                                            attr,
                                            updated_value)
                            else:
                                output_tracks.append((output_class,
                                                      output_filename,
                                                      output_quality,
                                                      current_metadata))
                        else:
                            output_tracks.append((output_class,
                                                  output_filename,
                                                  output_quality,
                                                  output_metadata))
                else:
                    sys.exit(0)
            else:
                #pick options without using GUI
                try:
                    output_tracks = list(
                        audiotools.ui.process_output_options(
                            metadata_choices=metadata_choices,
                            input_filenames=[audiotools.Filename(f.filename)
                                             for f in album_tracks],
                            output_directory=options.dir,
                            format_string=format,
                            output_class=AudioType,
                            quality=options.quality,
                            msg=msg,
                            use_default=options.use_default))
                except audiotools.UnsupportedTracknameField, err:
                    err.error_msg(msg)
                    sys.exit(1)
                except (audiotools.InvalidFilenameFormat,
                        audiotools.OutputFileIsInput,
                        audiotools.DuplicateOutputFile), err:
                    msg.error(unicode(err))
                    sys.exit(1)

            #queue jobs to be executed
            for (audiofile, (output_class,
                             output_filename,
                             output_quality,
                             output_metadata)) in zip(album_tracks,
                                                      output_tracks):
                #try to create subdirectories in advance
                #so as to bail out as early as possible
                try:
                    audiotools.make_dirs(str(output_filename))
                except OSError:
                    msg.error(_.ERR_ENCODING_ERROR % (output_filename,))
                    sys.exit(1)

                queue.execute(
                    function=convert,
                    progress_text=unicode(output_filename),
                    completion_output=
                    (_.LAB_ENCODE %
                     {"source": audiotools.Filename(audiofile.filename),
                      "destination": output_filename}),
                    source_audiofile=audiofile,
                    destination_filename=str(output_filename),
                    destination_class=output_class,
                    compression=output_quality,
                    metadata=output_metadata)

        #perform actual track conversion
        try:
            queue.run(options.max_processes)
        except audiotools.EncodingError, err:
            msg.error(unicode(err))
            sys.exit(1)

        output_files = audiotools.open_files(queue.results.values())

        #add ReplayGain to converted files, if necessary
        if ((audiotools.ADD_REPLAYGAIN and
             (options.add_replay_gain if (options.add_replay_gain is not None)
              else output_class.lossless_replay_gain()) and
             output_class.can_add_replay_gain(output_files))):
            #separate encoded files by album_name and album_number
            for album in audiotools.group_tracks(output_files):
                #add ReplayGain to groups of files
                #belonging to the same album

                album_number = set([a.album_number() for a in album]).pop()

                #FIXME - should pull ReplayGain text from elsewhere
                queue.execute(
                    output_class.add_replay_gain,
                    (u"%s ReplayGain%s" %
                     ((u"Adding" if output_class.lossless_replay_gain() else
                       u"Applying"),
                      (u"" if album_number is None else
                       (u" to album %d" % (album_number))))),
                    (u"ReplayGain %s%s" %
                     ((u"added" if output_class.lossless_replay_gain() else
                       u"applied"),
                      (u"" if album_number is None else
                       (u" to album %d" % (album_number))))),
                    [a.filename for a in album])

            try:
                queue.run(options.max_processes)
            except ValueError, err:
                msg.error(unicode(err))
                sys.exit(1)
    else:
        #encoding only a single file
        audiofile = audiofiles[0]
        input_filename = audiotools.Filename(audiofile.filename)

        if (options.interactive):
            track_metadata = audiofile.get_metadata()

            output_widget = audiotools.ui.SingleOutputFiller(
                track_label=unicode(input_filename),
                metadata_choices=[track_metadata if track_metadata is not None
                                  else audiotools.MetaData()],
                input_filenames=[input_filename],
                output_file=options.output,
                output_class=AudioType,
                quality=options.quality,
                completion_label=_.LAB_TRACK2TRACK_APPLY_1)
            loop = audiotools.ui.urwid.MainLoop(
                output_widget,
                audiotools.ui.style(),
                unhandled_input=output_widget.handle_text,
                pop_ups=True)
            loop.run()
            msg.ansi_clearscreen()

            if (not output_widget.cancelled()):
                (destination_class,
                 output_filename,
                 compression,
                 track_metadata) = output_widget.output_track()
            else:
                sys.exit(0)
        else:
            output_filename = audiotools.Filename(options.output)
            destination_class = AudioType
            compression = options.quality
            track_metadata = audiofile.get_metadata()

            if (input_filename == output_filename):
                msg.error(_.ERR_OUTPUT_IS_INPUT %
                          (output_filename,))
                sys.exit(1)

        progress = audiotools.SingleProgressDisplay(
            messenger=msg,
            progress_text=unicode(output_filename))
        try:
            convert(progress=progress.update,
                    source_audiofile=audiofile,
                    destination_filename=str(output_filename),
                    destination_class=destination_class,
                    compression=compression,
                    metadata=track_metadata)
            progress.clear()

            msg.output(_.LAB_ENCODE % {"source": input_filename,
                                       "destination": output_filename})
        except audiotools.EncodingError, err:
            progress.clear()
            msg.error(unicode(err))
            sys.exit(1)
