"""
iPod iTunesDB parser.

Documentation:
- http://ipl.derpapst.org/wiki/ITunesDB/iTunesDB_File

Author: Romain HERAULT
Creation date: 19 august 2006
"""

from hachoir.parser import Parser
from hachoir.field import (FieldSet,
                           UInt8, UInt16, UInt32, Int32, UInt64, TimestampMac32,
                           String, Float32, NullBytes, Enum, RawBytes)
from hachoir.core.endian import LITTLE_ENDIAN
from hachoir.core.tools import humanDuration
from hachoir.core.text_handler import displayHandler, filesizeHandler

list_order = {
    1: "playlist order (manual sort order)",
    2: "???",
    3: "songtitle",
    4: "album",
    5: "artist",
    6: "bitrate",
    7: "genre",
    8: "kind",
    9: "date modified",
    10: "track number",
    11: "size",
    12: "time",
    13: "year",
    14: "sample rate",
    15: "comment",
    16: "date added",
    17: "equalizer",
    18: "composer",
    19: "???",
    20: "play count",
    21: "last played",
    22: "disc number",
    23: "my rating",
    24: "release date",
    25: "BPM",
    26: "grouping",
    27: "category",
    28: "description",
    29: "show",
    30: "season",
    31: "episode number"
}


class DataObject(FieldSet):
    type_name = {
        1: "Title",
        2: "Location",
        3: "Album",
        4: "Artist",
        5: "Genre",
        6: "Filetype",
        7: "EQ Setting",
        8: "Comment",
        9: "Category",
        12: "Composer",
        13: "Grouping",
        14: "Description text",
        15: "Podcast Enclosure URL",
        16: "Podcast RSS URL",
        17: "Chapter data",
        18: "Subtitle",
        19: "Show (for TV Shows only)",
        20: "Episode",
        21: "TV Network",
        22: "Album-Artist",
        23: "Artist for Sorting",
        24: "List of keywords pretaining track",
        25: "Locale for TV show(?)",
        27: "Title for Sorting",
        28: "Album for Sorting",
        29: "Album-Artist for Sorting",
        30: "Composer for Sorting",
        31: "Show for Sorting",
        # 32: "Unknown binary field for video tracks",
        50: "Smart Playlist Data",
        51: "Smart Playlist Rules",
        52: "Library Playlist Index",
        53: "Library Playlist Index letter in jump table",
        100: "Ccolumn Sizing Info as well as an order indicator in playlists.",
        102: "For iPhone",
        200: "Album name (for album descriptions)",
        201: "Album artist (for album descriptions)",
        202: "Album sort artist (for album descriptions)",
        203: "Podcast URL in Album List",
        204: "TV Show in Album List"
    }

    mhod52_sort_index_type_name = {
        3: "Title",
        4: "Album, then Disk/Tracknumber, then Title",
        5: "Artist, then Album, then Disc/Tracknumber, then Title",
        7: "Genre, then Artist, then Album, then Disc/Tracknumber, then Title",
        8: "Composer, then Title"
    }

    def __init__(self, *args, **kw):
        FieldSet.__init__(self, *args, **kw)
        self._size = self["entry_length"].value * 8

    def createFields(self):
        yield String(self, "header_id", 4, "Data Object Header Markup (\"mhod\")", charset="ISO-8859-1")
        yield UInt32(self, "header_length", "Header Length")
        yield UInt32(self, "entry_length", "Entry Length")
        yield Enum(UInt32(self, "type", "type"), self.type_name)
        if (self["type"].value == 15) or (self["type"].value == 16):
            yield UInt32(self, "unknown[]")
            yield UInt32(self, "unknown[]")
            yield String(self, "string", self._size // 8 - self["header_length"].value, "String Data", charset="UTF-8")
        elif (self["type"].value == 52):
            yield UInt32(self, "unknown[]", "unk1")
            yield UInt32(self, "unknown[]", "unk2")
            yield Enum(UInt32(self, "sort_index_type", "Sort Index Type"), self.mhod52_sort_index_type_name)
            yield UInt32(self, "entry_count", "Entry Count")
            indexes_size = self["entry_count"].value * 4
            padding_offset = self["entry_length"].value - indexes_size
            padding = self.seekByte(padding_offset, "header padding")
            if padding:
                yield padding
            for i in range(self["entry_count"].value):
                yield UInt32(self, "index[" + str(i) + "]", "Index of the " + str(i) + "nth mhit")
        elif (self["type"].value < 15) or (self["type"].value > 17) or (self["type"].value >= 200):
            yield UInt32(self, "unknown[]")
            yield UInt32(self, "unknown[]")
            yield UInt32(self, "position", "Position")
            yield UInt32(self, "length", "String Length in bytes")
            yield UInt32(self, "unknown[]")
            yield UInt32(self, "unknown[]")
            yield String(self, "string", self["length"].value, "String Data", charset="UTF-16-LE")
        else:
            padding = self.seekByte(
                self["header_length"].value, "header padding")
            if padding:
                yield padding
        padding = self.seekBit(self._size, "entry padding")
        if padding:
            yield padding


class TrackItem(FieldSet):
    x1_type_name = {
        0: "AAC or CBR MP3",
        1: "VBR MP3"
    }
    x2_type_name = {
        0: "AAC",
        1: "MP3"
    }
    media_type_name = {
        0x00: "Audio/Video",
        0x01: "Audio",
        0x02: "Video",
        0x04: "Podcast",
        0x06: "Video Podcast",
        0x08: "Audiobook",
        0x20: "Music Video",
        0x40: "TV Show",
        0X60: "TV Show (Music lists)",
    }

    def __init__(self, *args, **kw):
        FieldSet.__init__(self, *args, **kw)
        self._size = self["entry_length"].value * 8

    def createFields(self):
        yield String(self, "header_id", 4, "Track Item Header Markup (\"mhit\")", charset="ISO-8859-1")
        yield UInt32(self, "header_length", "Header Length")
        yield UInt32(self, "entry_length", "Entry Length")
        yield UInt32(self, "string_number", "Number of Strings")
        yield UInt32(self, "unique_id", "Unique ID")
        yield UInt32(self, "visible_tag", "Visible Tag")
        yield String(self, "file_type", 4, "File Type")
        yield Enum(UInt8(self, "x1_type", "Extended Type 1"), self.x1_type_name)
        yield Enum(UInt8(self, "x2_type", "Extended type 2"), self.x2_type_name)
        yield UInt8(self, "compilation_flag", "Compilation Flag")
        yield UInt8(self, "rating", "Rating")
        yield TimestampMac32(self, "last_modified", "Time of the last modification of the track")
        yield filesizeHandler(UInt32(self, "size", "Track size in bytes"))
        yield displayHandler(UInt32(self, "length", "Track length in milliseconds"), humanDuration)
        yield UInt32(self, "track_number", "Number of this track")
        yield UInt32(self, "total_track", "Total number of tracks")
        yield UInt32(self, "year", "Year of the track")
        yield UInt32(self, "bitrate", "Bitrate")
        yield UInt32(self, "samplerate", "Sample Rate")
        yield UInt32(self, "volume", "volume")
        yield UInt32(self, "start_time", "Start playing at, in milliseconds")
        yield UInt32(self, "stop_time", "Stop playing at,  in milliseconds")
        yield UInt32(self, "soundcheck", "SoundCheck preamp")
        yield UInt32(self, "playcount_1", "Play count of the track")
        yield UInt32(self, "playcount_2", "Play count of the track when last synced")
        yield TimestampMac32(self, "last_played_time", "Time the song was last played")
        yield UInt32(self, "disc_number", "disc number in multi disc sets")
        yield UInt32(self, "total_discs", "Total number of discs in the disc set")
        yield UInt32(self, "userid", "User ID in the DRM scheme")
        yield TimestampMac32(self, "added_date", "Date when the item was added")
        yield UInt32(self, "bookmark_time", "Bookmark time for AudioBook")
        yield UInt64(self, "dbid", "Unique DataBase ID for the song (identical in mhit and in mhii)")
        yield UInt8(self, "checked", "song is checked")
        yield UInt8(self, "application_rating", "Last Rating before change")
        yield UInt16(self, "BPM", "BPM of the track")
        yield UInt16(self, "artwork_count", "number of artworks for this item")
        yield UInt16(self, "unknown[]")
        yield UInt32(self, "artwork_size", "Total size of artworks in bytes")
        yield UInt32(self, "unknown[]")
        yield Float32(self, "sample_rate_2", "Sample Rate express in float")
        yield UInt32(self, "released_date", "Date of release in Music Store or in Podcast")
        yield UInt16(self, "unknown[]")
        yield UInt16(self, "explicit_flag[]", "Explicit flag")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "skip_count[]", "Skip Count")
        yield TimestampMac32(self, "last_skipped", "Date when the item was last skipped")
        yield UInt8(self, "has_artwork", "0x01 for track with artwork, 0x02 otherwise")
        yield UInt8(self, "skip_wen_shuffling", "Skip that track when shuffling")
        yield UInt8(self, "remember_playback_position", "Remember playback position")
        yield UInt8(self, "flag4", "Flag 4")
        yield UInt64(self, "dbid2", "Unique DataBase ID for the song (identical as above)")
        yield UInt8(self, "lyrics_flag", "Lyrics Flag")
        yield UInt8(self, "movie_file_flag", "Movie File Flag")
        yield UInt8(self, "played_mark", "Track has been played")
        yield UInt8(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "pregap[]", "Number of samples of silence before the song starts")
        yield UInt64(self, "sample_count", "Number of samples in the song (only for WAV and AAC files)")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "postgap[]", "Number of samples of silence at the end of the song")
        yield UInt32(self, "unknown[]")
        yield Enum(UInt32(self, "media_type", "Media Type for video iPod"), self.media_type_name)
        yield UInt32(self, "season_number", "Season Number")
        yield UInt32(self, "episode_number", "Episode Number")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "gapless_data[]", "The size in bytes from first Sync Frame until the 8th before the last frame.")
        yield UInt32(self, "unknown[]")
        yield UInt16(self, "gaplessTrackFlag[]", "1 if track has gapless data")
        yield UInt16(self, "gaplessAlbumFlag[]", "1 if track uses crossfading in iTunes")
        yield RawBytes(self, "unknown[]", 20)
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt16(self, "unknown[]")
        yield UInt16(self, "album_id[]", "Album ID (used to link tracks with MHIAs)")
        yield RawBytes(self, "unknown[]", 52)
        yield UInt32(self, "mhii_link[]", "Artwork ID (used to link tracks with MHIIs)")
        padding = self.seekByte(self["header_length"].value, "header padding")
        if padding:
            yield padding

        # while ((self.stream.readBytes(0, 4) == b'mhod') and
        # ((self.current_size//8) < self["entry_length"].value)):
        for i in range(self["string_number"].value):
            yield DataObject(self, "data[]")
        padding = self.seekBit(self._size, "entry padding")
        if padding:
            yield padding


class TrackList(FieldSet):

    def createFields(self):
        yield String(self, "header_id", 4, "Track List Header Markup (\"mhlt\")", charset="ISO-8859-1")
        yield UInt32(self, "header_length", "Header Length")
        yield UInt32(self, "track_number", "Number of Tracks")

        padding = self.seekByte(self["header_length"].value, "header padding")
        if padding:
            yield padding

        for i in range(self["track_number"].value):
            yield TrackItem(self, "track[]")


class PlaylistItem(FieldSet):

    def __init__(self, *args, **kw):
        FieldSet.__init__(self, *args, **kw)
        self._size = self["entry_length"].value * 8

    def createFields(self):
        yield String(self, "header_id", 4, "Playlist Item Header Markup (\"mhip\")", charset="ISO-8859-1")
        yield UInt32(self, "header_length", "Header Length")
        yield UInt32(self, "entry_length", "Entry Length")
        yield UInt32(self, "data_object_child_count", "Number of Child Data Objects")
        yield UInt32(self, "podcast_grouping_flag", "Podcast Grouping Flag")
        yield UInt32(self, "group_id", "Group ID")
        yield UInt32(self, "track_id", "Track ID")
        yield TimestampMac32(self, "timestamp", "Song Timestamp")
        yield UInt32(self, "podcast_grouping_ref", "Podcast Grouping Reference")
        padding = self.seekByte(self["header_length"].value, "header padding")
        if padding:
            yield padding

        for i in range(self["data_object_child_count"].value):
            yield DataObject(self, "mhod[]")


class Playlist(FieldSet):
    is_master_pl_name = {
        0: "Regular playlist",
        1: "Master playlist"
    }

    is_podcast_name = {
        0: "Normal Playlist List",
        1: "Podcast Playlist List"
    }

    list_sort_order_name = {
        1: "Manual Sort Order",
        2: "???",
        3: "Song Title",
        4: "Album",
        5: "Artist",
        6: "Bitrate",
        7: "Genre",
        8: "Kind",
        9: "Date Modified",
        10: "Track Number",
        11: "Size",
        12: "Time",
        13: "Year",
        14: "Sample Rate",
        15: "Comment",
        16: "Date Added",
        17: "Equalizer",
        18: "Composer",
        19: "???",
        20: "Play Count",
        21: "Last Played",
        22: "Disc Number",
        23: "My Rating",
        24: "Release Date",
        25: "BPM",
        26: "Grouping",
        27: "Category",
        28: "Description",
        29: "Show",
        30: "Season",
        31: "Episode Number"
    }

    def __init__(self, *args, **kw):
        FieldSet.__init__(self, *args, **kw)
        self._size = self["entry_length"].value * 8

    def createFields(self):
        yield String(self, "header_id", 4, "Playlist Header Markup (\"mhyp\")", charset="ISO-8859-1")
        yield UInt32(self, "header_length", "Header Length")
        yield UInt32(self, "entry_length", "Entry Length")
        yield UInt32(self, "data_object_child_count", "Number of Child Data Objects")
        yield UInt32(self, "playlist_count", "Number of Playlist Items")
        yield Enum(UInt8(self, "type", "Normal or master playlist?"), self.is_master_pl_name)
        yield UInt8(self, "XXX1", "XXX1")
        yield UInt8(self, "XXX2", "XXX2")
        yield UInt8(self, "XXX3", "XXX3")
        yield TimestampMac32(self, "creation_date", "Date when the playlist was created")
        yield UInt64(self, "playlistid", "Persistent Playlist ID")
        yield UInt32(self, "unk3", "unk3")
        yield UInt16(self, "string_mhod_count", "Number of string MHODs for this playlist")
        yield Enum(UInt16(self, "is_podcast", "Playlist or Podcast List?"), self.is_podcast_name)
        yield Enum(UInt32(self, "sort_order", "Playlist Sort Order"), self.list_sort_order_name)

        padding = self.seekByte(self["header_length"].value, "entry padding")
        if padding:
            yield padding

        for i in range(self["data_object_child_count"].value):
            yield DataObject(self, "mhod[]")

        for i in range(self["playlist_count"].value):
            yield PlaylistItem(self, "playlist_item[]")


class PlaylistList(FieldSet):

    def createFields(self):
        yield String(self, "header_id", 4, "Playlist List Header Markup (\"mhlp\")", charset="ISO-8859-1")
        yield UInt32(self, "header_length", "Header Length")
        yield UInt32(self, "playlist_number", "Number of Playlists")

        padding = self.seekByte(self["header_length"].value, "header padding")
        if padding:
            yield padding

        for i in range(self["playlist_number"].value):
            yield Playlist(self, "playlist[]")


class Album(FieldSet):

    def __init__(self, *args, **kw):
        FieldSet.__init__(self, *args, **kw)
        self._size = self["entry_length"].value * 8

    def createFields(self):
        yield String(self, "header_id", 4, "Album Item Header Markup (\"mhia\")", charset="ISO-8859-1")
        yield UInt32(self, "header_length", "Header Length")
        yield UInt32(self, "entry_length", "Entry Length")
        yield UInt32(self, "data_object_child_count", "Number of Child Data Objects")
        yield UInt16(self, "unknow[]")
        yield UInt16(self, "album_id[]", "Album ID")
        yield UInt32(self, "unknow[]")
        yield UInt32(self, "unknow[]")
        yield UInt32(self, "unknow[]")

        padding = self.seekByte(self["header_length"].value, "entry padding")
        if padding:
            yield padding

        for i in range(self["data_object_child_count"].value):
            yield DataObject(self, "mhod[]")


class AlbumList(FieldSet):

    def createFields(self):
        yield String(self, "header_id", 4, "Album List Header Markup (\"mhla\")", charset="ISO-8859-1")
        yield UInt32(self, "header_length", "Header Length")
        yield UInt32(self, "album_number", "Number of Albums")

        padding = self.seekByte(self["header_length"].value, "header padding")
        if padding:
            yield padding

        for i in range(self["album_number"].value):
            yield Album(self, "album[]")


class DataSet(FieldSet):
    type_name = {
        1: "Track List",
        2: "Play List",
        3: "Podcast List",
        4: "Album List"
    }

    def __init__(self, *args, **kw):
        FieldSet.__init__(self, *args, **kw)
        self._size = self["entry_length"].value * 8

    def createFields(self):
        yield String(self, "header_id", 4, "DataSet Header Markup (\"mhsd\")", charset="ISO-8859-1")
        yield UInt32(self, "header_length", "Header Length")
        yield UInt32(self, "entry_length", "Entry Length")
        yield Enum(UInt32(self, "type", "type"), self.type_name)
        padding = self.seekByte(self["header_length"].value, "header_raw")
        if padding:
            yield padding
        if self["type"].value == 1:
            yield TrackList(self, "tracklist[]")
        if self["type"].value == 2:
            yield PlaylistList(self, "playlist_list[]")
        if self["type"].value == 3:
            yield PlaylistList(self, "podcast_list[]")
        if self["type"].value == 4:
            yield AlbumList(self, "album_list[]")
        padding = self.seekBit(self._size, "entry padding")
        if padding:
            yield padding


class DataBase(FieldSet):

    def __init__(self, *args, **kw):
        FieldSet.__init__(self, *args, **kw)
        self._size = self["entry_length"].value * 8

#    def createFields(self):


class ITunesDBFile(Parser):
    PARSER_TAGS = {
        "id": "itunesdb",
        "category": "audio",
        "min_size": 44 * 8,
        "magic": ((b'mhbd', 0),),
        "description": "iPod iTunesDB file"
    }

    endian = LITTLE_ENDIAN

    def validate(self):
        return self.stream.readBytes(0, 4) == b'mhbd'

    def createFields(self):
        yield String(self, "header_id", 4, "DataBase Header Markup (\"mhbd\")", charset="ISO-8859-1")
        yield UInt32(self, "header_length", "Header Length")
        yield UInt32(self, "entry_length", "Entry Length")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "version_number", "Version Number")
        yield UInt32(self, "child_number", "Number of Children")
        yield UInt64(self, "id", "ID for this database")
        yield UInt16(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield UInt64(self, "unknown[]")
        yield UInt16(self, "unknown[]")
        yield UInt16(self, "hashing_scheme[]", "Algorithm used to calculate the database hash")
        yield NullBytes(self, "unknown[]", 20)
        yield String(self, "language_id", 2, "Language ID")
        yield UInt64(self, "persistent_id", "Library Persistent ID")
        yield UInt32(self, "unknown[]")
        yield UInt32(self, "unknown[]")
        yield RawBytes(self, "hash[]", 20)
        yield Int32(self, "timezone_offset[]", "Timezone offset in seconds")
        yield UInt16(self, "unknown[]")
        yield RawBytes(self, "iphone_hash[]", 45)
        size = self["header_length"].value - self.current_size / 8
        if size > 0:
            yield NullBytes(self, "padding", size)
        for i in range(self["child_number"].value):
            yield DataSet(self, "dataset[]")
        padding = self.seekByte(self["entry_length"].value, "entry padding")
        if padding:
            yield padding

    def createContentSize(self):
        return self["entry_length"].value * 8