#!/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 tempfile
from itertools import izip
import audiotools
import audiotools.ui
import audiotools.text as _
import termios


def limit(l, indexes):
    """given a list and set of indexes (starting from 1)
    returns a new list with only those items"""

    return [item for (index, item) in enumerate(l)
            if ((index + 1) in indexes)]


if (__name__ == '__main__'):
    import argparse

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

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

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

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

    parser.add_argument("-c", "--cdrom",
                        dest="cdrom",
                        default=audiotools.DEFAULT_CDROM)

    parser.add_argument("-A", "--audio-ts",
                        default=".",
                        dest="audio_ts",
                        metavar="DIR",
                        help=_.OPT_AUDIO_TS)

    parser.add_argument("--title",
                        default=1,
                        type=int,
                        dest="title",
                        help=_.OPT_DVDA_TITLE)

    conversion = parser.add_argument_group(_.OPT_CAT_EXTRACTION)

    conversion.add_argument("-t", "--type",
                            dest="type",
                            choices=sorted(audiotools.TYPE_MAP.keys() +
                                           ["help"]),
                            default=audiotools.DEFAULT_TYPE,
                            help=_.OPT_TYPE)

    conversion.add_argument("-q", "--quality",
                            dest="quality",
                            help=_.OPT_QUALITY)

    conversion.add_argument("-d", "--dir",
                            dest="dir",
                            default=".",
                            help=_.OPT_DIR)

    conversion.add_argument("--format",
                            default=audiotools.FILENAME_FORMAT,
                            dest="format",
                            help=_.OPT_FORMAT)

    lookup = parser.add_argument_group(_.OPT_CAT_DVDA_LOOKUP)

    lookup.add_argument("--musicbrainz-server",
                        dest="musicbrainz_server",
                        default=audiotools.MUSICBRAINZ_SERVER,
                        metavar="HOSTNAME")

    lookup.add_argument("--musicbrainz-port",
                        type=int,
                        dest="musicbrainz_port",
                        default=audiotools.MUSICBRAINZ_PORT,
                        metavar="PORT")

    lookup.add_argument("--no-musicbrainz",
                        action="store_false",
                        dest="use_musicbrainz",
                        default=audiotools.MUSICBRAINZ_SERVICE,
                        help=_.OPT_NO_MUSICBRAINZ)

    lookup.add_argument("--freedb-server",
                        dest="freedb_server",
                        default=audiotools.FREEDB_SERVER,
                        metavar="HOSTNAME")

    lookup.add_argument("--freedb-port",
                        type=int,
                        dest="freedb_port",
                        default=audiotools.FREEDB_PORT,
                        metavar="PORT")

    lookup.add_argument("--no-freedb",
                        action="store_false",
                        dest="use_freedb",
                        default=audiotools.FREEDB_SERVICE,
                        help=_.OPT_NO_FREEDB)

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

    metadata = parser.add_argument_group(_.OPT_CAT_METADATA)

    metadata.add_argument("--track-start",
                          type=int,
                          dest="track_start",
                          default=None,
                          help=_.OPT_TRACK_START)

    metadata.add_argument("--track-total",
                          type=int,
                          dest="track_total",
                          default=None,
                          help=_.OPT_TRACK_TOTAL)

    metadata.add_argument("--album-number",
                          dest="album_number",
                          type=int,
                          default=None,
                          help=_.OPT_ALBUM_NUMBER)

    metadata.add_argument("--album-total",
                          dest="album_total",
                          type=int,
                          default=None,
                          help=_.OPT_ALBUM_TOTAL)

    # if adding ReplayGain is a lossless process
    # (i.e. added as tags rather than modifying track data)
    # add_replay_gain should default to True
    # if not, add_replay_gain should default to False
    # which is which depends on the track type
    metadata.add_argument("--replay-gain",
                          action="store_true",
                          default=None,
                          dest="add_replay_gain",
                          help=_.OPT_REPLAY_GAIN)

    metadata.add_argument("--no-replay-gain",
                          action="store_false",
                          default=None,
                          dest="add_replay_gain",
                          help=_.OPT_NO_REPLAY_GAIN)

    parser.add_argument("tracks",
                        type=int,
                        metavar="TRACK",
                        nargs="*",
                        help=_.OPT_TRACK_INDEX)

    options = parser.parse_args()
    msg = audiotools.Messenger("dvda2track", 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)

    # get the AudioFile class we are converted to
    if (options.type == 'help'):
        audiotools.ui.show_available_formats(msg)
        sys.exit(0)
    else:
        AudioType = audiotools.TYPE_MAP[options.type]

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

    quality = options.quality
    base_directory = options.dir

    if (options.audio_ts is None):
        msg.error(_.ERR_NO_AUDIO_TS)
        sys.exit(1)

    # get main DVDAudio object
    try:
        dvda = audiotools.DVDAudio(options.audio_ts, options.cdrom)
    except audiotools.InvalidDVDA as err:
        msg.error(unicode(err))
        sys.exit(1)
    except OSError as err:
        msg.os_error(err)
        sys.exit(1)

    # get selected DVDATitle from DVDAudio object
    if (options.title < 1):
        msg.error(_.ERR_INVALID_TITLE_NUMBER)
        sys.exit(1)
    title = dvda[0][options.title - 1]

    # use DVDATtitle object to query metadata services for metadata choices
    try:
        metadata_choices = audiotools.metadata_lookup(
            musicbrainz_disc_id=title.musicbrainz_disc_id(),
            freedb_disc_id=title.freedb_disc_id(),
            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)
    except KeyboardInterrupt:
        msg.ansi_clearline()
        msg.error(_.ERR_CANCELLED)
        sys.exit(1)

    # update MetaData with command-line
    # track_start, track_total, album_number, album_total - if given
    for c in metadata_choices:
        for m in c:
            if (((options.track_start is not None) and
                 (m.track_number is not None))):
                # track_number and track_start both start at 1
                # so minus 1 to take care of the offset
                m.track_number += (options.track_start - 1)
            if (options.track_total is not None):
                m.track_total = options.track_total
            if (options.album_number is not None):
                m.album_number = options.album_number
            if (options.album_total is not None):
                m.album_total = options.album_total

    # determine which tracks to extract from the DVDATitle
    if (len(options.tracks) == 0):
        to_rip = {track.track: track for track in title}
    else:
        to_rip = {}
        for arg in options.tracks:
            try:
                to_rip[int(arg)] = title[int(arg)]
            except IndexError:
                continue
            except ValueError:
                continue

    # decide which metadata and output options use when extracting tracks
    if (options.interactive):
        # pick choice using interactive widget
        output_widget = audiotools.ui.OutputFiller(
            track_labels=[_.LAB_TRACK_X_OF_Y % (i, len(title))
                          for i in sorted(to_rip.keys())],
            metadata_choices=[limit(c, to_rip.keys())
                              for c in metadata_choices],
            input_filenames=[
                audiotools.Filename("track-%d-%2.2d.dvda.wav" %
                                    (options.title, i))
                for i in sorted(to_rip.keys())],
            output_directory=options.dir,
            format_string=options.format,
            output_class=AudioType,
            quality=options.quality,
            completion_label=_.LAB_DVDA2TRACK_APPLY)

        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)

        if (not output_widget.cancelled()):
            output_tracks = list(output_widget.output_tracks())
        else:
            sys.exit(0)
    else:
        # pick choice without using GUI
        try:
            output_tracks = list(
                audiotools.ui.process_output_options(
                    metadata_choices=[limit(c, to_rip.keys())
                                      for c in metadata_choices],
                    input_filenames=[
                        audiotools.Filename("track-%d-%2.2d.dvda.wav" %
                                            (options.title, i))
                        for i in sorted(to_rip.keys())],
                    output_directory=options.dir,
                    format_string=options.format,
                    output_class=AudioType,
                    quality=options.quality,
                    msg=msg,
                    use_default=options.use_default))
        except audiotools.UnsupportedTracknameField as err:
            err.error_msg(msg)
            sys.exit(1)
        except (audiotools.InvalidFilenameFormat,
                audiotools.OutputFileIsInput,
                audiotools.DuplicateOutputFile) as err:
            msg.error(unicode(err))
            sys.exit(1)

    # perform actual track extraction
    encoded = []
    pcmreader = title.to_pcm()
    for (i, pts_ticks) in enumerate([t.pts_length for t in title]):
        pcmreader.next_track(pts_ticks)
        pcm_frames = pcmreader.pcm_frames

        if ((i + 1) in to_rip):
            (output_class,
             output_filename,
             output_quality,
             output_metadata) = output_tracks.pop(0)

            try:
                audiotools.make_dirs(str(output_filename))
            except OSError as err:
                msg.os_error(err)
                sys.exit(1)

            progress = audiotools.SingleProgressDisplay(
                msg, unicode(output_filename))

            try:
                encoded.append(
                    output_class.from_pcm(
                        str(output_filename),
                        audiotools.PCMReaderProgress(pcmreader,
                                                     pcm_frames,
                                                     progress.update),
                        output_quality,
                        total_pcm_frames=pcm_frames))
            except audiotools.EncodingError as err:
                progress.clear_rows()
                msg.error(_.ERR_ENCODING_ERROR % (output_filename,))
                sys.exit(1)
            except KeyboardInterrupt:
                progress.clear_rows()
                msg.error(_.ERR_CANCELLED)
                try:
                    os.unlink(str(output_filename))
                except OSError:
                    pass
                sys.exit(1)

            encoded[-1].set_metadata(output_metadata)
            progress.clear_rows()

            msg.info(
                audiotools.output_progress(
                    _.LAB_ENCODE % {
                        "source": _.LAB_DVDA_TRACK %
                        {"title_number": options.title,
                         "track_number": i + 1},
                        "destination": output_filename},
                    len(to_rip) - len(output_tracks),
                    len(to_rip)))
        else:
            # bypass skipped tracks by decoding them to null
            audiotools.transfer_framelist_data(pcmreader, lambda f: f)

    # add ReplayGain to ripped tracks, if necessary
    if (output_class.supports_replay_gain() and
        (options.add_replay_gain if options.add_replay_gain is not None else
         audiotools.ADD_REPLAYGAIN)):
        rg_progress = audiotools.ReplayGainProgressDisplay(msg)
        rg_progress.initial_message()
        try:
            # all audio files must belong to the same album, by definition
            audiotools.add_replay_gain(encoded, rg_progress.update)
        except ValueError as err:
            rg_progress.clear_rows()
            msg.error(unicode(err))
            sys.exit(1)
        except KeyboardInterrupt:
            rg_progress.clear_rows()
            msg.error(_.ERR_CANCELLED)
            sys.exit(1)
        rg_progress.final_message()
