#  Utils (c) 2002, 2004, 2007, 2008  David Turner <david@freetype.org>
#

import string, sys, os, glob

# current output directory
#
output_dir = None

# This function is used to sort the index.  It is a simple lexicographical
# sort, except that it places capital letters before lowercase ones.
#
def  index_sort( s1, s2 ):
    if not s1:
        return -1

    if not s2:
        return 1

    l1 = len( s1 )
    l2 = len( s2 )
    m1 = string.lower( s1 )
    m2 = string.lower( s2 )

    for i in range( l1 ):
        if i >= l2 or m1[i] > m2[i]:
            return 1

        if m1[i] < m2[i]:
            return -1

        if s1[i] < s2[i]:
            return -1

        if s1[i] > s2[i]:
            return 1

    if l2 > l1:
        return -1

    return 0

# Sort input_list, placing the elements of order_list in front.
#
def  sort_order_list( input_list, order_list ):
    new_list = order_list[:]
    for id in input_list:
        if not id in order_list:
            new_list.append( id )
    return new_list

# Open the standard output to a given project documentation file.  Use
# "output_dir" to determine the filename location if necessary and save the
# old stdout in a tuple that is returned by this function.
#
def  open_output( filename ):
    global output_dir

    if output_dir and output_dir != "":
        filename = output_dir + os.sep + filename

    old_stdout = sys.stdout
    new_file   = open( filename, "w" )
    sys.stdout = new_file

    return ( new_file, old_stdout )

# Close the output that was returned by "close_output".
#
def  close_output( output ):
    output[0].close()
    sys.stdout = output[1]

# Check output directory.
#
def  check_output():
    global output_dir
    if output_dir:
        if output_dir != "":
            if not os.path.isdir( output_dir ):
                sys.stderr.write( "argument" + " '" + output_dir + "' " + \
                                  "is not a valid directory" )
                sys.exit( 2 )
        else:
            output_dir = None

def  file_exists( pathname ):
    """checks that a given file exists"""
    result = 1
    try:
        file = open( pathname, "r" )
        file.close()
    except:
        result = None
        sys.stderr.write( pathname + " couldn't be accessed\n" )

    return result

def  make_file_list( args = None ):
    """builds a list of input files from command-line arguments"""
    file_list = []
    # sys.stderr.write( repr( sys.argv[1 :] ) + '\n' )

    if not args:
        args = sys.argv[1 :]

    for pathname in args:
        if string.find( pathname, '*' ) >= 0:
            newpath = glob.glob( pathname )
            newpath.sort()  # sort files -- this is important because
                            # of the order of files
        else:
            newpath = [pathname]

        file_list.extend( newpath )

    if len( file_list ) == 0:
        file_list = None
    else:
        # now filter the file list to remove non-existing ones
        file_list = filter( file_exists, file_list )

    return file_list

# eof


"""
Contains a parser for HostedZone tags. These are used in several kinds of
XML responses (ListHostedZones and CreateHostedZone, for example).
"""

from route53.hosted_zone import HostedZone

# This dict maps tag names in the API response to a kwarg key used to
# instantiate HostedZone instances.
HOSTED_ZONE_TAG_TO_KWARG_MAP = {
    'Id': 'id',
    'Name': 'name',
    'CallerReference': 'caller_reference',
    'ResourceRecordSetCount': 'resource_record_set_count',
}

def parse_hosted_zone(e_zone, connection):
    """
    This a common parser that allows the passing of any valid HostedZone
    tag. It will spit out the appropriate HostedZone object for the tag.

    :param lxml.etree._Element e_zone: The root node of the etree parsed
        response from the API.
    :param Route53Connection connection: The connection instance used to
        query the API.
    :rtype: HostedZone
    :returns: An instantiated HostedZone object.
    """

    # This dict will be used to instantiate a HostedZone instance to yield.
    kwargs = {}
    # Within HostedZone tags are a number of sub-tags that include info
    # about the instance.
    for e_field in e_zone:
        # Cheesy way to strip off the namespace.
        tag_name = e_field.tag.split('}')[1]
        field_text = e_field.text

        if tag_name == 'Config':
            # Config has the Comment tag beneath it, needing
            # special handling.
            e_comment = e_field.find('./{*}Comment')
            kwargs['comment'] = e_comment.text if e_comment is not None else None
            continue
        elif tag_name == 'Id':
            # This comes back with a path prepended. Yank that sillyness.
            field_text = field_text.strip('/hostedzone/')

        # Map the XML tag name to a kwarg name.
        kw_name = HOSTED_ZONE_TAG_TO_KWARG_MAP[tag_name]
        # This will be the key/val pair used to instantiate the
        # HostedZone instance.
        kwargs[kw_name] = field_text

    return HostedZone(connection, **kwargs)

def parse_delegation_set(zone, e_delegation_set):
    """
    Parses a DelegationSet tag. These often accompany HostedZone tags in
    responses like CreateHostedZone and GetHostedZone.

    :param HostedZone zone: An existing HostedZone instance to populate.
    :param lxml.etree._Element e_delegation_set: A DelegationSet element.
    """

    e_nameservers = e_delegation_set.find('./{*}NameServers')

    nameservers = []
    for e_nameserver in e_nameservers:
        nameservers.append(e_nameserver.text)

    zone._nameservers = nameservers


# -*- coding: utf-8 -*-

# Copyright 2005 Joe Wreschnig
#           2014 Ben Ockmore
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.

"""Read and write FLAC Vorbis comments and stream information.

Read more about FLAC at http://flac.sourceforge.net.

FLAC supports arbitrary metadata blocks. The two most interesting ones
are the FLAC stream information block, and the Vorbis comment block;
these are also the only ones Mutagen can currently read.

This module does not handle Ogg FLAC files.

Based off documentation available at
http://flac.sourceforge.net/format.html
"""

__all__ = ["FLAC", "Open", "delete"]

import struct
from mutagen._vorbis import VCommentDict
from mutagen import FileType
import mutagen

from ._compat import cBytesIO, endswith, chr_
from mutagen._util import insert_bytes
from mutagen.id3 import BitPaddedInt
from functools import reduce

class error(IOError):
    pass

class FLACNoHeaderError(error):
    pass

class FLACVorbisError(ValueError, error):
    pass

def to_int_be(data):
    """Convert an arbitrarily-long string to a long using big-endian
    byte order."""
    return reduce(lambda a, b: (a << 8) + b, bytearray(data), 0)

class StrictFileObject(object):
    """Wraps a file-like object and raises an exception if the requested
    amount of data to read isn't returned."""

    def __init__(self, fileobj):
        self._fileobj = fileobj
        for m in ["close", "tell", "seek", "write", "name"]:
            if hasattr(fileobj, m):
                setattr(self, m, getattr(fileobj, m))

    def read(self, size=-1):
        data = self._fileobj.read(size)
        if size >= 0 and len(data) != size:
            raise error("file said %d bytes, read %d bytes" % (
                        size, len(data)))
        return data

    def tryread(self, *args):
        return self._fileobj.read(*args)

class MetadataBlock(object):
    """A generic block of FLAC metadata.

    This class is extended by specific used as an ancestor for more specific
    blocks, and also as a container for data blobs of unknown blocks.

    Attributes:

    * data -- raw binary data for this block
    """

    _distrust_size = False

    def __init__(self, data):
        """Parse the given data string or file-like as a metadata block.
        The metadata header should not be included."""
        if data is not None:
            if not isinstance(data, StrictFileObject):
                if isinstance(data, bytes):
                    data = cBytesIO(data)
                elif not hasattr(data, 'read'):
                    raise TypeError(
                        "StreamInfo requires string data or a file-like")
                data = StrictFileObject(data)
            self.load(data)

    def load(self, data):
        self.data = data.read()

    def write(self):
        return self.data

    @staticmethod
    def writeblocks(blocks):
        """Render metadata block as a byte string."""
        data = []
        codes = [[block.code, block.write()] for block in blocks]
        codes[-1][0] |= 128
        for code, datum in codes:
            byte = chr_(code)
            if len(datum) > 2**24:
                raise error("block is too long to write")
            length = struct.pack(">I", len(datum))[-3:]
            data.append(byte + length + datum)
        return b"".join(data)

    @staticmethod
    def group_padding(blocks):
        """Consolidate FLAC padding metadata blocks.

        The overall size of the rendered blocks does not change, so
        this adds several bytes of padding for each merged block.
        """

        paddings = [b for b in blocks if isinstance(b, Padding)]
        for p in paddings:
            blocks.remove(p)
        # total padding size is the sum of padding sizes plus 4 bytes
        # per removed header.
        size = sum(padding.length for padding in paddings)
        padding = Padding()
        padding.length = size + 4 * (len(paddings) - 1)
        blocks.append(padding)

class StreamInfo(MetadataBlock, mutagen.StreamInfo):
    """FLAC stream information.

    This contains information about the audio data in the FLAC file.
    Unlike most stream information objects in Mutagen, changes to this
    one will rewritten to the file when it is saved. Unless you are
    actually changing the audio stream itself, don't change any
    attributes of this block.

    Attributes:

    * min_blocksize -- minimum audio block size
    * max_blocksize -- maximum audio block size
    * sample_rate -- audio sample rate in Hz
    * channels -- audio channels (1 for mono, 2 for stereo)
    * bits_per_sample -- bits per sample
    * total_samples -- total samples in file
    * length -- audio length in seconds
    """

    code = 0

    def __eq__(self, other):
        try:
            return (self.min_blocksize == other.min_blocksize and
                    self.max_blocksize == other.max_blocksize and
                    self.sample_rate == other.sample_rate and
                    self.channels == other.channels and
                    self.bits_per_sample == other.bits_per_sample and
                    self.total_samples == other.total_samples)
        except:
            return False

    __hash__ = MetadataBlock.__hash__

    def load(self, data):
        self.min_blocksize = int(to_int_be(data.read(2)))
        self.max_blocksize = int(to_int_be(data.read(2)))
        self.min_framesize = int(to_int_be(data.read(3)))
        self.max_framesize = int(to_int_be(data.read(3)))
        # first 16 bits of sample rate
        sample_first = to_int_be(data.read(2))
        # last 4 bits of sample rate, 3 of channels, first 1 of bits/sample
        sample_channels_bps = to_int_be(data.read(1))
        # last 4 of bits/sample, 36 of total samples
        bps_total = to_int_be(data.read(5))

        sample_tail = sample_channels_bps >> 4
        self.sample_rate = int((sample_first << 4) + sample_tail)
        if not self.sample_rate:
            raise error("A sample rate value of 0 is invalid")
        self.channels = int(((sample_channels_bps >> 1) & 7) + 1)
        bps_tail = bps_total >> 36
        bps_head = (sample_channels_bps & 1) << 4
        self.bits_per_sample = int(bps_head + bps_tail + 1)
        self.total_samples = bps_total & 0xFFFFFFFFF
        self.length = self.total_samples / float(self.sample_rate)

        self.md5_signature = to_int_be(data.read(16))

    def write(self):
        f = cBytesIO()
        f.write(struct.pack(">I", self.min_blocksize)[-2:])
        f.write(struct.pack(">I", self.max_blocksize)[-2:])
        f.write(struct.pack(">I", self.min_framesize)[-3:])
        f.write(struct.pack(">I", self.max_framesize)[-3:])

        # first 16 bits of sample rate
        f.write(struct.pack(">I", self.sample_rate >> 4)[-2:])
        # 4 bits sample, 3 channel, 1 bps
        byte = (self.sample_rate & 0xF) << 4
        byte += ((self.channels - 1) & 7) << 1
        byte += ((self.bits_per_sample - 1) >> 4) & 1
        f.write(chr_(byte))
        # 4 bits of bps, 4 of sample count
        byte = ((self.bits_per_sample - 1) & 0xF) << 4
        byte += (self.total_samples >> 32) & 0xF
        f.write(chr_(byte))
        # last 32 of sample count
        f.write(struct.pack(">I", self.total_samples & 0xFFFFFFFF))
        # MD5 signature
        sig = self.md5_signature
        f.write(struct.pack(
            ">4I", (sig >> 96) & 0xFFFFFFFF, (sig >> 64) & 0xFFFFFFFF,
            (sig >> 32) & 0xFFFFFFFF, sig & 0xFFFFFFFF))
        return f.getvalue()

    def pprint(self):
        return "FLAC, %.2f seconds, %d Hz" % (self.length, self.sample_rate)

class SeekPoint(tuple):
    """A single seek point in a FLAC file.

    Placeholder seek points have first_sample of 0xFFFFFFFFFFFFFFFFL,
    and byte_offset and num_samples undefined. Seek points must be
    sorted in ascending order by first_sample number. Seek points must
    be unique by first_sample number, except for placeholder
    points. Placeholder points must occur last in the table and there
    may be any number of them.

    Attributes:

    * first_sample -- sample number of first sample in the target frame
    * byte_offset -- offset from first frame to target frame
    * num_samples -- number of samples in target frame
    """

    def __new__(cls, first_sample, byte_offset, num_samples):
        return super(cls, SeekPoint).__new__(
            cls, (first_sample, byte_offset, num_samples))

    first_sample = property(lambda self: self[0])
    byte_offset = property(lambda self: self[1])
    num_samples = property(lambda self: self[2])

class SeekTable(MetadataBlock):
    """Read and write FLAC seek tables.

    Attributes:

    * seekpoints -- list of SeekPoint objects
    """

    __SEEKPOINT_FORMAT = '>QQH'
    __SEEKPOINT_SIZE = struct.calcsize(__SEEKPOINT_FORMAT)

    code = 3

    def __init__(self, data):
        self.seekpoints = []
        super(SeekTable, self).__init__(data)

    def __eq__(self, other):
        try:
            return (self.seekpoints == other.seekpoints)
        except (AttributeError, TypeError):
            return False

    __hash__ = MetadataBlock.__hash__

    def load(self, data):
        self.seekpoints = []
        sp = data.tryread(self.__SEEKPOINT_SIZE)
        while len(sp) == self.__SEEKPOINT_SIZE:
            self.seekpoints.append(SeekPoint(
                *struct.unpack(self.__SEEKPOINT_FORMAT, sp)))
            sp = data.tryread(self.__SEEKPOINT_SIZE)

    def write(self):
        f = cBytesIO()
        for seekpoint in self.seekpoints:
            packed = struct.pack(
                self.__SEEKPOINT_FORMAT,
                seekpoint.first_sample, seekpoint.byte_offset,
                seekpoint.num_samples)
            f.write(packed)
        return f.getvalue()

    def __repr__(self):
        return "<%s seekpoints=%r>" % (type(self).__name__, self.seekpoints)

class VCFLACDict(VCommentDict):
    """Read and write FLAC Vorbis comments.

    FLACs don't use the framing bit at the end of the comment block.
    So this extends VCommentDict to not use the framing bit.
    """

    code = 4
    _distrust_size = True

    def load(self, data, errors='replace', framing=False):
        super(VCFLACDict, self).load(data, errors=errors, framing=framing)

    def write(self, framing=False):
        return super(VCFLACDict, self).write(framing=framing)

class CueSheetTrackIndex(tuple):
    """Index for a track in a cuesheet.

    For CD-DA, an index_number of 0 corresponds to the track
    pre-gap. The first index in a track must have a number of 0 or 1,
    and subsequently, index_numbers must increase by 1. Index_numbers
    must be unique within a track. And index_offset must be evenly
    divisible by 588 samples.

    Attributes:

    * index_number -- index point number
    * index_offset -- offset in samples from track start
    """

    def __new__(cls, index_number, index_offset):
        return super(cls, CueSheetTrackIndex).__new__(
            cls, (index_number, index_offset))

    index_number = property(lambda self: self[0])
    index_offset = property(lambda self: self[1])

class CueSheetTrack(object):
    """A track in a cuesheet.

    For CD-DA, track_numbers must be 1-99, or 170 for the
    lead-out. Track_numbers must be unique within a cue sheet. There
    must be atleast one index in every track except the lead-out track
    which must have none.

    Attributes:

    * track_number -- track number
    * start_offset -- track offset in samples from start of FLAC stream
    * isrc -- ISRC code
    * type -- 0 for audio, 1 for digital data
    * pre_emphasis -- true if the track is recorded with pre-emphasis
    * indexes -- list of CueSheetTrackIndex objects
    """

    def __init__(self, track_number, start_offset, isrc='', type_=0,
                 pre_emphasis=False):
        self.track_number = track_number
        self.start_offset = start_offset
        self.isrc = isrc
        self.type = type_
        self.pre_emphasis = pre_emphasis
        self.indexes = []

    def __eq__(self, other):
        try:
            return (self.track_number == other.track_number and
                    self.start_offset == other.start_offset and
                    self.isrc == other.isrc and
                    self.type == other.type and
                    self.pre_emphasis == other.pre_emphasis and
                    self.indexes == other.indexes)
        except (AttributeError, TypeError):
            return False

    __hash__ = object.__hash__

    def __repr__(self):
        return ("<%s number=%r, offset=%d, isrc=%r, type=%r, "
                "pre_emphasis=%r, indexes=%r)>") % (
                    type(self).__name__, self.track_number, self.start_offset,
                    self.isrc, self.type, self.pre_emphasis, self.indexes)

class CueSheet(MetadataBlock):
    """Read and write FLAC embedded cue sheets.

    Number of tracks should be from 1 to 100. There should always be
    exactly one lead-out track and that track must be the last track
    in the cue sheet.

    Attributes:

    * media_catalog_number -- media catalog number in ASCII
    * lead_in_samples -- number of lead-in samples
    * compact_disc -- true if the cuesheet corresponds to a compact disc
    * tracks -- list of CueSheetTrack objects
    * lead_out -- lead-out as CueSheetTrack or None if lead-out was not found
    """

    __CUESHEET_FORMAT = '>128sQB258xB'
    __CUESHEET_SIZE = struct.calcsize(__CUESHEET_FORMAT)
    __CUESHEET_TRACK_FORMAT = '>QB12sB13xB'
    __CUESHEET_TRACK_SIZE = struct.calcsize(__CUESHEET_TRACK_FORMAT)
    __CUESHEET_TRACKINDEX_FORMAT = '>QB3x'
    __CUESHEET_TRACKINDEX_SIZE = struct.calcsize(__CUESHEET_TRACKINDEX_FORMAT)

    code = 5

    media_catalog_number = b''
    lead_in_samples = 88200
    compact_disc = True

    def __init__(self, data):
        self.tracks = []
        super(CueSheet, self).__init__(data)

    def __eq__(self, other):
        try:
            return (self.media_catalog_number == other.media_catalog_number and
                    self.lead_in_samples == other.lead_in_samples and
                    self.compact_disc == other.compact_disc and
                    self.tracks == other.tracks)
        except (AttributeError, TypeError):
            return False

    __hash__ = MetadataBlock.__hash__

    def load(self, data):
        header = data.read(self.__CUESHEET_SIZE)
        media_catalog_number, lead_in_samples, flags, num_tracks = \
            struct.unpack(self.__CUESHEET_FORMAT, header)
        self.media_catalog_number = media_catalog_number.rstrip(b'\0')
        self.lead_in_samples = lead_in_samples
        self.compact_disc = bool(flags & 0x80)
        self.tracks = []
        for i in range(num_tracks):
            track = data.read(self.__CUESHEET_TRACK_SIZE)
            start_offset, track_number, isrc_padded, flags, num_indexes = \
                struct.unpack(self.__CUESHEET_TRACK_FORMAT, track)
            isrc = isrc_padded.rstrip(b'\0')
            type_ = (flags & 0x80) >> 7
            pre_emphasis = bool(flags & 0x40)
            val = CueSheetTrack(
                track_number, start_offset, isrc, type_, pre_emphasis)
            for j in range(num_indexes):
                index = data.read(self.__CUESHEET_TRACKINDEX_SIZE)
                index_offset, index_number = struct.unpack(
                    self.__CUESHEET_TRACKINDEX_FORMAT, index)
                val.indexes.append(
                    CueSheetTrackIndex(index_number, index_offset))
            self.tracks.append(val)

    def write(self):
        f = cBytesIO()
        flags = 0
        if self.compact_disc:
            flags |= 0x80
        packed = struct.pack(
            self.__CUESHEET_FORMAT, self.media_catalog_number,
            self.lead_in_samples, flags, len(self.tracks))
        f.write(packed)
        for track in self.tracks:
            track_flags = 0
            track_flags |= (track.type & 1) << 7
            if track.pre_emphasis:
                track_flags |= 0x40
            track_packed = struct.pack(
                self.__CUESHEET_TRACK_FORMAT, track.start_offset,
                track.track_number, track.isrc, track_flags,
                len(track.indexes))
            f.write(track_packed)
            for index in track.indexes:
                index_packed = struct.pack(
                    self.__CUESHEET_TRACKINDEX_FORMAT,
                    index.index_offset, index.index_number)
                f.write(index_packed)
        return f.getvalue()

    def __repr__(self):
        return ("<%s media_catalog_number=%r, lead_in=%r, compact_disc=%r, "
                "tracks=%r>") % (
                    type(self).__name__, self.media_catalog_number,
                    self.lead_in_samples, self.compact_disc, self.tracks)

class Picture(MetadataBlock):
    """Read and write FLAC embed pictures.

    Attributes:

    * type -- picture type (same as types for ID3 APIC frames)
    * mime -- MIME type of the picture
    * desc -- picture's description
    * width -- width in pixels
    * height -- height in pixels
    * depth -- color depth in bits-per-pixel
    * colors -- number of colors for indexed palettes (like GIF),
      0 for non-indexed
    * data -- picture data
    """

    code = 6
    _distrust_size = True

    def __init__(self, data=None):
        self.type = 0
        self.mime = u''
        self.desc = u''
        self.width = 0
        self.height = 0
        self.depth = 0
        self.colors = 0
        self.data = b''
        super(Picture, self).__init__(data)

    def __eq__(self, other):
        try:
            return (self.type == other.type and
                    self.mime == other.mime and
                    self.desc == other.desc and
                    self.width == other.width and
                    self.height == other.height and
                    self.depth == other.depth and
                    self.colors == other.colors and
                    self.data == other.data)
        except (AttributeError, TypeError):
            return False

    __hash__ = MetadataBlock.__hash__

    def load(self, data):
        self.type, length = struct.unpack('>2I', data.read(8))
        self.mime = data.read(length).decode('UTF-8', 'replace')
        length, = struct.unpack('>I', data.read(4))
        self.desc = data.read(length).decode('UTF-8', 'replace')
        (self.width, self.height, self.depth,
         self.colors, length) = struct.unpack('>5I', data.read(20))
        self.data = data.read(length)

    def write(self):
        f = cBytesIO()
        mime = self.mime.encode('UTF-8')
        f.write(struct.pack('>2I', self.type, len(mime)))
        f.write(mime)
        desc = self.desc.encode('UTF-8')
        f.write(struct.pack('>I', len(desc)))
        f.write(desc)
        f.write(struct.pack('>5I', self.width, self.height, self.depth,
                            self.colors, len(self.data)))
        f.write(self.data)
        return f.getvalue()

    def __repr__(self):
        return "<%s '%s' (%d bytes)>" % (type(self).__name__, self.mime,
                                         len(self.data))

class Padding(MetadataBlock):
    """Empty padding space for metadata blocks.

    To avoid rewriting the entire FLAC file when editing comments,
    metadata is often padded. Padding should occur at the end, and no
    more than one padding block should be in any FLAC file. Mutagen
    handles this with MetadataBlock.group_padding.
    """

    code = 1

    def __init__(self, data=b""):
        super(Padding, self).__init__(data)

    def load(self, data):
        self.length = len(data.read())

    def write(self):
        try:
            return b"\x00" * self.length
        # On some 64 bit platforms this won't generate a MemoryError
        # or OverflowError since you might have enough RAM, but it
        # still generates a ValueError. On other 64 bit platforms,
        # this will still succeed for extremely large values.
        # Those should never happen in the real world, and if they
        # do, writeblocks will catch it.
        except (OverflowError, ValueError, MemoryError):
            raise error("cannot write %d bytes" % self.length)

    def __eq__(self, other):
        return isinstance(other, Padding) and self.length == other.length

    __hash__ = MetadataBlock.__hash__

    def __repr__(self):
        return "<%s (%d bytes)>" % (type(self).__name__, self.length)

class FLAC(mutagen.FileType):
    """A FLAC audio file.

    Attributes:

    * info -- stream information (length, bitrate, sample rate)
    * tags -- metadata tags, if any
    * cuesheet -- CueSheet object, if any
    * seektable -- SeekTable object, if any
    * pictures -- list of embedded pictures
    """

    _mimes = ["audio/x-flac", "application/x-flac"]

    METADATA_BLOCKS = [StreamInfo, Padding, None, SeekTable, VCFLACDict,
                       CueSheet, Picture]
    """Known metadata block types, indexed by ID."""

    @staticmethod
    def score(filename, fileobj, header_data):
        if isinstance(filename, bytes):
            filename = filename.decode('utf-8')
        filename = filename.lower()

        return (header_data.startswith(b"fLaC") +
                endswith(filename,".flac") * 3)

    def __read_metadata_block(self, fileobj):
        byte = ord(fileobj.read(1))
        size = to_int_be(fileobj.read(3))
        code = byte & 0x7F
        last_block = bool(byte & 0x80)

        try:
            block_type = self.METADATA_BLOCKS[code] or MetadataBlock
        except IndexError:
            block_type = MetadataBlock

        if block_type._distrust_size:
            # Some jackass is writing broken Metadata block length
            # for Vorbis comment blocks, and the FLAC reference
            # implementaton can parse them (mostly by accident),
            # so we have to too.  Instead of parsing the size
            # given, parse an actual Vorbis comment, leaving
            # fileobj in the right position.
            # http://code.google.com/p/mutagen/issues/detail?id=52
            # ..same for the Picture block:
            # http://code.google.com/p/mutagen/issues/detail?id=106
            block = block_type(fileobj)
        else:
            data = fileobj.read(size)
            block = block_type(data)
        block.code = code

        if block.code == VCFLACDict.code:
            if self.tags is None:
                self.tags = block
            else:
                raise FLACVorbisError("> 1 Vorbis comment block found")
        elif block.code == CueSheet.code:
            if self.cuesheet is None:
                self.cuesheet = block
            else:
                raise error("> 1 CueSheet block found")
        elif block.code == SeekTable.code:
            if self.seektable is None:
                self.seektable = block
            else:
                raise error("> 1 SeekTable block found")
        self.metadata_blocks.append(block)
        return not last_block

    def add_tags(self):
        """Add a Vorbis comment block to the file."""
        if self.tags is None:
            self.tags = VCFLACDict()
            self.metadata_blocks.append(self.tags)
        else:
            raise FLACVorbisError("a Vorbis comment already exists")

    add_vorbiscomment = add_tags

    def delete(self, filename=None):
        """Remove Vorbis comments from a file.

        If no filename is given, the one most recently loaded is used.
        """
        if filename is None:
            filename = self.filename
        for s in list(self.metadata_blocks):
            if isinstance(s, VCFLACDict):
                self.metadata_blocks.remove(s)
                self.tags = None
                self.save()
                break

    vc = property(lambda s: s.tags, doc="Alias for tags; don't use this.")

    def load(self, filename):
        """Load file information from a filename."""

        self.metadata_blocks = []
        self.tags = None
        self.cuesheet = None
        self.seektable = None
        self.filename = filename
        fileobj = StrictFileObject(open(filename, "rb"))
        try:
            self.__check_header(fileobj)
            while self.__read_metadata_block(fileobj):
                pass
        finally:
            fileobj.close()

        try:
            self.metadata_blocks[0].length
        except (AttributeError, IndexError):
            raise FLACNoHeaderError("Stream info block not found")

    @property
    def info(self):
        return self.metadata_blocks[0]

    def add_picture(self, picture):
        """Add a new picture to the file."""
        self.metadata_blocks.append(picture)

    def clear_pictures(self):
        """Delete all pictures from the file."""

        blocks = [b for b in self.metadata_blocks if b.code != Picture.code]
        self.metadata_blocks = blocks

    @property
    def pictures(self):
        """List of embedded pictures"""

        return [b for b in self.metadata_blocks if b.code == Picture.code]

    def save(self, filename=None, deleteid3=False):
        """Save metadata blocks to a file.

        If no filename is given, the one most recently loaded is used.
        """

        if filename is None:
            filename = self.filename
        f = open(filename, 'rb+')

        try:
            # Ensure we've got padding at the end, and only at the end.
            # If adding makes it too large, we'll scale it down later.
            self.metadata_blocks.append(Padding(b'\x00' * 1020))
            MetadataBlock.group_padding(self.metadata_blocks)

            header = self.__check_header(f)
            # "fLaC" and maybe ID3
            available = self.__find_audio_offset(f) - header
            data = MetadataBlock.writeblocks(self.metadata_blocks)

            # Delete ID3v2
            if deleteid3 and header > 4:
                available += header - 4
                header = 4

            if len(data) > available:
                # If we have too much data, see if we can reduce padding.
                padding = self.metadata_blocks[-1]
                newlength = padding.length - (len(data) - available)
                if newlength > 0:
                    padding.length = newlength
                    data = MetadataBlock.writeblocks(self.metadata_blocks)
                    assert len(data) == available

            elif len(data) < available:
                # If we have too little data, increase padding.
                self.metadata_blocks[-1].length += (available - len(data))
                data = MetadataBlock.writeblocks(self.metadata_blocks)
                assert len(data) == available

            if len(data) != available:
                # We couldn't reduce the padding enough.
                diff = (len(data) - available)
                insert_bytes(f, diff, header)

            f.seek(header - 4)
            f.write(b"fLaC" + data)

            # Delete ID3v1
            if deleteid3:
                try:
                    f.seek(-128, 2)
                except IOError:
                    pass
                else:
                    if f.read(3) == b"TAG":
                        f.seek(-128, 2)
                        f.truncate()
        finally:
            f.close()

    def __find_audio_offset(self, fileobj):
        byte = 0x00
        while not (byte & 0x80):
            byte = ord(fileobj.read(1))
            size = to_int_be(fileobj.read(3))
            try:
                block_type = self.METADATA_BLOCKS[byte & 0x7F]
            except IndexError:
                block_type = None

            if block_type and block_type._distrust_size:
                # See comments in read_metadata_block; the size can't
                # be trusted for Vorbis comment blocks and Picture block
                block_type(fileobj)
            else:
                fileobj.read(size)
        return fileobj.tell()

    def __check_header(self, fileobj):
        size = 4
        header = fileobj.read(4)
        if header != b"fLaC":
            size = None
            if header[:3] == b"ID3":
                size = 14 + BitPaddedInt(fileobj.read(6)[2:])
                fileobj.seek(size - 4)
                if fileobj.read(4) != b"fLaC":
                    size = None
        if size is None:
            raise FLACNoHeaderError(
                "%r is not a valid FLAC file" % fileobj.name)
        return size

Open = FLAC

def delete(filename):
    """Remove tags from a file."""
    FLAC(filename).delete()


from route53.exceptions import Route53Error
from route53.resource_record_set import AResourceRecordSet, AAAAResourceRecordSet, CNAMEResourceRecordSet, MXResourceRecordSet, NSResourceRecordSet, PTRResourceRecordSet, SOAResourceRecordSet, SPFResourceRecordSet, SRVResourceRecordSet, TXTResourceRecordSet

# Maps ResourceRecordSet subtag names to kwargs in RRSet subclasses.
RRSET_TAG_TO_KWARG_MAP = {
    'Name': 'name',
    'TTL': 'ttl',
    'Weight': 'weight',
    'Region': 'region',
    'SetIdentifier': 'set_identifier',
}

# Maps the various ResourceRecordSet Types to various RRSet subclasses.
RRSET_TYPE_TO_RSET_SUBCLASS_MAP = {
    'A': AResourceRecordSet,
    'AAAA': AAAAResourceRecordSet,
    'CNAME': CNAMEResourceRecordSet,
    'MX': MXResourceRecordSet,
    'NS': NSResourceRecordSet,
    'PTR': PTRResourceRecordSet,
    'SOA': SOAResourceRecordSet,
    'SPF': SPFResourceRecordSet,
    'SRV': SRVResourceRecordSet,
    'TXT': TXTResourceRecordSet,
}

def parse_rrset_alias(e_alias):
    """
    Parses an Alias tag beneath a ResourceRecordSet, spitting out the two values
    found within. This is specific to A records that are set to Alias.

    :param lxml.etree._Element e_alias: An Alias tag beneath a ResourceRecordSet.
    :rtype: tuple
    :returns: A tuple in the form of ``(alias_hosted_zone_id, alias_dns_name)``.
    """

    alias_hosted_zone_id = e_alias.find('./{*}HostedZoneId').text
    alias_dns_name = e_alias.find('./{*}DNSName').text
    return alias_hosted_zone_id, alias_dns_name

def parse_rrset_record_values(e_resource_records):
    """
    Used to parse the various Values from the ResourceRecords tags on
    most rrset types.

    :param lxml.etree._Element e_resource_records: A ResourceRecords tag
        beneath a ResourceRecordSet.
    :rtype: list
    :returns: A list of resource record strings.
    """

    records = []

    for e_record in e_resource_records:
        for e_value in e_record:
            records.append(e_value.text)

    return records

def parse_rrset(e_rrset, connection, zone_id):
    """
    This a parser that allows the passing of any valid ResourceRecordSet
    tag. It will spit out the appropriate ResourceRecordSet object for the tag.

    :param lxml.etree._Element e_rrset: The root node of the etree parsed
        response from the API.
    :param Route53Connection connection: The connection instance used to
        query the API.
    :param str zone_id: The zone ID of the HostedZone these rrsets belong to.
    :rtype: ResourceRecordSet
    :returns: An instantiated ResourceRecordSet object.
    """

    # This dict will be used to instantiate a ResourceRecordSet instance to yield.
    kwargs = {
        'connection': connection,
        'zone_id': zone_id,
    }
    rrset_type = None

    for e_field in e_rrset:
        # Cheesy way to strip off the namespace.
        tag_name = e_field.tag.split('}')[1]
        field_text = e_field.text

        if tag_name == 'Type':
            # Need to store this to determine which ResourceRecordSet
            # subclass to instantiate.
            rrset_type = field_text
            continue
        elif tag_name == 'AliasTarget':
            # A records have some special field values we need.
            alias_hosted_zone_id, alias_dns_name = parse_rrset_alias(e_field)
            kwargs['alias_hosted_zone_id'] = alias_hosted_zone_id
            kwargs['alias_dns_name'] = alias_dns_name
            # Alias A entries have no TTL.
            kwargs['ttl'] = None
            continue
        elif tag_name == 'ResourceRecords':
            kwargs['records'] = parse_rrset_record_values(e_field)
            continue

        # Map the XML tag name to a kwarg name.
        kw_name = RRSET_TAG_TO_KWARG_MAP[tag_name]
        # This will be the key/val pair used to instantiate the
        # ResourceRecordSet instance.
        kwargs[kw_name] = field_text

    if not rrset_type:
        raise Route53Error("No Type tag found in ListResourceRecordSetsResponse.")

    if 'records' not in kwargs:
        # Not all rrsets have records.
        kwargs['records'] = []

    RRSetSubclass = RRSET_TYPE_TO_RSET_SUBCLASS_MAP[rrset_type]
    return RRSetSubclass(**kwargs)

def list_resource_record_sets_by_zone_id_parser(e_root, connection, zone_id):
    """
    Parses the API responses for the
    :py:meth:`route53.connection.Route53Connection.list_resource_record_sets_by_zone_id`
    method.

    :param lxml.etree._Element e_root: The root node of the etree parsed
        response from the API.
    :param Route53Connection connection: The connection instance used to
        query the API.
    :param str zone_id: The zone ID of the HostedZone these rrsets belong to.
    :rtype: ResourceRecordSet
    :returns: A generator of fully formed ResourceRecordSet instances.
    """

    # The rest of the list pagination tags are handled higher up in the stack.
    # We'll just worry about the ResourceRecordSets tag, which has
    # ResourceRecordSet tags nested beneath it.
    e_rrsets = e_root.find('./{*}ResourceRecordSets')

    for e_rrset in e_rrsets:
        yield parse_rrset(e_rrset, connection, zone_id)


from route53.change_set import ChangeSet
from route53.exceptions import AlreadyDeletedError
from route53.resource_record_set import AResourceRecordSet, AAAAResourceRecordSet, CNAMEResourceRecordSet, MXResourceRecordSet, NSResourceRecordSet, PTRResourceRecordSet, SOAResourceRecordSet, SPFResourceRecordSet, SRVResourceRecordSet, TXTResourceRecordSet

class HostedZone(object):
    """
    A hosted zone is a collection of resource record sets hosted by Route 53.
    Like a traditional DNS zone file, a hosted zone represents a collection of
    resource record sets that are managed together under a single domain name.
    Each hosted zone has its own metadata and configuration information.

    .. warning:: Do not instantiate this directly yourself. Go through
        one of the methods on :py:class:`route53.connection.Route53Connection`.
    """

    def __init__(self, connection, id, name, caller_reference,
                 resource_record_set_count, comment):
        """
        :param Route53Connection connection: The connection instance that
            was used to query the Route53 API, leading to this object's
            creation.
        :param str id: Route53's unique ID for this hosted zone.
        :param str name: The name of the domain.
        :param str caller_reference: A unique string that identifies the
            request to create the hosted zone.
        :param int resource_record_set_count: The number of resource record
            sets in the hosted zone.
        """

        self.connection = connection
        self.id = id
        self.name = name
        self.caller_reference = caller_reference
        self.resource_record_set_count = int(resource_record_set_count)
        self.comment = comment

        # Don't access this directly, we use it for lazy loading.
        self._nameservers = []
        # This is set to True when this HostedZone has been deleted in Route53.
        self._is_deleted = False

    def __str__(self):
        return '<HostedZone: %s -- %s>' % (self.name, self.id)

    @property
    def nameservers(self):
        """
        :rtype: list
        :returns: A list of nameserver strings for this hosted zone.
        """

        # If this HostedZone was instantiated by ListHostedZones, the nameservers
        # attribute didn't get populated. If the user requests it, we'll
        # lazy load by querying it in after the fact. It's safe to cache like
        # this since  these nameserver values won't change.
        if not self._nameservers:
            # We'll just snatch the nameserver values from a fresh copy
            # via GetHostedZone.
            hosted_zone = self.connection.get_hosted_zone_by_id(self.id)
            self._nameservers = hosted_zone._nameservers

        return self._nameservers

    @property
    def record_sets(self):
        """
        Queries for the Resource Record Sets that are under this HostedZone.
        This is typically the way to go to find specific record sets, or
        to list them all.

        We don't currently implement any filtering convenience method,
        since it is very easy to do this yourself, catered to your own needs.
        For example, if you find your match, you may choose to stop iterating
        on the generator, potentially saving yourself extra API queries
        (behind the scenes).

        .. warning:: This result set can get pretty large if you have a ton
            of records.

        :rtype: generator
        :returns: A generator of ResourceRecordSet sub-classes.
        """

        for rrset in self.connection._list_resource_record_sets_by_zone_id(self.id):
            yield rrset

    def delete(self, force=False):
        """
        Deletes this hosted zone. After this method is ran, you won't be able
        to add records, or do anything else with the zone. You'd need to
        re-create it, as zones are read-only after creation.

        :keyword bool force: If ``True``, delete the
            :py:class:`HostedZone <route53.hosted_zone.HostedZone>`, even if it
            means nuking all associated record sets. If ``False``, an
            exception is raised if this
            :py:class:`HostedZone <route53.hosted_zone.HostedZone>`
            has record sets.
        :rtype: dict
        :returns: A dict of change info, which contains some details about
            the request.
        """

        self._halt_if_already_deleted()

        if force:
            # Forcing deletion by cleaning up all record sets first. We'll
            # do it all in one change set.
            cset = ChangeSet(connection=self.connection, hosted_zone_id=self.id)

            for rrset in self.record_sets:
                # You can delete a HostedZone if there are only SOA and NS
                # entries left. So delete everything but SOA/NS entries.
                if rrset.rrset_type not in ['SOA', 'NS']:
                    cset.add_change('DELETE', rrset)

            if cset.deletions or cset.creations:
                # Bombs away.
                self.connection._change_resource_record_sets(cset)

        # Now delete the HostedZone.
        retval = self.connection.delete_hosted_zone_by_id(self.id)

        # Used to protect against modifying a deleted HostedZone.
        self._is_deleted = True

        return retval

    def _halt_if_already_deleted(self):
        """
        Convenience method used to raise an AlreadyDeletedError exception if
        this HostedZone has been deleted.

        :raises: AlreadyDeletedError
        """

        if self._is_deleted:
            raise AlreadyDeletedError("Can't manipulate a deleted zone.")

    def _add_record(self, record_set_class, name, values, ttl=60, weight=None,
                    region=None,set_identifier=None, alias_hosted_zone_id=None,
                    alias_dns_name=None):
        """
        Convenience method for creating ResourceRecordSets. Most of the calls
        are basically the same, this saves on repetition.

        :rtype: tuple
        :returns: A tuple in the form of ``(rrset, change_info)``, where
            ``rrset`` is the newly created ResourceRecordSet sub-class
             instance.
        """

        self._halt_if_already_deleted()

        rrset_kwargs = dict(
            connection=self.connection,
            zone_id=self.id,
            name=name,
            ttl=ttl,
            records=values,
            weight=weight,
            region=region,
            set_identifier=set_identifier,
            )

        if alias_hosted_zone_id or alias_dns_name:
            rrset_kwargs.update(dict(
                alias_hosted_zone_id=alias_hosted_zone_id,
                alias_dns_name=alias_dns_name
            ))

        rrset = record_set_class(**rrset_kwargs)

        cset = ChangeSet(connection=self.connection, hosted_zone_id=self.id)
        cset.add_change('CREATE', rrset)

        change_info = self.connection._change_resource_record_sets(cset)

        return rrset, change_info

    def create_a_record(self, name, values, ttl=60, weight=None, region=None,
                     set_identifier=None, alias_hosted_zone_id=None,
                     alias_dns_name=None):
        """
        Creates and returns an A record attached to this hosted zone.

        :param str name: The fully qualified name of the record to add.
        :param list values: A list of value strings for the record.
        :keyword int ttl: The time-to-live of the record (in seconds).
        :keyword int weight: *For weighted record sets only*. Among resource record
            sets that have the same combination of DNS name and type, a value
            that determines what portion of traffic for the current resource
            record set is routed to the associated location. Ranges from 0-255.
        :keyword str region: *For latency-based record sets*. The Amazon EC2 region
            where the resource that is specified in this resource record set
            resides.
        :keyword str set_identifier: *For weighted and latency resource record
            sets only*. An identifier that differentiates among multiple
            resource record sets that have the same combination of DNS name
            and type. 1-128 chars.
        :keyword str alias_hosted_zone_id: Alias A records have this specified.
            It appears to be the hosted zone ID for the ELB the Alias points at.
        :keyword str alias_dns_name: Alias A records have this specified. It is
            the DNS name for the ELB that the Alias points to.
        :rtype: tuple
        :returns: A tuple in the form of ``(rrset, change_info)``, where
            ``rrset`` is the newly created
            :py:class:`AResourceRecordSet <route53.resource_record_set.AResourceRecordSet>`
            instance.
        """

        self._halt_if_already_deleted()

        # Grab the params/kwargs here for brevity's sake.
        values = locals()
        del values['self']

        return self._add_record(AResourceRecordSet, **values)

    def create_aaaa_record(self, name, values, ttl=60, weight=None, region=None,
                        set_identifier=None):
        """
        Creates an AAAA record attached to this hosted zone.

        :param str name: The fully qualified name of the record to add.
        :param list values: A list of value strings for the record.
        :keyword int ttl: The time-to-live of the record (in seconds).
        :keyword int weight: *For weighted record sets only*. Among resource record
            sets that have the same combination of DNS name and type, a value
            that determines what portion of traffic for the current resource
            record set is routed to the associated location. Ranges from 0-255.
        :keyword str region: *For latency-based record sets*. The Amazon EC2 region
            where the resource that is specified in this resource record set
            resides.
        :keyword str set_identifier: *For weighted and latency resource record
            sets only*. An identifier that differentiates among multiple
            resource record sets that have the same combination of DNS name
            and type. 1-128 chars.
        :rtype: tuple
        :returns: A tuple in the form of ``(rrset, change_info)``, where
            ``rrset`` is the newly created AAAAResourceRecordSet instance.
        """

        self._halt_if_already_deleted()

        # Grab the params/kwargs here for brevity's sake.
        values = locals()
        del values['self']

        return self._add_record(AAAAResourceRecordSet, **values)

    def create_cname_record(self, name, values, ttl=60, weight=None, region=None,
                           set_identifier=None):
        """
        Creates a CNAME record attached to this hosted zone.

        :param str name: The fully qualified name of the record to add.
        :param list values: A list of value strings for the record.
        :keyword int ttl: The time-to-live of the record (in seconds).
        :keyword int weight: *For weighted record sets only*. Among resource record
            sets that have the same combination of DNS name and type, a value
            that determines what portion of traffic for the current resource
            record set is routed to the associated location. Ranges from 0-255.
        :keyword str region: *For latency-based record sets*. The Amazon EC2 region
            where the resource that is specified in this resource record set
            resides.
        :keyword str set_identifier: *For weighted and latency resource record
            sets only*. An identifier that differentiates among multiple
            resource record sets that have the same combination of DNS name
            and type. 1-128 chars.
        :rtype: tuple
        :returns: A tuple in the form of ``(rrset, change_info)``, where
            ``rrset`` is the newly created CNAMEResourceRecordSet instance.
        """

        self._halt_if_already_deleted()

        # Grab the params/kwargs here for brevity's sake.
        values = locals()
        del values['self']

        return self._add_record(CNAMEResourceRecordSet, **values)

    def create_mx_record(self, name, values, ttl=60):
        """
        Creates a MX record attached to this hosted zone.

        :param str name: The fully qualified name of the record to add.
        :param list values: A list of value strings for the record.
        :keyword int ttl: The time-to-live of the record (in seconds).
        :rtype: tuple
        :returns: A tuple in the form of ``(rrset, change_info)``, where
            ``rrset`` is the newly created MXResourceRecordSet instance.
        """

        self._halt_if_already_deleted()

        # Grab the params/kwargs here for brevity's sake.
        values = locals()
        del values['self']

        return self._add_record(MXResourceRecordSet, **values)

    def create_ns_record(self, name, values, ttl=60):
        """
        Creates a NS record attached to this hosted zone.

        :param str name: The fully qualified name of the record to add.
        :param list values: A list of value strings for the record.
        :keyword int ttl: The time-to-live of the record (in seconds).
        :rtype: tuple
        :returns: A tuple in the form of ``(rrset, change_info)``, where
            ``rrset`` is the newly created NSResourceRecordSet instance.
        """

        self._halt_if_already_deleted()

        # Grab the params/kwargs here for brevity's sake.
        values = locals()
        del values['self']

        return self._add_record(NSResourceRecordSet, **values)

    def create_ptr_record(self, name, values, ttl=60):
        """
        Creates a PTR record attached to this hosted zone.

        :param str name: The fully qualified name of the record to add.
        :param list values: A list of value strings for the record.
        :keyword int ttl: The time-to-live of the record (in seconds).
        :rtype: tuple
        :returns: A tuple in the form of ``(rrset, change_info)``, where
            ``rrset`` is the newly created PTRResourceRecordSet instance.
        """

        self._halt_if_already_deleted()

        # Grab the params/kwargs here for brevity's sake.
        values = locals()
        del values['self']

        return self._add_record(PTRResourceRecordSet, **values)

    def create_spf_record(self, name, values, ttl=60):
        """
        Creates a SPF record attached to this hosted zone.

        :param str name: The fully qualified name of the record to add.
        :param list values: A list of value strings for the record.
        :keyword int ttl: The time-to-live of the record (in seconds).
        :rtype: tuple
        :returns: A tuple in the form of ``(rrset, change_info)``, where
            ``rrset`` is the newly created SPFResourceRecordSet instance.
        """

        self._halt_if_already_deleted()

        # Grab the params/kwargs here for brevity's sake.
        values = locals()
        del values['self']

        return self._add_record(SPFResourceRecordSet, **values)

    def create_srv_record(self, name, values, ttl=60):
        """
        Creates a SRV record attached to this hosted zone.

        :param str name: The fully qualified name of the record to add.
        :param list values: A list of value strings for the record.
        :keyword int ttl: The time-to-live of the record (in seconds).
        :rtype: tuple
        :returns: A tuple in the form of ``(rrset, change_info)``, where
            ``rrset`` is the newly created SRVResourceRecordSet instance.
        """

        self._halt_if_already_deleted()

        # Grab the params/kwargs here for brevity's sake.
        values = locals()
        del values['self']

        return self._add_record(SRVResourceRecordSet, **values)

    def create_txt_record(self, name, values, ttl=60, weight=None, region=None,
                          set_identifier=None):
        """
        Creates a TXT record attached to this hosted zone.

        :param str name: The fully qualified name of the record to add.
        :param list values: A list of value strings for the record.
        :keyword int ttl: The time-to-live of the record (in seconds).
        :keyword int weight: *For weighted record sets only*. Among resource record
            sets that have the same combination of DNS name and type, a value
            that determines what portion of traffic for the current resource
            record set is routed to the associated location. Ranges from 0-255.
        :keyword str region: *For latency-based record sets*. The Amazon EC2 region
            where the resource that is specified in this resource record set
            resides.
        :keyword str set_identifier: *For weighted and latency resource record
            sets only*. An identifier that differentiates among multiple
            resource record sets that have the same combination of DNS name
            and type. 1-128 chars.
        :rtype: tuple
        :returns: A tuple in the form of ``(rrset, change_info)``, where
            ``rrset`` is the newly created TXTResourceRecordSet instance.
        """

        self._halt_if_already_deleted()

        # Grab the params/kwargs here for brevity's sake.
        values = locals()
        del values['self']

        return self._add_record(TXTResourceRecordSet, **values)


# -*- coding: utf-8 -*-

# Copyright 2006 Joe Wreschnig
#           2014 Ben Ockmore
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.

"""Easier access to ID3 tags.

EasyID3 is a wrapper around mutagen.id3.ID3 to make ID3 tags appear
more like Vorbis or APEv2 tags.
"""

from collections import MutableMapping

from fnmatch import fnmatchcase

import mutagen.id3

from ._compat import iteritems, text_type, PY2
from mutagen import Metadata
from mutagen._util import dict_match
from mutagen.id3 import ID3, error, delete, ID3FileType

__all__ = ['EasyID3', 'Open', 'delete']

class EasyID3KeyError(KeyError, ValueError, error):
    """Raised when trying to get/set an invalid key.

    Subclasses both KeyError and ValueError for API compatibility,
    catching KeyError is preferred.
    """

class EasyID3(MutableMapping, mutagen.Metadata):
    """A file with an ID3 tag.

    Like Vorbis comments, EasyID3 keys are case-insensitive ASCII
    strings. Only a subset of ID3 frames are supported by default. Use
    EasyID3.RegisterKey and its wrappers to support more.

    You can also set the GetFallback, SetFallback, and DeleteFallback
    to generic key getter/setter/deleter functions, which are called
    if no specific handler is registered for a key. Additionally,
    ListFallback can be used to supply an arbitrary list of extra
    keys. These can be set on EasyID3 or on individual instances after
    creation.

    To use an EasyID3 class with mutagen.mp3.MP3::

        from mutagen.mp3 import EasyMP3 as MP3
        MP3(filename)

    Because many of the attributes are constructed on the fly, things
    like the following will not work::

        ezid3["performer"].append("Joe")

    Instead, you must do::

        values = ezid3["performer"]
        values.append("Joe")
        ezid3["performer"] = values

    """

    Set = {}
    Get = {}
    Delete = {}
    List = {}

    # For compatibility.
    valid_keys = Get

    GetFallback = None
    SetFallback = None
    DeleteFallback = None
    ListFallback = None

    @classmethod
    def RegisterKey(cls, key,
                    getter=None, setter=None, deleter=None, lister=None):
        """Register a new key mapping.

        A key mapping is four functions, a getter, setter, deleter,
        and lister. The key may be either a string or a glob pattern.

        The getter, deleted, and lister receive an ID3 instance and
        the requested key name. The setter also receives the desired
        value, which will be a list of strings.

        The getter, setter, and deleter are used to implement __getitem__,
        __setitem__, and __delitem__.

        The lister is used to implement keys(). It should return a
        list of keys that are actually in the ID3 instance, provided
        by its associated getter.
        """
        key = key.lower()
        if getter is not None:
            cls.Get[key] = getter
        if setter is not None:
            cls.Set[key] = setter
        if deleter is not None:
            cls.Delete[key] = deleter
        if lister is not None:
            cls.List[key] = lister

    @classmethod
    def RegisterTextKey(cls, key, frameid):
        """Register a text key.

        If the key you need to register is a simple one-to-one mapping
        of ID3 frame name to EasyID3 key, then you can use this
        function::

            EasyID3.RegisterTextKey("title", "TIT2")
        """
        def getter(id3, key):
            return list(id3[frameid])

        def setter(id3, key, value):
            try:
                frame = id3[frameid]
            except KeyError:
                id3.add(mutagen.id3.Frames[frameid](encoding=3, text=value))
            else:
                frame.encoding = 3
                frame.text = value

        def deleter(id3, key):
            del(id3[frameid])

        cls.RegisterKey(key, getter, setter, deleter)

    @classmethod
    def RegisterTXXXKey(cls, key, desc):
        """Register a user-defined text frame key.

        Some ID3 tags are stored in TXXX frames, which allow a
        freeform 'description' which acts as a subkey,
        e.g. TXXX:BARCODE.::

            EasyID3.RegisterTXXXKey('barcode', 'BARCODE').
        """
        frameid = "TXXX:" + desc

        def getter(id3, key):
            return list(id3[frameid])

        def setter(id3, key, value):
            try:
                frame = id3[frameid]
            except KeyError:
                enc = 0
                # Store 8859-1 if we can, per MusicBrainz spec.
                try:
                    for v in value:
                        v.encode('latin_1')
                except UnicodeError:
                    enc = 3

                id3.add(mutagen.id3.TXXX(encoding=enc, text=value, desc=desc))
            else:
                frame.text = value

        def deleter(id3, key):
            del(id3[frameid])

        cls.RegisterKey(key, getter, setter, deleter)

    def __init__(self, filename=None):
        self.__id3 = ID3()
        if filename is not None:
            self.load(filename)

    load = property(lambda s: s.__id3.load,
                    lambda s, v: setattr(s.__id3, 'load', v))

    save = property(lambda s: s.__id3.save,
                    lambda s, v: setattr(s.__id3, 'save', v))

    delete = property(lambda s: s.__id3.delete,
                      lambda s, v: setattr(s.__id3, 'delete', v))

    filename = property(lambda s: s.__id3.filename,
                        lambda s, fn: setattr(s.__id3, 'filename', fn))

    size = property(lambda s: s.__id3.size,
                    lambda s, fn: setattr(s.__id3, 'size', s))

    def __getitem__(self, key):
        key = key.lower()
        func = dict_match(self.Get, key, self.GetFallback)
        if func is not None:
            return func(self.__id3, key)
        else:
            raise EasyID3KeyError("%r is not a valid key" % key)

    def __setitem__(self, key, value):
        key = key.lower()
        if PY2:
            if isinstance(value, basestring):
                value = [value]
        else:
            if isinstance(value, text_type):
                value = [value]
        func = dict_match(self.Set, key, self.SetFallback)
        if func is not None:
            return func(self.__id3, key, value)
        else:
            raise EasyID3KeyError("%r is not a valid key" % key)

    def __delitem__(self, key):
        key = key.lower()
        func = dict_match(self.Delete, key, self.DeleteFallback)
        if func is not None:
            return func(self.__id3, key)
        else:
            raise EasyID3KeyError("%r is not a valid key" % key)

    def __iter__(self):
        keys = []
        for key in self.Get.keys():
            if key in self.List:
                keys.extend(self.List[key](self.__id3, key))
            elif key in self:
                keys.append(key)
        if self.ListFallback is not None:
            keys.extend(self.ListFallback(self.__id3, ""))
        return iter(keys)

    def __len__(self):
        keys = []
        for key in self.Get.keys():
            if key in self.List:
                keys.extend(self.List[key](self.__id3, key))
            elif key in self:
                keys.append(key)
        if self.ListFallback is not None:
            keys.extend(self.ListFallback(self.__id3, ""))
        return len(keys)

    def pprint(self):
        """Print tag key=value pairs."""
        strings = []
        for key in sorted(self.keys()):
            values = self[key]
            for value in values:
                strings.append("%s=%s" % (key, value))
        return "\n".join(strings)

Open = EasyID3

def genre_get(id3, key):
    return id3["TCON"].genres

def genre_set(id3, key, value):
    try:
        frame = id3["TCON"]
    except KeyError:
        id3.add(mutagen.id3.TCON(encoding=3, text=value))
    else:
        frame.encoding = 3
        frame.genres = value

def genre_delete(id3, key):
    del(id3["TCON"])

def date_get(id3, key):
    return [stamp.text for stamp in id3["TDRC"].text]

def date_set(id3, key, value):
    id3.add(mutagen.id3.TDRC(encoding=3, text=value))

def date_delete(id3, key):
    del(id3["TDRC"])

def performer_get(id3, key):
    people = []
    wanted_role = key.split(":", 1)[1]
    try:
        mcl = id3["TMCL"]
    except KeyError:
        raise KeyError(key)
    for role, person in mcl.people:
        if role == wanted_role:
            people.append(person)
    if people:
        return people
    else:
        raise KeyError(key)

def performer_set(id3, key, value):
    wanted_role = key.split(":", 1)[1]
    try:
        mcl = id3["TMCL"]
    except KeyError:
        mcl = mutagen.id3.TMCL(encoding=3, people=[])
        id3.add(mcl)
    mcl.encoding = 3
    people = [p for p in mcl.people if p[0] != wanted_role]
    for v in value:
        people.append((wanted_role, v))
    mcl.people = people

def performer_delete(id3, key):
    wanted_role = key.split(":", 1)[1]
    try:
        mcl = id3["TMCL"]
    except KeyError:
        raise KeyError(key)
    people = [p for p in mcl.people if p[0] != wanted_role]
    if people == mcl.people:
        raise KeyError(key)
    elif people:
        mcl.people = people
    else:
        del(id3["TMCL"])

def performer_list(id3, key):
    try:
        mcl = id3["TMCL"]
    except KeyError:
        return []
    else:
        return list(set("performer:" + p[0] for p in mcl.people))

def musicbrainz_trackid_get(id3, key):
    return [id3["UFID:http://musicbrainz.org"].data.decode('ascii')]

def musicbrainz_trackid_set(id3, key, value):
    if len(value) != 1:
        raise ValueError("only one track ID may be set per song")
    value = value[0].encode('ascii')
    try:
        frame = id3["UFID:http://musicbrainz.org"]
    except KeyError:
        frame = mutagen.id3.UFID(owner="http://musicbrainz.org", data=value)
        id3.add(frame)
    else:
        frame.data = value

def musicbrainz_trackid_delete(id3, key):
    del(id3["UFID:http://musicbrainz.org"])

def website_get(id3, key):
    urls = [frame.url for frame in id3.getall("WOAR")]
    if urls:
        return urls
    else:
        raise EasyID3KeyError(key)

def website_set(id3, key, value):
    id3.delall("WOAR")
    for v in value:
        id3.add(mutagen.id3.WOAR(url=v))

def website_delete(id3, key):
    id3.delall("WOAR")

def gain_get(id3, key):
    try:
        frame = id3["RVA2:" + key[11:-5]]
    except KeyError:
        raise EasyID3KeyError(key)
    else:
        return [u"%+f dB" % frame.gain]

def gain_set(id3, key, value):
    if len(value) != 1:
        raise ValueError(
            "there must be exactly one gain value, not %r.", value)
    gain = float(value[0].split()[0])
    try:
        frame = id3["RVA2:" + key[11:-5]]
    except KeyError:
        frame = mutagen.id3.RVA2(desc=key[11:-5], gain=0, peak=0, channel=1)
        id3.add(frame)
    frame.gain = gain

def gain_delete(id3, key):
    try:
        frame = id3["RVA2:" + key[11:-5]]
    except KeyError:
        pass
    else:
        if frame.peak:
            frame.gain = 0.0
        else:
            del(id3["RVA2:" + key[11:-5]])

def peak_get(id3, key):
    try:
        frame = id3["RVA2:" + key[11:-5]]
    except KeyError:
        raise EasyID3KeyError(key)
    else:
        return [u"%f" % frame.peak]

def peak_set(id3, key, value):
    if len(value) != 1:
        raise ValueError(
            "there must be exactly one peak value, not %r.", value)
    peak = float(value[0])
    if peak >= 2 or peak < 0:
        raise ValueError("peak must be => 0 and < 2.")
    try:
        frame = id3["RVA2:" + key[11:-5]]
    except KeyError:
        frame = mutagen.id3.RVA2(desc=key[11:-5], gain=0, peak=0, channel=1)
        id3.add(frame)
    frame.peak = peak

def peak_delete(id3, key):
    try:
        frame = id3["RVA2:" + key[11:-5]]
    except KeyError:
        pass
    else:
        if frame.gain:
            frame.peak = 0.0
        else:
            del(id3["RVA2:" + key[11:-5]])

def peakgain_list(id3, key):
    keys = []
    for frame in id3.getall("RVA2"):
        keys.append("replaygain_%s_gain" % frame.desc)
        keys.append("replaygain_%s_peak" % frame.desc)
    return keys

for frameid, key in iteritems({
    "TALB": "album",
    "TBPM": "bpm",
    "TCMP": "compilation",  # iTunes extension
    "TCOM": "composer",
    "TCOP": "copyright",
    "TENC": "encodedby",
    "TEXT": "lyricist",
    "TLEN": "length",
    "TMED": "media",
    "TMOO": "mood",
    "TIT2": "title",
    "TIT3": "version",
    "TPE1": "artist",
    "TPE2": "performer",
    "TPE3": "conductor",
    "TPE4": "arranger",
    "TPOS": "discnumber",
    "TPUB": "organization",
    "TRCK": "tracknumber",
    "TOLY": "author",
    "TSO2": "albumartistsort",  # iTunes extension
    "TSOA": "albumsort",
    "TSOC": "composersort",  # iTunes extension
    "TSOP": "artistsort",
    "TSOT": "titlesort",
    "TSRC": "isrc",
    "TSST": "discsubtitle",
}):
    EasyID3.RegisterTextKey(key, frameid)

EasyID3.RegisterKey("genre", genre_get, genre_set, genre_delete)
EasyID3.RegisterKey("date", date_get, date_set, date_delete)
EasyID3.RegisterKey(
    "performer:*", performer_get, performer_set, performer_delete,
    performer_list)
EasyID3.RegisterKey("musicbrainz_trackid", musicbrainz_trackid_get,
                    musicbrainz_trackid_set, musicbrainz_trackid_delete)
EasyID3.RegisterKey("website", website_get, website_set, website_delete)
EasyID3.RegisterKey(
    "replaygain_*_gain", gain_get, gain_set, gain_delete, peakgain_list)
EasyID3.RegisterKey("replaygain_*_peak", peak_get, peak_set, peak_delete)

# At various times, information for this came from
# http://musicbrainz.org/docs/specs/metadata_tags.html
# http://bugs.musicbrainz.org/ticket/1383
# http://musicbrainz.org/doc/MusicBrainzTag
for desc, key in iteritems({
    u"MusicBrainz Artist Id": "musicbrainz_artistid",
    u"MusicBrainz Album Id": "musicbrainz_albumid",
    u"MusicBrainz Album Artist Id": "musicbrainz_albumartistid",
    u"MusicBrainz TRM Id": "musicbrainz_trmid",
    u"MusicIP PUID": "musicip_puid",
    u"MusicMagic Fingerprint": "musicip_fingerprint",
    u"MusicBrainz Album Status": "musicbrainz_albumstatus",
    u"MusicBrainz Album Type": "musicbrainz_albumtype",
    u"MusicBrainz Album Release Country": "releasecountry",
    u"MusicBrainz Disc Id": "musicbrainz_discid",
    u"ASIN": "asin",
    u"BARCODE": "barcode",
}):
    EasyID3.RegisterTXXXKey(key, desc)

class EasyID3FileType(ID3FileType):
    """Like ID3FileType, but uses EasyID3 for tags."""
    ID3 = EasyID3


from io import BytesIO
from lxml import etree
from route53.util import prettyprint_xml

def get_change_values(change):
    """
    In the case of deletions, we pull the change values for the XML request
    from the ResourceRecordSet._initial_vals dict, since we want the original
    values. For creations, we pull from the attributes on ResourceRecordSet.

    Since we're dealing with attributes vs. dict key/vals, we'll abstract
    this part away here and just always pass a dict to write_change.

    :rtype: dict
    :returns: A dict of change data, used by :py:func:`write_change` to
        write the change request XML.
    """

    action, rrset = change

    if action == 'CREATE':
        # For creations, we want the current values, since they don't need to
        # match an existing record set.
        values = dict()
        for key, val in rrset._initial_vals.items():
            # Pull from the record set's attributes, which are the current
            # values.
            values[key] = getattr(rrset, key)
        return values
    else:
        # We can look at the initial values dict for deletions, since we
        # have to match against the values currently in Route53.
        return rrset._initial_vals

def write_change(change):
    """
    Creates an XML element for the change.

    :param tuple change: A change tuple from a ChangeSet. Comes in the form
        of ``(action, rrset)``.
    :rtype: lxml.etree._Element
    :returns: A fully baked Change tag.
    """

    action, rrset = change

    change_vals = get_change_values(change)

    e_change = etree.Element("Change")

    e_action = etree.SubElement(e_change, "Action")
    e_action.text = action

    e_rrset = etree.SubElement(e_change, "ResourceRecordSet")

    e_name = etree.SubElement(e_rrset, "Name")
    e_name.text = change_vals['name']

    e_type = etree.SubElement(e_rrset, "Type")
    e_type.text = rrset.rrset_type

    if change_vals.get('set_identifier'):
        e_set_id = etree.SubElement(e_rrset, "SetIdentifier")
        e_set_id.text = change_vals['set_identifier']

    if change_vals.get('weight'):
        e_weight = etree.SubElement(e_rrset, "Weight")
        e_weight.text = change_vals['weight']

    if change_vals.get('alias_hosted_zone_id') or change_vals.get('alias_dns_name'):
        e_alias_target = etree.SubElement(e_rrset, "AliasTarget")

        e_hosted_zone_id = etree.SubElement(e_alias_target, "HostedZoneId")
        e_hosted_zone_id.text = change_vals['alias_hosted_zone_id']
        e_dns_name = etree.SubElement(e_alias_target, "DNSName")
        e_dns_name.text = change_vals['alias_dns_name']

    if change_vals.get('region'):
        e_weight = etree.SubElement(e_rrset, "Region")
        e_weight.text = change_vals['region']

    e_ttl = etree.SubElement(e_rrset, "TTL")
    e_ttl.text = str(change_vals['ttl'])

    if rrset.is_alias_record_set():
        # A record sets in Alias mode don't have any resource records.
        return e_change

    e_resource_records = etree.SubElement(e_rrset, "ResourceRecords")

    for value in change_vals['records']:
        e_resource_record = etree.SubElement(e_resource_records, "ResourceRecord")
        e_value = etree.SubElement(e_resource_record, "Value")
        e_value.text = value

    return e_change

def change_resource_record_set_writer(connection, change_set, comment=None):
    """
    Forms an XML string that we'll send to Route53 in order to change
    record sets.

    :param Route53Connection connection: The connection instance used to
        query the API.
    :param change_set.ChangeSet change_set: The ChangeSet object to create the
        XML doc from.
    :keyword str comment: An optional comment to go along with the request.
    """

    e_root = etree.Element(
        "ChangeResourceRecordSetsRequest",
        xmlns=connection._xml_namespace
    )

    e_change_batch = etree.SubElement(e_root, "ChangeBatch")

    if comment:
        e_comment = etree.SubElement(e_change_batch, "Comment")
        e_comment.text = comment

    e_changes = etree.SubElement(e_change_batch, "Changes")

    # Deletions need to come first in the change sets.
    for change in change_set.deletions + change_set.creations:
        e_changes.append(write_change(change))

    e_tree = etree.ElementTree(element=e_root)

    #print(prettyprint_xml(e_root))

    fobj = BytesIO()
    # This writes bytes.
    e_tree.write(fobj, xml_declaration=True, encoding='utf-8', method="xml")
    return fobj.getvalue().decode('utf-8')


import tkinter
from tkinter import ttk
import os
import nanoplot.NanoPlot as nanoplot
import logging
from datetime import datetime as dt
import sys
from .version import __version__
import time
from argparse import ArgumentParser
from nanogui.nanoguis import nanoPlotGui as nanoPlotGui
from nanogui.nanoguis import nanoCompGui as nanoCompGui
# from nanogui.nanoguis import nanoFiltGui as nanoFiltGui

class toolSelector(tkinter.Frame):
    def __init__(self, logfile):
        self.logfile = logfile
        s = ttk.Style()
        if 'alt' in s.theme_names():
            s.theme_use('alt')
        self.root = tkinter.Frame.__init__(self)
        self.master.title("Welcome to nanoGUI")
        self.master.rowconfigure(5, weight=1)
        self.master.columnconfigure(5, weight=1)
        self.grid(sticky=tkinter.W + tkinter.E + tkinter.N + tkinter.S, padx=10, pady=10)
        ttk.Label(self,
                  text='Which tool do you want to use?',
                  ).grid(column=0, row=0, sticky=tkinter.W, padx=3, pady=3)
        ttk.Button(self,
                   text="NanoPlot",
                   command=self.start_nanoplotgui,
                   ).grid(column=0, row=1, sticky=tkinter.W, padx=3, pady=3)
        ttk.Button(self,
                   text="NanoComp",
                   command=self.start_nanocompgui,
                   ).grid(column=0, row=2, sticky=tkinter.W, padx=3, pady=3)
        # ttk.Button(self,
        #            text="NanoFilt",
        #            command=self.start_nanofiltgui,
        #            ).grid(column=1, row=1, sticky=tkinter.W, padx=3, pady=3)

    def start_nanoplotgui(self):
        self.destroy()
        nanoplotgui(self.logfile)

    def start_nanocompgui(self):
        self.destroy()
        nanocompgui(self.logfile)

    # def start_nanofiltgui(self):
    #     self.destroy()
    #     nanofiltgui(self.logfile)

def main():
    args = get_args()
    if args.debug:
        logfile = init_logs()
    else:
        logfile = None
    toolSelector(logfile).mainloop()

def nanoplotgui(logfile):
    nanoPlotGui(logfile).mainloop()

def nanocompgui(logfile):
    nanoCompGui(logfile).mainloop()

# def nanofiltgui(logfile):
#     nanoFiltGui(logfile).mainloop()

def init_logs():
    """Initiate log file."""
    start_time = dt.fromtimestamp(time.time()).strftime('%Y%m%d_%H%M')
    logname = os.path.join(os.path.expanduser("~") + "/nanoGUI_" + start_time + ".log")
    handlers = [logging.FileHandler(logname)]
    logging.basicConfig(
        format='%(asctime)s %(message)s',
        handlers=handlers,
        level=logging.INFO)
    logging.info('NanoGUI {} started with NanoPlot {}'.format(__version__, nanoplot.__version__))
    logging.info('Python version is: {}'.format(sys.version.replace('\n', ' ')))
    return logname

def get_args():
    parser = ArgumentParser(description="Gui for NanoPlot".upper())
    parser.add_argument("-d", "--debug",
                        action="store_true",
                        help="create logfile creating debug info in $HOME")
    return parser.parse_args()

if __name__ == '__main__':
    main()