///////////////////////////////////////////////////////////////////////////////
//
//  The contents of this file are subject to the Mozilla Public License
//  Version 1.1 (the "License"); you may not use this file except in
//  compliance with the License. You may obtain a copy of the License at
//  http://www.mozilla.org/MPL/
//
//  Software distributed under the License is distributed on an "AS IS"
//  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//  License for the specific language governing rights and limitations
//  under the License.
// 
//  The Original Code is MP4v2.
// 
//  The Initial Developer of the Original Code is Kona Blend.
//  Portions created by Kona Blend are Copyright (C) 2008.
//  Portions created by David Byron are Copyright (C) 2011.
//  All Rights Reserved.
//
//  Contributors:
//      Kona Blend, kona8lend@@gmail.com
//      Rouven Wessling, mp4v2@rouvenwessling.de
//      David Byron, dbyron@dbyron.com
//
///////////////////////////////////////////////////////////////////////////////

#include "impl.h"

namespace mp4v2 { namespace impl { namespace itmf {

///////////////////////////////////////////////////////////////////////////////

Tags::Tags()
    : hasMetadata(false)
{
}

///////////////////////////////////////////////////////////////////////////////

Tags::~Tags()
{
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_addArtwork( MP4Tags*& tags, MP4TagArtwork& c_artwork )
{
    artwork.resize( artwork.size() + 1 );
    c_setArtwork( tags, (uint32_t)artwork.size() - 1, c_artwork );
    updateArtworkShadow( tags );
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_alloc( MP4Tags*& tags )
{
    tags = new MP4Tags();
    memset( tags, 0, sizeof(MP4Tags) ); // safe: pure C-struct
    tags->__handle = this;
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_fetch( MP4Tags*& tags, MP4FileHandle hFile )
{
    MP4Tags& c = *tags;
    MP4File& file = *static_cast<MP4File*>(hFile);

    MP4ItmfItemList* itemList = genericGetItems( file ); // alloc

    hasMetadata = (itemList->size > 0);

    /* create code -> item map.
     * map will only be used for items which do not repeat; we do not care if
     * cover-art is inserted multiple times.
     */
    CodeItemMap cim;
    for( uint32_t i = 0; i < itemList->size; i++ ) {
        MP4ItmfItem& item = itemList->elements[i];
        cim.insert( CodeItemMap::value_type( item.code, &item ));
    }

    fetchString(  cim, CODE_NAME,              name,              c.name );
    fetchString(  cim, CODE_ARTIST,            artist,            c.artist );
    fetchString(  cim, CODE_ALBUMARTIST,       albumArtist,       c.albumArtist );
    fetchString(  cim, CODE_ALBUM,             album,             c.album );
    fetchString(  cim, CODE_GROUPING,          grouping,          c.grouping );
    fetchString(  cim, CODE_COMPOSER,          composer,          c.composer );
    fetchString(  cim, CODE_COMMENTS,          comments,          c.comments );

    fetchString(  cim, CODE_GENRE,             genre,             c.genre );
    fetchGenre(   cim,                         genreType,         c.genreType );

    fetchString(  cim, CODE_RELEASEDATE,       releaseDate,       c.releaseDate );
    fetchTrack(   cim,                         track,             c.track );
    fetchDisk(    cim,                         disk,              c.disk );
    fetchInteger( cim, CODE_TEMPO,             tempo,             c.tempo );
    fetchInteger( cim, CODE_COMPILATION,       compilation,       c.compilation );

    fetchString(  cim, CODE_TVSHOW,            tvShow,            c.tvShow );
    fetchString(  cim, CODE_TVNETWORK,         tvNetwork,         c.tvNetwork );
    fetchString(  cim, CODE_TVEPISODEID,       tvEpisodeID,       c.tvEpisodeID );
    fetchInteger( cim, CODE_TVSEASON,          tvSeason,          c.tvSeason );
    fetchInteger( cim, CODE_TVEPISODE,         tvEpisode,         c.tvEpisode );

    fetchString(  cim, CODE_SORTNAME,          sortName,          c.sortName );
    fetchString(  cim, CODE_SORTARTIST,        sortArtist,        c.sortArtist );
    fetchString(  cim, CODE_SORTALBUMARTIST,   sortAlbumArtist,   c.sortAlbumArtist );
    fetchString(  cim, CODE_SORTALBUM,         sortAlbum,         c.sortAlbum );
    fetchString(  cim, CODE_SORTCOMPOSER,      sortComposer,      c.sortComposer );
    fetchString(  cim, CODE_SORTTVSHOW,        sortTVShow,        c.sortTVShow );

    fetchString(  cim, CODE_DESCRIPTION,       description,       c.description );
    fetchString(  cim, CODE_LONGDESCRIPTION,   longDescription,   c.longDescription );
    fetchString(  cim, CODE_LYRICS,            lyrics,            c.lyrics );

    fetchString(  cim, CODE_COPYRIGHT,         copyright,         c.copyright );
    fetchString(  cim, CODE_ENCODINGTOOL,      encodingTool,      c.encodingTool ); 
    fetchString(  cim, CODE_ENCODEDBY,         encodedBy,         c.encodedBy );
    fetchString(  cim, CODE_PURCHASEDATE,      purchaseDate,      c.purchaseDate );

    fetchInteger( cim, CODE_PODCAST,           podcast,           c.podcast );
    fetchString(  cim, CODE_KEYWORDS,          keywords,          c.keywords );
    fetchString(  cim, CODE_CATEGORY,          category,          c.category );

    fetchInteger( cim, CODE_HDVIDEO,           hdVideo,           c.hdVideo );
    fetchInteger( cim, CODE_MEDIATYPE,         mediaType,         c.mediaType );
    fetchInteger( cim, CODE_CONTENTRATING,     contentRating,     c.contentRating );
    fetchInteger( cim, CODE_GAPLESS,           gapless,           c.gapless );

    fetchString(  cim, CODE_ITUNESACCOUNT,     iTunesAccount,     c.iTunesAccount );
    fetchInteger( cim, CODE_ITUNESACCOUNTTYPE, iTunesAccountType, c.iTunesAccountType );
    fetchInteger( cim, CODE_ITUNESCOUNTRY,     iTunesCountry,     c.iTunesCountry );

    fetchInteger( cim, CODE_CONTENTID,         contentID,         c.contentID );
    fetchInteger( cim, CODE_ARTISTID,          artistID,          c.artistID );
    fetchInteger( cim, CODE_PLAYLISTID,        playlistID,        c.playlistID );
    fetchInteger( cim, CODE_GENREID,           genreID,           c.genreID );
    fetchInteger( cim, CODE_COMPOSERID,        composerID,        c.composerID );
    fetchString(  cim, CODE_XID,               xid,               c.xid );

    genericItemListFree( itemList ); // free

    // fetch full list and overwrite our copy, otherwise clear
    {
        CoverArtBox::ItemList items;
        if( CoverArtBox::list( hFile, items ))
            artwork.clear();
        else
            artwork = items;

        updateArtworkShadow( tags );
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_free( MP4Tags*& tags )
{
    MP4Tags* c = const_cast<MP4Tags*>(tags);

    delete[] c->artwork;
    delete c;

    tags = NULL;
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_removeArtwork( MP4Tags*& tags, uint32_t index ) 
{
    if( !(index < artwork.size()) )
        return;

    artwork.erase( artwork.begin() + index );
    updateArtworkShadow( tags );
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_setArtwork( MP4Tags*& tags, uint32_t index, MP4TagArtwork& c_artwork )
{
    if( !(index < artwork.size()) )
        return;

    CoverArtBox::Item& item = artwork[index];

    switch( c_artwork.type ) {
        case MP4_ART_BMP:
            item.type = BT_BMP;
            break;

        case MP4_ART_GIF:
            item.type = BT_GIF;
            break;

        case MP4_ART_JPEG:
            item.type = BT_JPEG;
            break;

        case MP4_ART_PNG:
            item.type = BT_PNG;
            break;

        case MP4_ART_UNDEFINED:
        default:
            item.type = computeBasicType( c_artwork.data, c_artwork.size );
            break;
    }

    item.buffer   = (uint8_t*)malloc( c_artwork.size );
    item.size     = c_artwork.size;
    item.autofree = true;

    memcpy( item.buffer, c_artwork.data, c_artwork.size );
    updateArtworkShadow( tags );
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_setInteger( const uint8_t* value, uint8_t& cpp, const uint8_t*& c )
{
    if( !value ) {
        cpp = 0;
        c = NULL;
    }
    else {
        cpp = *value;
        c = &cpp;
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_setInteger( const uint16_t* value, uint16_t& cpp, const uint16_t*& c )
{
    if( !value ) {
        cpp = 0;
        c = NULL;
    }
    else {
        cpp = *value;
        c = &cpp;
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_setInteger( const uint32_t* value, uint32_t& cpp, const uint32_t*& c )
{
    if( !value ) {
        cpp = 0;
        c = NULL;
    }
    else {
        cpp = *value;
        c = &cpp;
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_setInteger( const uint64_t* value, uint64_t& cpp, const uint64_t*& c )
{
    if( !value ) {
        cpp = 0;
        c = NULL;
    }
    else {
        cpp = *value;
        c = &cpp;
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_setString( const char* value, string& cpp, const char*& c )
{
    if( !value ) {
        cpp.clear();
        c = NULL;
    }
    else {
        cpp = value;
        c = cpp.c_str();
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_setTrack( const MP4TagTrack* value, MP4TagTrack& cpp, const MP4TagTrack*& c )
{
    if( !value ) {
        cpp.index = 0;
        cpp.total = 0;
        c = NULL;
    }
    else {
        cpp.index = value->index;
        cpp.total = value->total;
        c = &cpp;
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_setDisk( const MP4TagDisk* value, MP4TagDisk& cpp, const MP4TagDisk*& c )
{
    if( !value ) {
        cpp.index = 0;
        cpp.total = 0;
        c = NULL;
    }
    else {
        cpp.index = value->index;
        cpp.total = value->total;
        c = &cpp;
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::c_store( MP4Tags*& tags, MP4FileHandle hFile )
{
    MP4Tags& c = *tags;
    MP4File& file = *static_cast<MP4File*>(hFile);
   
    storeString(  file, CODE_NAME,              name,              c.name );
    storeString(  file, CODE_ARTIST,            artist,            c.artist );
    storeString(  file, CODE_ALBUMARTIST,       albumArtist,       c.albumArtist );
    storeString(  file, CODE_ALBUM,             album,             c.album );
    storeString(  file, CODE_GROUPING,          grouping,          c.grouping );
    storeString(  file, CODE_COMPOSER,          composer,          c.composer );
    storeString(  file, CODE_COMMENTS,          comments,          c.comments );

    storeString(  file, CODE_GENRE,             genre,             c.genre );
    storeGenre(   file,                         genreType,         c.genreType );

    storeString(  file, CODE_RELEASEDATE,       releaseDate,       c.releaseDate );
    storeTrack(   file,                         track,             c.track );
    storeDisk(    file,                         disk,              c.disk );
    storeInteger( file, CODE_TEMPO,             tempo,             c.tempo );
    storeInteger( file, CODE_COMPILATION,       compilation,       c.compilation );
    
    storeString(  file, CODE_TVSHOW,            tvShow,            c.tvShow );
    storeString(  file, CODE_TVNETWORK,         tvNetwork,         c.tvNetwork );
    storeString(  file, CODE_TVEPISODEID,       tvEpisodeID,       c.tvEpisodeID );
    storeInteger( file, CODE_TVSEASON,          tvSeason,          c.tvSeason );
    storeInteger( file, CODE_TVEPISODE,         tvEpisode,         c.tvEpisode );
    
    storeString(  file, CODE_SORTNAME,          sortName,          c.sortName );
    storeString(  file, CODE_SORTARTIST,        sortArtist,        c.sortArtist );
    storeString(  file, CODE_SORTALBUMARTIST,   sortAlbumArtist,   c.sortAlbumArtist );
    storeString(  file, CODE_SORTALBUM,         sortAlbum,         c.sortAlbum );
    storeString(  file, CODE_SORTCOMPOSER,      sortComposer,      c.sortComposer );
    storeString(  file, CODE_SORTTVSHOW,        sortTVShow,        c.sortTVShow );

    storeString(  file, CODE_DESCRIPTION,       description,       c.description );
    storeString(  file, CODE_LONGDESCRIPTION,   longDescription,   c.longDescription );
    storeString(  file, CODE_LYRICS,            lyrics,            c.lyrics );

    storeString(  file, CODE_COPYRIGHT,         copyright,         c.copyright );
    storeString(  file, CODE_ENCODINGTOOL,      encodingTool,      c.encodingTool );
    storeString(  file, CODE_ENCODEDBY,         encodedBy,         c.encodedBy );
    storeString(  file, CODE_PURCHASEDATE,      purchaseDate,      c.purchaseDate );

    storeInteger( file, CODE_PODCAST,           podcast,           c.podcast );
    storeString(  file, CODE_KEYWORDS,          keywords,          c.keywords );
    storeString(  file, CODE_CATEGORY,          category,          c.category );

    storeInteger( file, CODE_HDVIDEO,           hdVideo,           c.hdVideo );
    storeInteger( file, CODE_MEDIATYPE,         mediaType,         c.mediaType );
    storeInteger( file, CODE_CONTENTRATING,     contentRating,     c.contentRating );
    storeInteger( file, CODE_GAPLESS,           gapless,           c.gapless );

    storeString(  file, CODE_ITUNESACCOUNT,     iTunesAccount,     c.iTunesAccount );
    storeInteger( file, CODE_ITUNESACCOUNTTYPE, iTunesAccountType, c.iTunesAccountType );
    storeInteger( file, CODE_ITUNESCOUNTRY,     iTunesCountry,     c.iTunesCountry );

    storeInteger( file, CODE_CONTENTID,         contentID,         c.contentID );
    storeInteger( file, CODE_ARTISTID,          artistID,          c.artistID );
    storeInteger( file, CODE_PLAYLISTID,        playlistID,        c.playlistID );
    storeInteger( file, CODE_GENREID,           genreID,           c.genreID );
    storeInteger( file, CODE_COMPOSERID,        composerID,        c.composerID );
    storeString(  file, CODE_XID,               xid,               c.xid );

    // destroy all cover-art then add each
    {
        CoverArtBox::remove( hFile );
        const CoverArtBox::ItemList::size_type max = artwork.size();
        for( CoverArtBox::ItemList::size_type i = 0; i < max; i++ )
            CoverArtBox::add( hFile, artwork[i] );
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::fetchGenre( const CodeItemMap& cim, uint16_t& cpp, const uint16_t*& c )
{
    cpp = 0;
    c = NULL;

    CodeItemMap::const_iterator f = cim.find( CODE_GENRETYPE );
    if( f == cim.end() || 0 == f->second->dataList.size )
        return;

    const MP4ItmfData& data = f->second->dataList.elements[0];
    if( NULL == data.value )
        return;

    cpp = (uint16_t(data.value[0]) <<  8)
        | (uint16_t(data.value[1])      );

    c = &cpp;
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::fetchDisk( const CodeItemMap& cim, MP4TagDisk& cpp, const MP4TagDisk*& c )
{    
    cpp.index = 0;
    cpp.total = 0;
    c = NULL;

    CodeItemMap::const_iterator f = cim.find( CODE_DISK );
    if( f == cim.end() || 0 == f->second->dataList.size )
        return;

    const MP4ItmfData& data = f->second->dataList.elements[0];

    if( NULL == data.value )
        return;

    cpp.index = (uint16_t(data.value[2]) <<  8)
              | (uint16_t(data.value[3])      );

    cpp.total = (uint16_t(data.value[4]) <<  8)
              | (uint16_t(data.value[5])      );

    c = &cpp;
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::fetchTrack( const CodeItemMap& cim, MP4TagTrack& cpp, const MP4TagTrack*& c )
{    
    cpp.index = 0;
    cpp.total = 0;
    c = NULL;

    CodeItemMap::const_iterator f = cim.find( CODE_TRACK );
    if( f == cim.end() || 0 == f->second->dataList.size )
        return;

    const MP4ItmfData& data = f->second->dataList.elements[0];

    if( NULL == data.value )
        return;

    cpp.index = (uint16_t(data.value[2]) <<  8)
              | (uint16_t(data.value[3])      );

    cpp.total = (uint16_t(data.value[4]) <<  8)
              | (uint16_t(data.value[5])      );

    c = &cpp;
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::fetchInteger( const CodeItemMap& cim, const string& code, uint8_t& cpp, const uint8_t*& c )
{
    cpp = 0;
    c = NULL;

    CodeItemMap::const_iterator f = cim.find( code );
    if( f == cim.end() || 0 == f->second->dataList.size )
        return;

    const MP4ItmfData& data = f->second->dataList.elements[0];
    if( NULL == data.value )
        return;

    cpp = data.value[0];
    c = &cpp;
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::fetchInteger( const CodeItemMap& cim, const string& code, uint16_t& cpp, const uint16_t*& c )
{
    cpp = 0;
    c = NULL;

    CodeItemMap::const_iterator f = cim.find( code );
    if( f == cim.end() || 0 == f->second->dataList.size )
        return;

    const MP4ItmfData& data = f->second->dataList.elements[0];

    if( NULL == data.value )
        return;

    cpp = (uint16_t(data.value[0]) <<  8)
        | (uint16_t(data.value[1])      );

    c = &cpp;
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::fetchInteger( const CodeItemMap& cim, const string& code, uint32_t& cpp, const uint32_t*& c )
{
    cpp = 0;
    c = NULL;

    CodeItemMap::const_iterator f = cim.find( code );
    if( f == cim.end() || 0 == f->second->dataList.size )
        return;

    const MP4ItmfData& data = f->second->dataList.elements[0];

    if( NULL == data.value )
        return;

    cpp = (uint32_t(data.value[0]) << 24)
        | (uint32_t(data.value[1]) << 16)
        | (uint32_t(data.value[2]) <<  8)
        | (uint32_t(data.value[3])      );

    c = &cpp;
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::fetchInteger( const CodeItemMap& cim, const string& code, uint64_t& cpp, const uint64_t*& c )
{
    cpp = 0;
    c = NULL;

    CodeItemMap::const_iterator f = cim.find( code );
    if( f == cim.end() || 0 == f->second->dataList.size )
        return;

    const MP4ItmfData& data = f->second->dataList.elements[0];

    if( NULL == data.value )
        return;

    cpp = (uint64_t(data.value[0]) << 56)
        | (uint64_t(data.value[1]) << 48)
        | (uint64_t(data.value[2]) << 40)
        | (uint64_t(data.value[3]) << 32)
        | (uint64_t(data.value[4]) << 24)
        | (uint64_t(data.value[5]) << 16)
        | (uint64_t(data.value[6]) <<  8)
        | (uint64_t(data.value[7])      );

    c = &cpp;
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::fetchString( const CodeItemMap& cim, const string& code, string& cpp, const char*& c )
{
    cpp.clear();
    c = NULL;

    CodeItemMap::const_iterator f = cim.find( code );
    if( f == cim.end() || 0 == f->second->dataList.size )
        return;

    const MP4ItmfData& data = f->second->dataList.elements[0];

    if( NULL == data.value )
        return;

    cpp.append( reinterpret_cast<char*>( data.value ), data.valueSize );
    c = cpp.c_str();
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::remove( MP4File& file, const string& code )
{
    MP4ItmfItemList* itemList = genericGetItemsByCode( file, code ); // alloc

    if( itemList->size )
        genericRemoveItem( file, &itemList->elements[0] );

    genericItemListFree( itemList ); // free
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::store( MP4File& file, const string& code, MP4ItmfBasicType basicType, const void* buffer, uint32_t size )
{
    // remove existing item
    remove( file, code );

    // add item
    MP4ItmfItem& item = *genericItemAlloc( code, 1 ); // alloc
    MP4ItmfData& data = item.dataList.elements[0];

    data.typeCode = basicType;
    data.valueSize = size;
    data.value = (uint8_t*)malloc( data.valueSize );
    memcpy( data.value, buffer, data.valueSize );

    genericAddItem( file, &item );
    genericItemFree( &item ); // free
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::storeGenre( MP4File& file, uint16_t cpp, const uint16_t* c )
{
    if( c ) {
        uint8_t buf[2];

        buf[0] = uint8_t((cpp & 0xff00) >> 8);
        buf[1] = uint8_t((cpp & 0x00ff)     );

        // it's not clear if you must use implicit in these situations and iirc iTunes and other software are not consistent in this regard.
        // many other tags must be integer type yet no issues there. Silly that iTunes insists it must be implict, which is then hardcoded 
        // to interpret as genres anyways.
        store( file, CODE_GENRETYPE, MP4_ITMF_BT_IMPLICIT, buf, sizeof(buf) );
    }
    else {
        remove( file, CODE_GENRETYPE );
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::storeDisk( MP4File& file, const MP4TagDisk& cpp, const MP4TagDisk* c )
{
    if( c ) {
        uint8_t buf[6];
        memset( buf, 0, sizeof(buf) );

        buf[2] = uint8_t((cpp.index & 0xff00) >> 8);
        buf[3] = uint8_t((cpp.index & 0x00ff)     );
        buf[4] = uint8_t((cpp.total & 0xff00) >> 8);
        buf[5] = uint8_t((cpp.total & 0x00ff)     );

        store( file, CODE_DISK, MP4_ITMF_BT_IMPLICIT, buf, sizeof(buf) );
    }
    else {
        remove( file, CODE_DISK );
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::storeTrack( MP4File& file, const MP4TagTrack& cpp, const MP4TagTrack* c )
{
    if( c ) {
        uint8_t buf[8]; // iTMF spec says 7 but iTunes media is 8
        memset( buf, 0, sizeof(buf) );

        buf[2] = uint8_t((cpp.index & 0xff00) >> 8);
        buf[3] = uint8_t((cpp.index & 0x00ff)     );
        buf[4] = uint8_t((cpp.total & 0xff00) >> 8);
        buf[5] = uint8_t((cpp.total & 0x00ff)     );

        store( file, CODE_TRACK, MP4_ITMF_BT_IMPLICIT, buf, sizeof(buf) );
    }
    else {
        remove( file, CODE_TRACK );
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::storeInteger( MP4File& file, const string& code, uint8_t cpp, const uint8_t* c )
{
    if( c )
        store( file, code, MP4_ITMF_BT_INTEGER, &cpp, sizeof(cpp) );
    else
        remove( file, code );
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::storeInteger( MP4File& file, const string& code, uint16_t cpp, const uint16_t* c )
{
    if( c ) {
        uint8_t buf[2];

        buf[0] = uint8_t((cpp & 0xff00) >> 8);
        buf[1] = uint8_t((cpp & 0x00ff)     );

        store( file, code, MP4_ITMF_BT_INTEGER, buf, sizeof(buf) );
    }
    else {
        remove( file, code );
    }
}


///////////////////////////////////////////////////////////////////////////////

void
Tags::storeInteger( MP4File& file, const string& code, uint32_t cpp, const uint32_t* c )
{
    if( c ) {
        uint8_t buf[4];

        buf[0] = uint8_t((cpp & 0xff000000) >> 24 );
        buf[1] = uint8_t((cpp & 0x00ff0000) >> 16 );
        buf[2] = uint8_t((cpp & 0x0000ff00) >>  8 );
        buf[3] = uint8_t((cpp & 0x000000ff)       );

        store( file, code, MP4_ITMF_BT_INTEGER, buf, sizeof(buf) );
    }
    else {
        remove( file, code );
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::storeInteger( MP4File& file, const string& code, uint64_t cpp, const uint64_t* c )
{
    if( c ) {
        uint8_t buf[8];

        buf[0] = uint8_t((cpp & 0xff00000000000000LL) >> 56 );
        buf[1] = uint8_t((cpp & 0x00ff000000000000LL) >> 48 );
        buf[2] = uint8_t((cpp & 0x0000ff0000000000LL) >> 40 );
        buf[3] = uint8_t((cpp & 0x000000ff00000000LL) >> 32 );
        buf[4] = uint8_t((cpp & 0x00000000ff000000LL) >> 24 );
        buf[5] = uint8_t((cpp & 0x0000000000ff0000LL) >> 16 );
        buf[6] = uint8_t((cpp & 0x000000000000ff00LL) >>  8 );
        buf[7] = uint8_t((cpp & 0x00000000000000ffLL)       );

        store( file, code, MP4_ITMF_BT_INTEGER, buf, sizeof(buf) );
    }
    else {
        remove( file, code );
    }
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::storeString( MP4File& file, const string& code, const string& cpp, const char* c )
{
    if( c )
        store( file, code, MP4_ITMF_BT_UTF8, cpp.c_str(), (uint32_t)cpp.size() );
    else
        remove( file, code );
}

///////////////////////////////////////////////////////////////////////////////

void
Tags::updateArtworkShadow( MP4Tags*& tags )
{
    if( tags->artwork ) {
        delete[] tags->artwork;
        tags->artwork = NULL;
        tags->artworkCount = 0;
    }

    if( artwork.empty() )
        return;

    MP4TagArtwork* const cartwork = new MP4TagArtwork[ artwork.size() ];
    uint32_t max = (uint32_t)artwork.size();

    for( uint32_t i = 0; i < max; i++ ) {
        MP4TagArtwork& a = cartwork[i];
        CoverArtBox::Item& item = artwork[i];

        a.data = item.buffer;
        a.size = item.size;

        switch( item.type ) {
            case BT_BMP:
                a.type = MP4_ART_BMP;
                break;

            case BT_GIF:
                a.type = MP4_ART_GIF;
                break;

            case BT_JPEG:
                a.type = MP4_ART_JPEG;
                break;

            case BT_PNG:
                a.type = MP4_ART_PNG;
                break;

            default:
                a.type = MP4_ART_UNDEFINED;
                break;
        }
    }

    tags->artwork      = cartwork;
    tags->artworkCount = max;
}

///////////////////////////////////////////////////////////////////////////////

const string Tags::CODE_NAME              = "\xa9" "nam";
const string Tags::CODE_ARTIST            = "\xa9" "ART";
const string Tags::CODE_ALBUMARTIST       = "aART";
const string Tags::CODE_ALBUM             = "\xa9" "alb";
const string Tags::CODE_GROUPING          = "\xa9" "grp";
const string Tags::CODE_COMPOSER          = "\xa9" "wrt";
const string Tags::CODE_COMMENTS          = "\xa9" "cmt";
const string Tags::CODE_GENRE             = "\xa9" "gen";
const string Tags::CODE_GENRETYPE         = "gnre";
const string Tags::CODE_RELEASEDATE       = "\xa9" "day";
const string Tags::CODE_TRACK             = "trkn";
const string Tags::CODE_DISK              = "disk";
const string Tags::CODE_TEMPO             = "tmpo";
const string Tags::CODE_COMPILATION       = "cpil";

const string Tags::CODE_TVSHOW            = "tvsh";
const string Tags::CODE_TVNETWORK         = "tvnn";
const string Tags::CODE_TVEPISODEID       = "tven";
const string Tags::CODE_TVSEASON          = "tvsn";
const string Tags::CODE_TVEPISODE         = "tves";

const string Tags::CODE_DESCRIPTION       = "desc";
const string Tags::CODE_LONGDESCRIPTION   = "ldes";
const string Tags::CODE_LYRICS            = "\xa9" "lyr";

const string Tags::CODE_SORTNAME          = "sonm";
const string Tags::CODE_SORTARTIST        = "soar";
const string Tags::CODE_SORTALBUMARTIST   = "soaa";
const string Tags::CODE_SORTALBUM         = "soal";
const string Tags::CODE_SORTCOMPOSER      = "soco";
const string Tags::CODE_SORTTVSHOW        = "sosn";

const string Tags::CODE_COPYRIGHT         = "cprt";
const string Tags::CODE_ENCODINGTOOL      = "\xa9" "too";
const string Tags::CODE_ENCODEDBY         = "\xa9" "enc";
const string Tags::CODE_PURCHASEDATE      = "purd";

const string Tags::CODE_PODCAST           = "pcst";
const string Tags::CODE_KEYWORDS          = "keyw";
const string Tags::CODE_CATEGORY          = "catg";

const string Tags::CODE_HDVIDEO           = "hdvd";
const string Tags::CODE_MEDIATYPE         = "stik";
const string Tags::CODE_CONTENTRATING     = "rtng";
const string Tags::CODE_GAPLESS           = "pgap";

const string Tags::CODE_ITUNESACCOUNT     = "apID";
const string Tags::CODE_ITUNESACCOUNTTYPE = "akID";
const string Tags::CODE_ITUNESCOUNTRY     = "sfID";
const string Tags::CODE_CONTENTID         = "cnID";
const string Tags::CODE_ARTISTID          = "atID";
const string Tags::CODE_PLAYLISTID        = "plID";
const string Tags::CODE_GENREID           = "geID";
const string Tags::CODE_COMPOSERID        = "cmID";
const string Tags::CODE_XID               = "xid ";

///////////////////////////////////////////////////////////////////////////////

}}} // namespace mp4v2::impl::itmf
