/*****************************************************************
|
|    AP4 - MetaData 
|
|    Copyright 2002-2008 Axiomatic Systems, LLC
|
|
|    This file is part of Bento4/AP4 (MP4 Atom Processing Library).
|
|    Unless you have obtained Bento4 under a difference license,
|    this version of Bento4 is Bento4|GPL.
|    Bento4|GPL is free software; you can redistribute it and/or modify
|    it under the terms of the GNU General Public License as published by
|    the Free Software Foundation; either version 2, or (at your option)
|    any later version.
|
|    Bento4|GPL is distributed in the hope that it will be useful,
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
|    GNU General Public License for more details.
|
|    You should have received a copy of the GNU General Public License
|    along with Bento4|GPL; see the file COPYING.  If not, write to the
|    Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|    02111-1307, USA.
|
****************************************************************/

/*----------------------------------------------------------------------
|   includes
+---------------------------------------------------------------------*/
#include "Ap4File.h"
#include "Ap4Movie.h"
#include "Ap4MetaData.h"
#include "Ap4ContainerAtom.h"
#include "Ap4MoovAtom.h"
#include "Ap4HdlrAtom.h"
#include "Ap4DataBuffer.h"
#include "Ap4Utils.h"
#include "Ap4String.h"

/*----------------------------------------------------------------------
|   dynamic cast support
+---------------------------------------------------------------------*/
AP4_DEFINE_DYNAMIC_CAST_ANCHOR(AP4_3GppLocalizedStringAtom)
AP4_DEFINE_DYNAMIC_CAST_ANCHOR(AP4_DcfdAtom)
AP4_DEFINE_DYNAMIC_CAST_ANCHOR(AP4_DcfStringAtom)
AP4_DEFINE_DYNAMIC_CAST_ANCHOR(AP4_DataAtom)
AP4_DEFINE_DYNAMIC_CAST_ANCHOR(AP4_MetaDataStringAtom)

/*----------------------------------------------------------------------
|   metadata keys
+---------------------------------------------------------------------*/
static const AP4_MetaData::KeyInfo AP4_MetaData_KeyInfos [] = {
    {"Name",                "Name",                 AP4_ATOM_TYPE_cNAM, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Artist",              "Artist",               AP4_ATOM_TYPE_cART, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"AlbumArtist",         "Album Artist",         AP4_ATOM_TYPE_aART, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Composer",            "Composer",             AP4_ATOM_TYPE_cCOM, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Writer",              "Writer",               AP4_ATOM_TYPE_cWRT, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Album",               "Album",                AP4_ATOM_TYPE_cALB, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"GenreCode",           "Genre",                AP4_ATOM_TYPE_GNRE, AP4_MetaData::Value::TYPE_BINARY},
    {"GenreName",           "Genre",                AP4_ATOM_TYPE_cGEN, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Grouping",            "Grouping",             AP4_ATOM_TYPE_cGRP, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Date",                "Date",                 AP4_ATOM_TYPE_cDAY, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Tool",                "Encoding Tool",        AP4_ATOM_TYPE_cTOO, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Comment",             "Comment",              AP4_ATOM_TYPE_cCMT, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Lyrics",              "Lyrics",               AP4_ATOM_TYPE_cLYR, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Copyright",           "Copyright",            AP4_ATOM_TYPE_CPRT, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Track",               "Track Number",         AP4_ATOM_TYPE_TRKN, AP4_MetaData::Value::TYPE_BINARY},
    {"Disc",                "Disc Number",          AP4_ATOM_TYPE_DISK, AP4_MetaData::Value::TYPE_BINARY},
    {"Cover",               "Cover Art",            AP4_ATOM_TYPE_COVR, AP4_MetaData::Value::TYPE_BINARY},
    {"Description",         "Description",          AP4_ATOM_TYPE_DESC, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Rating",              "Rating",               AP4_ATOM_TYPE_RTNG, AP4_MetaData::Value::TYPE_INT_08_BE},
    {"Tempo",               "Tempo",                AP4_ATOM_TYPE_TMPO, AP4_MetaData::Value::TYPE_INT_16_BE},
    {"Compilation",         "Compilation",          AP4_ATOM_TYPE_CPIL, AP4_MetaData::Value::TYPE_INT_08_BE},
    {"IsGapless",           "Is Gapless",           AP4_ATOM_TYPE_PGAP, AP4_MetaData::Value::TYPE_INT_08_BE},
    {"Title",               "Title",                AP4_ATOM_TYPE_TITL, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Description",         "Description",          AP4_ATOM_TYPE_DSCP, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"StoreFrontID",        "Store Front ID",       AP4_ATOM_TYPE_sfID, AP4_MetaData::Value::TYPE_INT_32_BE},
    {"FileKind",            "File Kind",            AP4_ATOM_TYPE_STIK, AP4_MetaData::Value::TYPE_INT_08_BE},
    {"ShowName",            "Show Name",            AP4_ATOM_TYPE_TVSH, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"ShowSeason",          "Show Season Number",   AP4_ATOM_TYPE_TVSN, AP4_MetaData::Value::TYPE_INT_32_BE},
    {"ShowEpisodeNumber",   "Show Episode Number",  AP4_ATOM_TYPE_TVES, AP4_MetaData::Value::TYPE_INT_32_BE},
    {"ShowEpisodeName",     "Show Episode Name",    AP4_ATOM_TYPE_TVEN, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"TVNetworkName",       "TV Network Name",      AP4_ATOM_TYPE_TVNN, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"IsPodcast",           "Is a Podcast",         AP4_ATOM_TYPE_PCST, AP4_MetaData::Value::TYPE_INT_08_BE},
    {"PodcastUrl",          "Podcast URL",          AP4_ATOM_TYPE_PURL, AP4_MetaData::Value::TYPE_BINARY},
    {"PodcastGuid",         "Podcast GUID",         AP4_ATOM_TYPE_EGID, AP4_MetaData::Value::TYPE_BINARY},
    {"PodcastCategory",     "Podcast Category",     AP4_ATOM_TYPE_CATG, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Keywords",            "Keywords",             AP4_ATOM_TYPE_KEYW, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"PurchaseDate",        "Purchase Date",        AP4_ATOM_TYPE_PURD, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"IconUri",             "Icon URI",             AP4_ATOM_TYPE_ICNU, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"InfoUrl",             "Info URL",             AP4_ATOM_TYPE_INFU, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"CoverUri",            "Cover Art URI",        AP4_ATOM_TYPE_CVRU, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"LyricsUri",           "Lyrics URI",           AP4_ATOM_TYPE_LRCU, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Duration",            "Duration",             AP4_ATOM_TYPE_DCFD, AP4_MetaData::Value::TYPE_INT_32_BE},
    {"Performer",           "Performer",            AP4_ATOM_TYPE_PERF, AP4_MetaData::Value::TYPE_STRING_UTF_8},
    {"Author",              "Author",               AP4_ATOM_TYPE_AUTH, AP4_MetaData::Value::TYPE_STRING_UTF_8},
};
AP4_Array<AP4_MetaData::KeyInfo> AP4_MetaData::KeyInfos(
    AP4_MetaData_KeyInfos, 
    sizeof(AP4_MetaData_KeyInfos)/sizeof(KeyInfo));
AP4_Result
AP4_MetaData::Initialized() { return AP4_MetaData::KeyInfos.ItemCount() != 0; }
AP4_Result
AP4_MetaData::Initialize() {
    unsigned int item_count = sizeof(AP4_MetaData_KeyInfos)/sizeof(KeyInfo);
    KeyInfos.SetItemCount(item_count);
    for (unsigned int i=0; i<item_count; i++) {
        KeyInfos[i] = AP4_MetaData_KeyInfos[i];
    }
    return AP4_SUCCESS;
}
AP4_Result
AP4_MetaData::UnInitialize() {
    return AP4_MetaData::KeyInfos.Clear();
}

/*----------------------------------------------------------------------
|   genre IDs
+---------------------------------------------------------------------*/
static const char* const Ap4Id3Genres[] = 
{
    "Blues",
    "Classic Rock",
    "Country",
    "Dance",
    "Disco",
    "Funk",
    "Grunge",
    "Hip-Hop",
    "Jazz",
    "Metal",
    "New Age",
    "Oldies",
    "Other",
    "Pop",
    "R&B",
    "Rap",
    "Reggae",
    "Rock",
    "Techno",
    "Industrial",
    "Alternative",
    "Ska",
    "Death Metal",
    "Pranks",
    "Soundtrack",
    "Euro-Techno",
    "Ambient",
    "Trip-Hop",
    "Vocal",
    "Jazz+Funk",
    "Fusion",
    "Trance",
    "Classical",
    "Instrumental",
    "Acid",
    "House",
    "Game",
    "Sound Clip",
    "Gospel",
    "Noise",
    "AlternRock",
    "Bass",
    "Soul",
    "Punk",
    "Space",
    "Meditative",
    "Instrumental Pop",
    "Instrumental Rock",
    "Ethnic",
    "Gothic",
    "Darkwave",
    "Techno-Industrial",
    "Electronic",
    "Pop-Folk",
    "Eurodance",
    "Dream",
    "Southern Rock",
    "Comedy",
    "Cult",
    "Gangsta",
    "Top 40",
    "Christian Rap",
    "Pop/Funk",
    "Jungle",
    "Native American",
    "Cabaret",
    "New Wave",
    "Psychadelic",
    "Rave",
    "Showtunes",
    "Trailer",
    "Lo-Fi",
    "Tribal",
    "Acid Punk",
    "Acid Jazz",
    "Polka",
    "Retro",
    "Musical",
    "Rock & Roll",
    "Hard Rock",
    "Folk",
    "Folk-Rock",
    "National Folk",
    "Swing",
    "Fast Fusion",
    "Bebob",
    "Latin",
    "Revival",
    "Celtic",
    "Bluegrass",
    "Avantgarde",
    "Gothic Rock",
    "Progressive Rock",
    "Psychedelic Rock",
    "Symphonic Rock",
    "Slow Rock",
    "Big Band",
    "Chorus",
    "Easy Listening",
    "Acoustic",
    "Humour",
    "Speech",
    "Chanson",
    "Opera",
    "Chamber Music",
    "Sonata",
    "Symphony",
    "Booty Bass",
    "Primus",
    "Porn Groove",
    "Satire",
    "Slow Jam",
    "Club",
    "Tango",
    "Samba",
    "Folklore",
    "Ballad",
    "Power Ballad",
    "Rhythmic Soul",
    "Freestyle",
    "Duet",
    "Punk Rock",
    "Drum Solo",
    "Acapella",
    "Euro-House",
    "Dance Hall"
};

static const char*
Ap4StikNames[] = {
    "Movie",            // 0
    "Normal",           // 1
    "Audiobook",        // 2
    "?",                // 3
    "?",                // 4
    "Whacked Bookmark", // 5
    "Music Video",      // 6
    "?",                // 7
    "?",                // 8
    "Short Film",       // 9 
    "TV Show",          // 10
    "Booklet",          // 11
    "?",                // 12
    "?",                // 13
    "Ring Tone"         // 14
};


/* sfID Store Front country
    Australia   => 143460,
    Austria     => 143445,
    Belgium     => 143446,
    Canada      => 143455,
    Denmark     => 143458,
    Finland     => 143447,
    France      => 143442,
    Germany     => 143443,
    Greece      => 143448,
    Ireland     => 143449,
    Italy       => 143450,
    Japan       => 143462,
    Luxembourg  => 143451,
    Netherlands => 143452,
    Norway      => 143457,
    Portugal    => 143453,
    Spain       => 143454,
    Sweden      => 143456,
    Switzerland => 143459,
    UK          => 143444,
    USA         => 143441,
*/

/*----------------------------------------------------------------------
|   constants
+---------------------------------------------------------------------*/
const AP4_Size AP4_DATA_ATOM_MAX_SIZE = 0x40000000;

/*----------------------------------------------------------------------
|   3GPP localized string atoms
+---------------------------------------------------------------------*/
const AP4_Atom::Type AP4_MetaDataAtomTypeHandler::_3gppLocalizedStringTypes[] = {
    AP4_ATOM_TYPE_TITL,
    AP4_ATOM_TYPE_DSCP,
    AP4_ATOM_TYPE_CPRT,
    AP4_ATOM_TYPE_PERF,
    AP4_ATOM_TYPE_AUTH,
    AP4_ATOM_TYPE_GNRE
};
const AP4_MetaDataAtomTypeHandler::TypeList AP4_MetaDataAtomTypeHandler::_3gppLocalizedStringTypeList = {
    _3gppLocalizedStringTypes,
    sizeof(_3gppLocalizedStringTypes)/sizeof(_3gppLocalizedStringTypes[0])
};

/*----------------------------------------------------------------------
|   other 3GPP atoms
+---------------------------------------------------------------------*/
const AP4_Atom::Type AP4_MetaDataAtomTypeHandler::_3gppOtherTypes[] = {
    AP4_ATOM_TYPE_RTNG,
    AP4_ATOM_TYPE_CLSF,
    AP4_ATOM_TYPE_KYWD,
    AP4_ATOM_TYPE_LOCI,
    AP4_ATOM_TYPE_ALBM,
    AP4_ATOM_TYPE_YRRC,
};
const AP4_MetaDataAtomTypeHandler::TypeList AP4_MetaDataAtomTypeHandler::_3gppOtherTypeList = {
    _3gppOtherTypes,
    sizeof(_3gppOtherTypes)/sizeof(_3gppOtherTypes[0])
};

/*----------------------------------------------------------------------
|   DCF string atoms
+---------------------------------------------------------------------*/
const AP4_Atom::Type AP4_MetaDataAtomTypeHandler::DcfStringTypes[] = {
    AP4_ATOM_TYPE_ICNU,
    AP4_ATOM_TYPE_INFU,
    AP4_ATOM_TYPE_CVRU,
    AP4_ATOM_TYPE_LRCU
};
const AP4_MetaDataAtomTypeHandler::TypeList AP4_MetaDataAtomTypeHandler::DcfStringTypeList = {
    DcfStringTypes,
    sizeof(DcfStringTypes)/sizeof(DcfStringTypes[0])
};

/*----------------------------------------------------------------------
|   atom type lists
+---------------------------------------------------------------------*/
const AP4_Atom::Type AP4_MetaDataAtomTypeHandler::IlstTypes[] = 
{
    AP4_ATOM_TYPE_dddd,
    AP4_ATOM_TYPE_cNAM,
    AP4_ATOM_TYPE_cART,
    AP4_ATOM_TYPE_cCOM,
    AP4_ATOM_TYPE_cWRT,
    AP4_ATOM_TYPE_cALB,
    AP4_ATOM_TYPE_cGEN,
    AP4_ATOM_TYPE_cGRP,
    AP4_ATOM_TYPE_cDAY,
    AP4_ATOM_TYPE_cTOO,
    AP4_ATOM_TYPE_cCMT,
    AP4_ATOM_TYPE_CPRT,
    AP4_ATOM_TYPE_TRKN,
    AP4_ATOM_TYPE_DISK,
    AP4_ATOM_TYPE_COVR,
    AP4_ATOM_TYPE_DESC,
    AP4_ATOM_TYPE_GNRE,
    AP4_ATOM_TYPE_CPIL,
    AP4_ATOM_TYPE_TMPO,
    AP4_ATOM_TYPE_RTNG,
    AP4_ATOM_TYPE_apID,
    AP4_ATOM_TYPE_cnID,
    AP4_ATOM_TYPE_cmID,
    AP4_ATOM_TYPE_atID,
    AP4_ATOM_TYPE_plID,
    AP4_ATOM_TYPE_geID,
    AP4_ATOM_TYPE_sfID,
    AP4_ATOM_TYPE_akID,
    AP4_ATOM_TYPE_aART,
    AP4_ATOM_TYPE_TVNN,
    AP4_ATOM_TYPE_TVSH,
    AP4_ATOM_TYPE_TVEN,
    AP4_ATOM_TYPE_TVSN,
    AP4_ATOM_TYPE_TVES,
    AP4_ATOM_TYPE_STIK,
    AP4_ATOM_TYPE_PGAP,
    AP4_ATOM_TYPE_PCST,
    AP4_ATOM_TYPE_PURD,
    AP4_ATOM_TYPE_PURL,
    AP4_ATOM_TYPE_EGID,
    AP4_ATOM_TYPE_SONM,
    AP4_ATOM_TYPE_SOAL,
    AP4_ATOM_TYPE_SOAR,
    AP4_ATOM_TYPE_SOAA,
    AP4_ATOM_TYPE_SOCO,
    AP4_ATOM_TYPE_SOSN
};
const AP4_MetaDataAtomTypeHandler::TypeList AP4_MetaDataAtomTypeHandler::IlstTypeList = {
    IlstTypes,
    sizeof(IlstTypes)/sizeof(IlstTypes[0])
};

/*----------------------------------------------------------------------
|   AP4_MetaDataAtomTypeHandler::CreateAtom
+---------------------------------------------------------------------*/
AP4_Result 
AP4_MetaDataAtomTypeHandler::CreateAtom(AP4_Atom::Type  type,
                                        AP4_UI32        size,
                                        AP4_ByteStream& stream,
                                        AP4_Atom::Type  context,
                                        AP4_Atom*&      atom)
{
    atom = NULL;

    if (context == AP4_ATOM_TYPE_ILST) {
        if (IsTypeInList(type, IlstTypeList)) {
            m_AtomFactory->PushContext(type);
            atom = AP4_ContainerAtom::Create(type, size, false, false, stream, *m_AtomFactory);
            m_AtomFactory->PopContext();
        }
    } else if (type == AP4_ATOM_TYPE_DATA) {
        if (IsTypeInList(context, IlstTypeList)) {
            atom = new AP4_DataAtom(size, stream);
        }
    } else if (context == AP4_ATOM_TYPE_dddd) {
        if (type == AP4_ATOM_TYPE_MEAN || type == AP4_ATOM_TYPE_NAME) {
            atom = new AP4_MetaDataStringAtom(type, size, stream);
        }
    } else if (context == AP4_ATOM_TYPE_UDTA) {
        if (IsTypeInList(type, _3gppLocalizedStringTypeList)) {
            atom = AP4_3GppLocalizedStringAtom::Create(type, size, stream);
        } else if (IsTypeInList(type, DcfStringTypeList)) {
            atom = AP4_DcfStringAtom::Create(type, size, stream);
        } else if (type == AP4_ATOM_TYPE_DCFD) {
            atom = AP4_DcfdAtom::Create(size, stream);
        }
    }

    return atom?AP4_SUCCESS:AP4_FAILURE;
}

/*----------------------------------------------------------------------
|   AP4_MetaDataAtomTypeHandler::IsTypeInList
+---------------------------------------------------------------------*/
bool
AP4_MetaDataAtomTypeHandler::IsTypeInList(AP4_Atom::Type type, const AP4_MetaDataAtomTypeHandler::TypeList& list)
{
    for (unsigned int i=0; i<list.m_Size; i++) {
        if (type == list.m_Types[i]) return true;
    }
    return false;
}

/*----------------------------------------------------------------------
|   AP4_MetaData::AP4_MetaData
+---------------------------------------------------------------------*/
AP4_MetaData::AP4_MetaData(AP4_File* file)
{
    // get the file's movie
    AP4_Movie* movie = file->GetMovie();

    // handle the movie's metadata if there is a movie in the file
    if (movie) {
        AP4_MoovAtom* moov = movie->GetMoovAtom();
        if (moov == NULL) return;
        ParseMoov(moov);
        AP4_Atom* udta = moov->GetChild(AP4_ATOM_TYPE_UDTA);
        if (udta) {
            AP4_ContainerAtom* udta_container = AP4_DYNAMIC_CAST(AP4_ContainerAtom, udta);
            if (udta_container) {
                ParseUdta(udta_container, "3gpp");
            }
        }
    } else {
        // if we don't have a movie, try to show metadata from a udta atom
        AP4_List<AP4_Atom>& top_level_atoms = file->GetTopLevelAtoms();
        
        AP4_List<AP4_Atom>::Item* atom_item = top_level_atoms.FirstItem();
        while (atom_item) {
            AP4_ContainerAtom* container = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom_item->GetData());
            if (container) {
                // look for a udta in a DCF layout
                AP4_Atom* udta = container->FindChild("odhe/udta");
                if (udta) {
                    AP4_ContainerAtom* udta_container = AP4_DYNAMIC_CAST(AP4_ContainerAtom, udta);
                    if (udta_container) {
                        ParseUdta(udta_container, "dcf");
                    }
                }
            }
            atom_item = atom_item->GetNext();
        }
    } 
}

/*----------------------------------------------------------------------
|   AP4_MetaData::ParseMoov
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::ParseMoov(AP4_MoovAtom* moov)
{
    // look for a 'meta' atom with 'hdlr' type 'mdir'
    AP4_HdlrAtom* hdlr = AP4_DYNAMIC_CAST(AP4_HdlrAtom, moov->FindChild("udta/meta/hdlr"));
    if (hdlr == NULL || hdlr->GetHandlerType() != AP4_HANDLER_TYPE_MDIR) return AP4_ERROR_NO_SUCH_ITEM;

    // get the list of entries
    AP4_ContainerAtom* ilst = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moov->FindChild("udta/meta/ilst"));
    if (ilst == NULL) return AP4_ERROR_NO_SUCH_ITEM;
    
    AP4_List<AP4_Atom>::Item* ilst_item = ilst->GetChildren().FirstItem();
    while (ilst_item) {
        AP4_ContainerAtom* entry_atom = AP4_DYNAMIC_CAST(AP4_ContainerAtom, ilst_item->GetData()); 
        if (entry_atom) {
            AddIlstEntries(entry_atom, "meta");
        }
        ilst_item = ilst_item->GetNext();
    }
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MetaData::ParseUdta
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::ParseUdta(AP4_ContainerAtom* udta, const char* namespc)
{
    // check that the atom is indeed a 'udta' atom
    if (udta->GetType() != AP4_ATOM_TYPE_UDTA) {
        return AP4_ERROR_INVALID_PARAMETERS;
    }
    
    AP4_List<AP4_Atom>::Item* udta_item = udta->GetChildren().FirstItem();
    for (; udta_item; udta_item = udta_item->GetNext()) {
        AP4_3GppLocalizedStringAtom* _3gpp_atom = AP4_DYNAMIC_CAST(AP4_3GppLocalizedStringAtom, udta_item->GetData()); 
        if (_3gpp_atom) {
            Add3GppEntry(_3gpp_atom, namespc);
            continue;
        } 
        
        AP4_DcfStringAtom* dcfs_atom = AP4_DYNAMIC_CAST(AP4_DcfStringAtom, udta_item->GetData());
        if (dcfs_atom) {
            AddDcfStringEntry(dcfs_atom, namespc);
            continue;
        } 

        AP4_DcfdAtom* dcfd_atom = AP4_DYNAMIC_CAST(AP4_DcfdAtom, udta_item->GetData());
        if (dcfd_atom) {
            AddDcfdEntry(dcfd_atom, namespc);
        }
    }
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MetaData::~AP4_MetaData
+---------------------------------------------------------------------*/
AP4_MetaData::~AP4_MetaData()
{
    m_Entries.DeleteReferences();
}

/*----------------------------------------------------------------------
|   AP4_MetaData::ResolveKeyName
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::ResolveKeyName(AP4_Atom::Type atom_type, AP4_String& value)
{
    const char* key_name = NULL;
    char        four_cc[5];

    // look for a match in the key infos
    for (unsigned int i=0; 
         i<sizeof(AP4_MetaData_KeyInfos)/sizeof(AP4_MetaData_KeyInfos[0]); 
         i++) {
        if (AP4_MetaData_KeyInfos[i].four_cc == atom_type) {
            key_name = AP4_MetaData_KeyInfos[i].name;
            break;
        }
    }
    if (key_name == NULL) {
        // this key was not found in the key infos, create a name for it
        AP4_FormatFourChars(four_cc, (AP4_UI32)atom_type);
        key_name = four_cc;
    }
    value = key_name;
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MetaData::AddIlstEntries
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::AddIlstEntries(AP4_ContainerAtom* atom, const char* namespc)
{
    AP4_MetaData::Value* value = NULL;

    if (atom->GetType() == AP4_ATOM_TYPE_dddd) {
        // look for the namespace
        AP4_MetaDataStringAtom* mean = static_cast<AP4_MetaDataStringAtom*>(atom->GetChild(AP4_ATOM_TYPE_MEAN));
        if (mean == NULL) return AP4_ERROR_INVALID_FORMAT;

        // look for the name
        AP4_MetaDataStringAtom* name = static_cast<AP4_MetaDataStringAtom*>(atom->GetChild(AP4_ATOM_TYPE_NAME));
        if (name == NULL) return AP4_ERROR_INVALID_FORMAT;

        // get the value
        AP4_DataAtom* data_atom = static_cast<AP4_DataAtom*>(atom->GetChild(AP4_ATOM_TYPE_DATA));
        if (data_atom == NULL) return AP4_ERROR_INVALID_FORMAT;
        value = new AP4_AtomMetaDataValue(data_atom, atom->GetType());
        
        return m_Entries.Add(new Entry(name->GetValue().GetChars(),
                                       mean->GetValue().GetChars(),
                                       value));
    } else {
        const char* key_name = NULL;
        char        four_cc[5];

        // convert the atom type to a name
        AP4_FormatFourChars(four_cc, (AP4_UI32)atom->GetType());
        key_name = four_cc;

        // add one entry for each data atom
        AP4_List<AP4_Atom>::Item* data_item = atom->GetChildren().FirstItem();
        while (data_item) {
            AP4_Atom* item_atom = data_item->GetData();
            if (item_atom->GetType() == AP4_ATOM_TYPE_DATA) {
                AP4_DataAtom* data_atom = static_cast<AP4_DataAtom*>(item_atom);
                value = new AP4_AtomMetaDataValue(data_atom, atom->GetType());
                m_Entries.Add(new Entry(key_name, namespc, value));
            }
            data_item = data_item->GetNext();
        }

        return AP4_SUCCESS;
    }
}

/*----------------------------------------------------------------------
|   AP4_MetaData::Add3GppEntry
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::Add3GppEntry(AP4_3GppLocalizedStringAtom* atom, const char* namespc)
{
    AP4_String key_name;
    ResolveKeyName(atom->GetType(), key_name);
    
    const char* language = NULL;
    if (atom->GetLanguage()[0]) {
        language = atom->GetLanguage();
    }
    AP4_MetaData::Value* value = new AP4_StringMetaDataValue(atom->GetValue().GetChars(),
                                                             language);
    m_Entries.Add(new Entry(key_name.GetChars(), namespc, value));
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MetaData::AddDcfStringEntry
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::AddDcfStringEntry(AP4_DcfStringAtom* atom, const char* namespc)
{
    AP4_String key_name;
    ResolveKeyName(atom->GetType(), key_name);
    
    AP4_MetaData::Value* value = new AP4_StringMetaDataValue(atom->GetValue().GetChars());
    m_Entries.Add(new Entry(key_name.GetChars(), namespc, value));
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MetaData::AddDcfdEntry
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::AddDcfdEntry(AP4_DcfdAtom* atom, const char* namespc)
{
    AP4_String key_name;
    ResolveKeyName(atom->GetType(), key_name);
    
    AP4_MetaData::Value* value = new AP4_IntegerMetaDataValue(AP4_MetaData::Value::TYPE_INT_32_BE, 
                                                              atom->GetDuration());
    m_Entries.Add(new Entry(key_name.GetChars(), namespc, value));
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MetaData::Value::MapDataTypeToCategory
+---------------------------------------------------------------------*/
AP4_MetaData::Value::TypeCategory 
AP4_MetaData::Value::MapTypeToCategory(Type type)
{
    switch (type) {
        case AP4_MetaData::Value::TYPE_INT_08_BE:
        case AP4_MetaData::Value::TYPE_INT_16_BE:
        case AP4_MetaData::Value::TYPE_INT_32_BE:
            return AP4_MetaData::Value::TYPE_CATEGORY_INTEGER;

        case AP4_MetaData::Value::TYPE_STRING_UTF_8:
        case AP4_MetaData::Value::TYPE_STRING_UTF_16:
        case AP4_MetaData::Value::TYPE_STRING_PASCAL:
            return AP4_MetaData::Value::TYPE_CATEGORY_STRING;

        case AP4_MetaData::Value::TYPE_FLOAT_32_BE:
        case AP4_MetaData::Value::TYPE_FLOAT_64_BE:
            return AP4_MetaData::Value::TYPE_CATEGORY_FLOAT;
            
        default:
            return AP4_MetaData::Value::TYPE_CATEGORY_BINARY;
    }
}

/*----------------------------------------------------------------------
|   AP4_MetaData::Value::GetTypeCategory
+---------------------------------------------------------------------*/
AP4_MetaData::Value::TypeCategory 
AP4_MetaData::Value::GetTypeCategory() const
{
    return MapTypeToCategory(m_Type);
}

/*----------------------------------------------------------------------
|   AP4_MetaData::Entry::ToAtom
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::Entry::ToAtom(AP4_Atom*& atom) const
{
    atom = NULL;
    
    if (m_Value == NULL) {
        return AP4_ERROR_INVALID_PARAMETERS;
    }
    
    if (m_Key.GetNamespace() == "meta") {
        // convert the name into an atom type
        if (m_Key.GetName().GetLength() != 4) {
            // the name is not in the right format
            return AP4_ERROR_INVALID_PARAMETERS;
        }
        AP4_Atom::Type atom_type = AP4_Atom::TypeFromString(m_Key.GetName().GetChars()); 
                                   
        // create a container atom for the data
        AP4_ContainerAtom* container = new AP4_ContainerAtom(atom_type);

        // add the data atom
        AP4_DataAtom* data = new AP4_DataAtom(*m_Value);
        container->AddChild(data);
        
        atom = container;
        return AP4_SUCCESS;
    } else if (m_Key.GetNamespace() == "dcf") {
        // convert the name into an atom type
        if (m_Key.GetName().GetLength() != 4) {
            // the name is not in the right format
            return AP4_ERROR_INVALID_PARAMETERS;
        }
        AP4_Atom::Type atom_type = AP4_Atom::TypeFromString(m_Key.GetName().GetChars()); 

        if (AP4_MetaDataAtomTypeHandler::IsTypeInList(atom_type, 
            AP4_MetaDataAtomTypeHandler::DcfStringTypeList)) {
            AP4_String atom_value = m_Value->ToString();
            atom = new AP4_DcfStringAtom(atom_type, atom_value.GetChars());
            return AP4_SUCCESS;
        } else if (AP4_MetaDataAtomTypeHandler::IsTypeInList(atom_type, 
                   AP4_MetaDataAtomTypeHandler::_3gppLocalizedStringTypeList)) {
            AP4_String atom_value = m_Value->ToString();
            const char* language = "eng"; // default
            if (m_Value->GetLanguage().GetLength() != 0) {
                language = m_Value->GetLanguage().GetChars();
            }
            atom = new AP4_3GppLocalizedStringAtom(atom_type, language, atom_value.GetChars());
            return AP4_SUCCESS;
        } else if (atom_type == AP4_ATOM_TYPE_DCFD) {
            atom = new AP4_DcfdAtom((AP4_UI32)m_Value->ToInteger());
            return AP4_SUCCESS;
        }
        
         // not supported
         return AP4_ERROR_NOT_SUPPORTED; 
    } else {
        // create a '----' atom
        AP4_ContainerAtom* container = new AP4_ContainerAtom(AP4_ATOM_TYPE_dddd);
        
        // add a 'mean' string
        container->AddChild(new AP4_MetaDataStringAtom(AP4_ATOM_TYPE_MEAN, m_Key.GetNamespace().GetChars()));
        
        // add a 'name' string
        container->AddChild(new AP4_MetaDataStringAtom(AP4_ATOM_TYPE_NAME, m_Key.GetName().GetChars()));

        // add the data atom
        AP4_DataAtom* data = new AP4_DataAtom(*m_Value);
        container->AddChild(data);
        
        atom = container;
        return AP4_SUCCESS;
    }
        
    // unreachable - return AP4_ERROR_NOT_SUPPORTED;
}

/*----------------------------------------------------------------------
|   AP4_MetaData::Entry::FindInIlst
+---------------------------------------------------------------------*/
AP4_ContainerAtom*
AP4_MetaData::Entry::FindInIlst(AP4_ContainerAtom* ilst) const
{
    if (m_Key.GetNamespace() == "meta") {
        AP4_Atom::Type atom_type = AP4_Atom::TypeFromString(m_Key.GetName().GetChars());
        return AP4_DYNAMIC_CAST(AP4_ContainerAtom, ilst->GetChild(atom_type));
    } else {
        AP4_List<AP4_Atom>::Item* ilst_item = ilst->GetChildren().FirstItem();
        while (ilst_item) {
            AP4_ContainerAtom* entry_atom = AP4_DYNAMIC_CAST(AP4_ContainerAtom, ilst_item->GetData()); 
            if (entry_atom) {
                AP4_MetaDataStringAtom* mean = static_cast<AP4_MetaDataStringAtom*>(entry_atom->GetChild(AP4_ATOM_TYPE_MEAN));
                AP4_MetaDataStringAtom* name = static_cast<AP4_MetaDataStringAtom*>(entry_atom->GetChild(AP4_ATOM_TYPE_NAME));
                if (mean && name &&
                    mean->GetValue() == m_Key.GetNamespace() &&
                    name->GetValue() == m_Key.GetName()) {
                    return entry_atom;
                }
            }
            ilst_item = ilst_item->GetNext();
        }
    }
    
    // not found
    return NULL;
}
    
/*----------------------------------------------------------------------
|   AP4_MetaData::Entry::AddToFileIlst
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::Entry::AddToFileIlst(AP4_File& file, AP4_Ordinal index)
{
    // check that we have a correct entry
    if (m_Value == NULL) return AP4_ERROR_INVALID_STATE;

    // convert the entry into an atom
    AP4_Atom* atom;
    AP4_Result result = ToAtom(atom);
    if (AP4_FAILED(result)) return result;
    AP4_ContainerAtom* entry_atom = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
    if (entry_atom == NULL) {
        return AP4_ERROR_INVALID_FORMAT;
    }

    // look for the 'moov'
    AP4_Movie* movie = file.GetMovie();
    if (movie == NULL) return AP4_ERROR_INVALID_FORMAT;
    AP4_MoovAtom* moov = movie->GetMoovAtom();
    if (moov == NULL) return AP4_ERROR_INVALID_FORMAT;
    
    // look for 'udta', and create if it does not exist 
    AP4_ContainerAtom* udta = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moov->FindChild("udta", true));
    if (udta == NULL) return AP4_ERROR_INTERNAL;
    
    // look for 'meta', and create if it does not exist ('meta' is a FULL atom)
    AP4_ContainerAtom* meta = AP4_DYNAMIC_CAST(AP4_ContainerAtom, udta->FindChild("meta", true, true));
    if (meta == NULL) return AP4_ERROR_INTERNAL;

    // look for a 'hdlr' atom type 'mdir'
    AP4_HdlrAtom* hdlr = AP4_DYNAMIC_CAST(AP4_HdlrAtom, meta->FindChild("hdlr"));
    if (hdlr == NULL) {
        hdlr = new AP4_HdlrAtom(AP4_HANDLER_TYPE_MDIR, "");
        meta->AddChild(hdlr);
    } else {
        if (hdlr->GetHandlerType() != AP4_HANDLER_TYPE_MDIR) {
            return AP4_ERROR_INVALID_FORMAT;
        }
    }

    // get/create the list of entries
    AP4_ContainerAtom* ilst = AP4_DYNAMIC_CAST(AP4_ContainerAtom, meta->FindChild("ilst", true));
    if (ilst == NULL) return AP4_ERROR_INTERNAL;
    
    // look if there is already a container for this entry
    AP4_ContainerAtom* existing = FindInIlst(ilst);
    if (existing == NULL) {
        // just add the one we have
        ilst->AddChild(entry_atom);
    } else {
        // add the entry's data to the existing entry
        AP4_DataAtom* data_atom = AP4_DYNAMIC_CAST(AP4_DataAtom, entry_atom->GetChild(AP4_ATOM_TYPE_DATA));
        if (data_atom == NULL) return AP4_ERROR_INTERNAL;
        entry_atom->RemoveChild(data_atom);
        existing->AddChild(data_atom, index);
        delete entry_atom;
    }
    
    return AP4_SUCCESS;    
}

/*----------------------------------------------------------------------
|   AP4_MetaData::Entry::AddToFileDcf
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::Entry::AddToFileDcf(AP4_File& file, AP4_Ordinal index)
{
    // check that we have a correct entry
    if (m_Value == NULL) return AP4_ERROR_INVALID_STATE;
    
    // look for 'odrm/odhe'
    AP4_ContainerAtom* odhe = AP4_DYNAMIC_CAST(AP4_ContainerAtom, file.FindChild("odrm/odhe"));
    if (odhe == NULL) return AP4_ERROR_NO_SUCH_ITEM;

    // get/create the list of entries
    AP4_ContainerAtom* udta = AP4_DYNAMIC_CAST(AP4_ContainerAtom, odhe->FindChild("udta", true));
    if (udta == NULL) return AP4_ERROR_INTERNAL;
    
    // convert the entry into an atom
    AP4_Atom* data_atom;
    AP4_Result result = ToAtom(data_atom);
    if (AP4_FAILED(result)) return result;

    // add the entry's data to the container
    return udta->AddChild(data_atom, index);
}

/*----------------------------------------------------------------------
|   AP4_MetaData::Entry::AddToFile
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::Entry::AddToFile(AP4_File& file, AP4_Ordinal index)
{
    // check that we have a correct entry
    if (m_Value == NULL) return AP4_ERROR_INVALID_STATE;
 
    // check the namespace of the key to know where to add the atom
    if (m_Key.GetNamespace() == "meta") {
        return AddToFileIlst(file, index);
    } else if (m_Key.GetNamespace() == "dcf") {
        return AddToFileDcf(file, index);
    } else {
        // custom namespace
        return AddToFileIlst(file, index);
    }
}

/*----------------------------------------------------------------------
|   AP4_MetaData::Entry::RemoveFromFileIlst
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::Entry::RemoveFromFileIlst(AP4_File& file, AP4_Ordinal index)
{
    // look for the 'moov'
    AP4_Movie* movie = file.GetMovie();
    if (movie == NULL) return AP4_ERROR_INVALID_FORMAT;
    AP4_MoovAtom* moov = movie->GetMoovAtom();
    if (moov == NULL) return AP4_ERROR_INVALID_FORMAT;
    
    // look for 'udta/meta/ilst'
    AP4_ContainerAtom* ilst = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moov->FindChild("udta/meta/ilst"));
    if (ilst == NULL) return AP4_ERROR_NO_SUCH_ITEM;
    
    // look if there is already a container for this entry
    AP4_ContainerAtom* existing = FindInIlst(ilst);
    if (existing == NULL) return AP4_ERROR_NO_SUCH_ITEM;
            
    // remove the data atom in the entry
    AP4_Result result = existing->DeleteChild(AP4_ATOM_TYPE_DATA, index);
    if (AP4_FAILED(result)) return result;

    // cleanup
    if (existing->GetType() == AP4_ATOM_TYPE_dddd) {
        // custom entry: if there are no more 'data' children, remove the entry
        if (existing->GetChild(AP4_ATOM_TYPE_DATA) == NULL) {
            ilst->RemoveChild(existing);
            delete existing;
        }
    } else {
        // normal entry: if the entry is empty, remove it
        if (existing->GetChildren().ItemCount() == 0) {
            ilst->RemoveChild(existing);
            delete existing;
        }
    }
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MetaData::Entry::RemoveFromFileDcf
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::Entry::RemoveFromFileDcf(AP4_File& file, AP4_Ordinal index)
{
    // look for 'odrm/odhe/udta'
    AP4_ContainerAtom* udta = AP4_DYNAMIC_CAST(AP4_ContainerAtom, file.FindChild("odrm/odhe/udta"));
    if (udta == NULL) return AP4_ERROR_NO_SUCH_ITEM;
                
    // remove the data atom in the entry
    AP4_UI32 type = AP4_BytesToUInt32BE((const unsigned char*)m_Key.GetName().GetChars());
    AP4_Result result = udta->DeleteChild(type, index);
    if (AP4_FAILED(result)) return result;
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MetaData::Entry::RemoveFromFile
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaData::Entry::RemoveFromFile(AP4_File& file, AP4_Ordinal index)
{
    // check the namespace of the key to know where to add the atom
    if (m_Key.GetNamespace() == "meta") {
        return RemoveFromFileIlst(file, index);
    } else if (m_Key.GetNamespace() == "dcf") {
        return RemoveFromFileDcf(file, index);
    } else {
        // custom namespace
        return RemoveFromFileIlst(file, index);
    }
}

/*----------------------------------------------------------------------
|   AP4_StringMetaDataValue::ToString
+---------------------------------------------------------------------*/
AP4_String
AP4_StringMetaDataValue::ToString() const
{
    return m_Value;
}

/*----------------------------------------------------------------------
|   AP4_StringMetaDataValue::ToBytes
+---------------------------------------------------------------------*/
AP4_Result
AP4_StringMetaDataValue::ToBytes(AP4_DataBuffer& /* bytes */) const
{
    return AP4_ERROR_NOT_SUPPORTED;
}

/*----------------------------------------------------------------------
|   AP4_StringMetaDataValue::ToInteger
+---------------------------------------------------------------------*/
long
AP4_StringMetaDataValue::ToInteger() const
{
    return 0;
}

/*----------------------------------------------------------------------
|   AP4_IntegerMetaDataValue::ToString
+---------------------------------------------------------------------*/
AP4_String
AP4_IntegerMetaDataValue::ToString() const
{
    char value[16];
    AP4_FormatString(value, sizeof(value), "%ld", m_Value);
    return AP4_String(value);
}

/*----------------------------------------------------------------------
|   AP4_IntegerMetaDataValue::ToBytes
+---------------------------------------------------------------------*/
AP4_Result
AP4_IntegerMetaDataValue::ToBytes(AP4_DataBuffer& /* bytes */) const
{
    return AP4_ERROR_NOT_SUPPORTED;
}

/*----------------------------------------------------------------------
|   AP4_IntegerMetaDataValue::ToInteger
+---------------------------------------------------------------------*/
long
AP4_IntegerMetaDataValue::ToInteger() const
{
    return m_Value;
}

/*----------------------------------------------------------------------
|   AP4_BinaryMetaDataValue::ToString
+---------------------------------------------------------------------*/
AP4_String
AP4_BinaryMetaDataValue::ToString() const
{
    return AP4_String(); // not supported
}

/*----------------------------------------------------------------------
|   AP4_BinaryMetaDataValue::ToBytes
+---------------------------------------------------------------------*/
AP4_Result
AP4_BinaryMetaDataValue::ToBytes(AP4_DataBuffer& bytes) const
{
    bytes.SetDataSize(m_Value.GetDataSize());
    AP4_CopyMemory(bytes.UseData(), m_Value.GetData(), m_Value.GetDataSize());
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_BinaryMetaDataValue::ToInteger
+---------------------------------------------------------------------*/
long
AP4_BinaryMetaDataValue::ToInteger() const
{
    return 0; // NOT SUPPORTED
}

/*----------------------------------------------------------------------
|   AP4_AtomMetaDataValue::AP4_AtomMetaDataValue
+---------------------------------------------------------------------*/
AP4_AtomMetaDataValue::AP4_AtomMetaDataValue(AP4_DataAtom*  atom,
                                             AP4_UI32       parent_type) :
    Value(atom->GetValueType()),
    m_DataAtom(atom)
{
    switch (parent_type) {
        case AP4_ATOM_TYPE_GNRE:
            m_Meaning = MEANING_ID3_GENRE;
            break;

        case AP4_ATOM_TYPE_CPIL:
            m_Meaning = MEANING_BOOLEAN;
            break;

        case AP4_ATOM_TYPE_PGAP:
        case AP4_ATOM_TYPE_PCST:
            m_Meaning = MEANING_BOOLEAN;
            break;

        case AP4_ATOM_TYPE_STIK:
            m_Meaning = MEANING_FILE_KIND;
            break;

        case AP4_ATOM_TYPE_PURL:
        case AP4_ATOM_TYPE_EGID:
            m_Meaning = MEANING_BINARY_ENCODED_CHARS;
            break;
            
        default:
            break;
    }
}

/*----------------------------------------------------------------------
|   AP4_AtomMetaDataValue::ToString
+---------------------------------------------------------------------*/
AP4_String 
AP4_AtomMetaDataValue::ToString() const
{
    char string[256] = "";

    AP4_MetaData::Value::Type value_type = m_DataAtom->GetValueType();
    switch (AP4_MetaData::Value::MapTypeToCategory(value_type)) {
        case AP4_MetaData::Value::TYPE_CATEGORY_INTEGER: 
            {
                long value;
                if (AP4_SUCCEEDED(m_DataAtom->LoadInteger(value))) {
                    if (m_Meaning == MEANING_BOOLEAN) {
                        if (value) {
                            return "True";
                        } else {
                            return "False";
                        }
                    } else if (m_Meaning == MEANING_FILE_KIND) {
                        if (value >= 0 && ((unsigned int)value) <= sizeof(Ap4StikNames)/sizeof(Ap4StikNames[0])) {
                            AP4_FormatString(string, sizeof(string), "(%ld) %s", value, Ap4StikNames[value]);
                        } else {
                            return "Unknown";
                        }
                    } else {
                        AP4_FormatString(string, sizeof(string), "%ld", value);
                    }
                }
                return AP4_String((const char*)string);
                break;
            }

        case AP4_MetaData::Value::TYPE_CATEGORY_STRING:
            {
                AP4_String* category_string;
                if (AP4_SUCCEEDED(m_DataAtom->LoadString(category_string))) {
                    AP4_String result(*category_string);
                    delete category_string;
                    return result;
                }
                break;
            }

        case AP4_MetaData::Value::TYPE_CATEGORY_BINARY:
            {
                AP4_DataBuffer data;
                if (AP4_SUCCEEDED(m_DataAtom->LoadBytes(data))) {
                    if (m_Meaning == MEANING_ID3_GENRE && data.GetDataSize() == 2) {
                        unsigned int genre = (data.GetData()[0])*256+data.GetData()[1];
                        if (genre >= 1 && genre <= sizeof(Ap4Id3Genres)/sizeof(Ap4Id3Genres[0])) {
                            AP4_FormatString(string, sizeof(string), "(%d) %s", genre, Ap4Id3Genres[genre-1]);
                            return AP4_String((const char*)string);
                        } else {
                            return "Unknown";
                        }
                    } else if (m_Meaning == MEANING_BINARY_ENCODED_CHARS) {
                        AP4_String result;
                        result.Assign((const char*)data.GetData(), data.GetDataSize());
                        return result;
                    } else {
                        unsigned int dump_length = data.GetDataSize();
                        bool truncate = false;
                        if (dump_length > 16) {
                            dump_length = 16;
                            truncate = true;
                        }
                        char* out = string;
                        for (unsigned int i=0; i<dump_length; i++) {
                            AP4_FormatString(out, sizeof(string)-(out-string), "%02x ", data.GetData()[i]);
                            out += 3;
                        }
                        if (truncate) {
                            *out++='.'; *out++='.'; *out++='.'; *out++=' ';
                        }
                        AP4_FormatString(out, sizeof(string)-(out-string), "[%d bytes]", (int)data.GetDataSize());
                    }
                }
                return AP4_String(string);
            }
        default:
            return AP4_String();
    }

    return AP4_String();
}

/*----------------------------------------------------------------------
|   AP4_AtomMetaDataValue::ToBytes
+---------------------------------------------------------------------*/
AP4_Result
AP4_AtomMetaDataValue::ToBytes(AP4_DataBuffer& bytes) const
{
    return m_DataAtom->LoadBytes(bytes);
}

/*----------------------------------------------------------------------
|   AP4_AtomMetaDataValue::ToInteger
+---------------------------------------------------------------------*/
long       
AP4_AtomMetaDataValue::ToInteger() const
{
    long value;
    if (AP4_SUCCEEDED(m_DataAtom->LoadInteger(value))) {
        return value;
    } else {
        return 0;
    }
}

/*----------------------------------------------------------------------
|   AP4_DataAtom::AP4_DataAtom
+---------------------------------------------------------------------*/
AP4_DataAtom::AP4_DataAtom(const AP4_MetaData::Value& value) :
    AP4_Atom(AP4_ATOM_TYPE_DATA, AP4_ATOM_HEADER_SIZE),
    m_DataType(DATA_TYPE_BINARY)
{
    AP4_MemoryByteStream* memory = new AP4_MemoryByteStream(256);
    AP4_Size payload_size = 8;
    m_Source = memory;
    
    switch (value.GetType()) {
        case AP4_MetaData::Value::TYPE_STRING_UTF_8: {
            m_DataType = DATA_TYPE_STRING_UTF_8;
            AP4_String string_value = value.ToString();
            if (string_value.GetLength()) {
                memory->Write(string_value.GetChars(), string_value.GetLength());
            }
            payload_size += string_value.GetLength();
            break;
        }
         
        case AP4_MetaData::Value::TYPE_INT_08_BE: {
            m_DataType = DATA_TYPE_SIGNED_INT_BE;
            AP4_UI08 int_value = (AP4_UI08)value.ToInteger();
            memory->Write(&int_value, 1);
            payload_size += 1;
            break;
        }
            
        case AP4_MetaData::Value::TYPE_INT_16_BE: {
            m_DataType = DATA_TYPE_SIGNED_INT_BE;
            AP4_UI16 int_value = (AP4_UI16)value.ToInteger();
            memory->Write(&int_value, 2);
            payload_size += 2;
            break;
        }

        case AP4_MetaData::Value::TYPE_INT_32_BE: {
            m_DataType = DATA_TYPE_SIGNED_INT_BE;
            AP4_UI32 int_value = (AP4_UI32)value.ToInteger();
            memory->Write(&int_value, 4);
            payload_size += 4;
            break;
        }

        case AP4_MetaData::Value::TYPE_JPEG:
            m_DataType = DATA_TYPE_JPEG;
            // FALLTHROUGH
        case AP4_MetaData::Value::TYPE_GIF: 
            if (m_DataType == DATA_TYPE_BINARY) m_DataType = DATA_TYPE_GIF;
            // FALLTHROUGH
        case AP4_MetaData::Value::TYPE_BINARY: {
            AP4_DataBuffer buffer;
            value.ToBytes(buffer);
            if (buffer.GetDataSize()) {
                memory->Write(buffer.GetData(), buffer.GetDataSize());
            }
            payload_size += buffer.GetDataSize();
            break;
        }

        default:
            break;
    }
    
    const AP4_String& language = value.GetLanguage();
    if (language == "en") {
        m_DataLang = LANGUAGE_ENGLISH;
    } else {
        // default
        m_DataLang = LANGUAGE_ENGLISH;
    }
    
    m_Size32 += payload_size;
}

/*----------------------------------------------------------------------
|   AP4_DataAtom::AP4_DataAtom
+---------------------------------------------------------------------*/
AP4_DataAtom::AP4_DataAtom(AP4_UI32 size, AP4_ByteStream& stream) :
    AP4_Atom(AP4_ATOM_TYPE_DATA, size)
{
    if (size < AP4_ATOM_HEADER_SIZE+8) return;

    AP4_UI32 i;
    stream.ReadUI32(i); m_DataType = (DataType)i;
    stream.ReadUI32(i); m_DataLang = (DataLang)i;

    // the stream for the data is a substream of this source
    AP4_Position data_offset;
    stream.Tell(data_offset);
    AP4_Size data_size = size-AP4_ATOM_HEADER_SIZE-8;
    m_Source = new AP4_SubStream(stream, data_offset, data_size);
}

/*----------------------------------------------------------------------
|   AP4_DataAtom::~AP4_DataAtom
+---------------------------------------------------------------------*/
AP4_DataAtom::~AP4_DataAtom()
{
    delete(m_Source);
}

/*----------------------------------------------------------------------
|   AP4_DataAtom::GetValueType
+---------------------------------------------------------------------*/
AP4_MetaData::Value::Type
AP4_DataAtom::GetValueType()
{
    switch (m_DataType) {
        case DATA_TYPE_BINARY:
            return AP4_MetaData::Value::TYPE_BINARY;

        case DATA_TYPE_SIGNED_INT_BE:
            switch (m_Size32-16) {
                case 1: return AP4_MetaData::Value::TYPE_INT_08_BE;
                case 2: return AP4_MetaData::Value::TYPE_INT_16_BE;
                case 4: return AP4_MetaData::Value::TYPE_INT_32_BE;
                default: return AP4_MetaData::Value::TYPE_BINARY;
            }
            break;
            
        case DATA_TYPE_STRING_UTF_8:
            return AP4_MetaData::Value::TYPE_STRING_UTF_8;
            
        case DATA_TYPE_STRING_UTF_16:
            return AP4_MetaData::Value::TYPE_STRING_UTF_16;
            
        case DATA_TYPE_STRING_PASCAL:
            return AP4_MetaData::Value::TYPE_STRING_PASCAL;

        case DATA_TYPE_GIF:
            return AP4_MetaData::Value::TYPE_GIF;

        case DATA_TYPE_JPEG:
            return AP4_MetaData::Value::TYPE_JPEG;

        default:
            return AP4_MetaData::Value::TYPE_BINARY;
    }
    
    // unreachable - return AP4_MetaData::Value::TYPE_BINARY;
}

/*----------------------------------------------------------------------
|   AP4_DataAtom::WriteFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_DataAtom::WriteFields(AP4_ByteStream& stream)
{
    stream.WriteUI32(m_DataType);
    stream.WriteUI32(m_DataLang);
    if (m_Source) {
        AP4_LargeSize size = 0;
        m_Source->GetSize(size);
        m_Source->Seek(0);
        m_Source->CopyTo(stream, size);
    }

    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_DataAtom::InspectFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_DataAtom::InspectFields(AP4_AtomInspector& inspector)
{
    inspector.AddField("type", m_DataType);
    inspector.AddField("lang", m_DataLang);
    if (m_DataType == DATA_TYPE_STRING_UTF_8) {
        AP4_String* str;
        if (AP4_SUCCEEDED(LoadString(str))) {
            inspector.AddField("value", str->GetChars());
            delete str;
        }
    } else if (m_DataType == DATA_TYPE_SIGNED_INT_BE) {
        long value;
        if (AP4_SUCCEEDED(LoadInteger(value))) {
            inspector.AddField("value", value);
        }
    }

    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_DataAtom::LoadString
+---------------------------------------------------------------------*/
AP4_Result
AP4_DataAtom::LoadString(AP4_String*& string)
{
    if (m_Source == NULL) {
        string = new AP4_String();
        return AP4_SUCCESS;
    } else {
        // create a string with enough capactiy for the data
        AP4_LargeSize size = 0;
        m_Source->GetSize(size);
        if (size > AP4_DATA_ATOM_MAX_SIZE) return AP4_ERROR_OUT_OF_RANGE;
        string = new AP4_String((AP4_Size)size);

        // read from the start of the stream
        m_Source->Seek(0);
        AP4_Result result = m_Source->Read(string->UseChars(), (AP4_Size)size);
        if (AP4_FAILED(result)) {
            delete string;
            string = NULL;
        }

        return result;
    }
}

/*----------------------------------------------------------------------
|   AP4_DataAtom::LoadBytes
+---------------------------------------------------------------------*/
AP4_Result
AP4_DataAtom::LoadBytes(AP4_DataBuffer& bytes)
{
    if (m_Source == NULL) {
        bytes.SetDataSize(0);
        return AP4_SUCCESS;
    }
    AP4_LargeSize size = 0;
    m_Source->GetSize(size);
    if (size > AP4_DATA_ATOM_MAX_SIZE) return AP4_ERROR_OUT_OF_RANGE;
    bytes.SetDataSize((AP4_Size)size);
    m_Source->Seek(0);
    AP4_Result result = m_Source->Read(bytes.UseData(), (AP4_Size)size);
    if (AP4_FAILED(result)) {
        bytes.SetDataSize(0);
    }
    return result;
}

/*----------------------------------------------------------------------
|   AP4_DataAtom::LoadInteger
+---------------------------------------------------------------------*/
AP4_Result
AP4_DataAtom::LoadInteger(long& value) 
{
    AP4_Result result = AP4_FAILURE;
    value = 0;
    if (m_Source == NULL) return AP4_SUCCESS;
    AP4_LargeSize size = 0;
    m_Source->GetSize(size);
    if (size > 4) {
        return AP4_ERROR_OUT_OF_RANGE;
    }
    unsigned char bytes[4];
    m_Source->Seek(0);
    m_Source->Read(bytes, (AP4_Size)size);
    result = AP4_SUCCESS;
    switch (size) {
        case 1: value = bytes[0]; break;
        case 2: value = AP4_BytesToInt16BE(bytes); break;
        case 4: value = AP4_BytesToInt32BE(bytes); break;
        default: value = 0; result = AP4_ERROR_INVALID_FORMAT; break;
    }
    return result;
}

/*----------------------------------------------------------------------
|   AP4_MetaDataStringAtom::AP4_MetaDataStringAtom
+---------------------------------------------------------------------*/
AP4_MetaDataStringAtom::AP4_MetaDataStringAtom(Type type, const char* value) :
    AP4_Atom(type, AP4_ATOM_HEADER_SIZE),
    m_Reserved(0),
    m_Value(value)
{
    m_Size32 += 4+m_Value.GetLength();
}

/*----------------------------------------------------------------------
|   AP4_MetaDataStringAtom::AP4_MetaDataStringAtom
+---------------------------------------------------------------------*/
AP4_MetaDataStringAtom::AP4_MetaDataStringAtom(Type type, AP4_UI32 size, AP4_ByteStream& stream) :
    AP4_Atom(type, size),
    m_Reserved(0),
    m_Value((AP4_Size)(size-AP4_ATOM_HEADER_SIZE-4))
{
    stream.ReadUI32(m_Reserved);
    stream.Read(m_Value.UseChars(), m_Value.GetLength());
}

/*----------------------------------------------------------------------
|   AP4_MetaDataStringAtom::WriteFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaDataStringAtom::WriteFields(AP4_ByteStream& stream)
{
    stream.WriteUI32(m_Reserved);
    return stream.Write(m_Value.GetChars(), m_Value.GetLength());
}

/*----------------------------------------------------------------------
|   AP4_MetaDataStringAtom::InspectFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_MetaDataStringAtom::InspectFields(AP4_AtomInspector& inspector)
{
    inspector.AddField("value", m_Value.GetChars());
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_3GppLocalizedStringAtom::Create
+---------------------------------------------------------------------*/
AP4_3GppLocalizedStringAtom*
AP4_3GppLocalizedStringAtom::Create(Type type, AP4_UI32 size, AP4_ByteStream& stream) 
{
    AP4_UI08 version;
    AP4_UI32 flags;
    if (AP4_FAILED(AP4_Atom::ReadFullHeader(stream, version, flags))) return NULL;
    if (version != 0) return NULL;
    return new AP4_3GppLocalizedStringAtom(type, size, version, flags, stream);
}

/*----------------------------------------------------------------------
|   AP4_3GppLocalizedStringAtom::AP4_3GppLocalizedStringAtom
+---------------------------------------------------------------------*/
AP4_3GppLocalizedStringAtom::AP4_3GppLocalizedStringAtom(Type        type, 
                                                         const char* language, 
                                                         const char* value) :
    AP4_Atom(type, AP4_FULL_ATOM_HEADER_SIZE+2, 0, 0),
    m_Value(value)
{
    m_Language[0] = language[0];
    m_Language[1] = language[1];
    m_Language[2] = language[2];
    m_Language[3] = language[3];
    
    m_Size32 += m_Value.GetLength()+1;
}

/*----------------------------------------------------------------------
|   AP4_3GppLocalizedStringAtom::AP4_3GppLocalizedStringAtom
+---------------------------------------------------------------------*/
AP4_3GppLocalizedStringAtom::AP4_3GppLocalizedStringAtom(Type            type, 
                                                         AP4_UI32        size, 
                                                         AP4_UI08        version,
                                                         AP4_UI32        flags,
                                                         AP4_ByteStream& stream) :
    AP4_Atom(type, size, version, flags)
{
    // read the language code
    AP4_UI16 packed_language;
    stream.ReadUI16(packed_language);
    m_Language[0] = 0x60+((packed_language>>10)&0x1F);
    m_Language[1] = 0x60+((packed_language>> 5)&0x1F);
    m_Language[2] = 0x60+((packed_language    )&0x1F);
    m_Language[3] = '\0';
    
    // read the value (should be a NULL-terminated string, but we'll
    // allow for strings that are not terminated)
    if (size > AP4_FULL_ATOM_HEADER_SIZE+2) {
        AP4_UI32 value_size = size-(AP4_FULL_ATOM_HEADER_SIZE+2);
        char* value = new char[value_size];
        stream.Read(value, value_size);
        m_Value.Assign(value, value_size);
        delete[] value;
    }
}

/*----------------------------------------------------------------------
|   AP4_3GppLocalizedStringAtom::WriteFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_3GppLocalizedStringAtom::WriteFields(AP4_ByteStream& stream)
{
    AP4_UI16 packed_language = ((m_Language[0]-0x60)<<10) |
                               ((m_Language[1]-0x60)<< 5) |
                               ((m_Language[2]-0x60));
    stream.WriteUI16(packed_language);
    AP4_Size payload_size = (AP4_UI32)GetSize()-GetHeaderSize();
    if (payload_size < 2) return AP4_ERROR_INVALID_FORMAT;
    AP4_Size value_size = m_Value.GetLength()+1;
    if (value_size > payload_size-2) {
        value_size = payload_size-2;
    }
    stream.Write(m_Value.GetChars(), value_size);
    for (unsigned int i=value_size; i<payload_size-2; i++) {
        stream.WriteUI08(0);
    }
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_3GppLocalizedStringAtom::InspectFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_3GppLocalizedStringAtom::InspectFields(AP4_AtomInspector& inspector)
{
    inspector.AddField("language", GetLanguage());
    inspector.AddField("value", m_Value.GetChars());
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_DcfStringAtom::Create
+---------------------------------------------------------------------*/
AP4_DcfStringAtom*
AP4_DcfStringAtom::Create(Type type, AP4_UI32 size, AP4_ByteStream& stream) 
{
    AP4_UI08 version;
    AP4_UI32 flags;
    if (AP4_FAILED(AP4_Atom::ReadFullHeader(stream, version, flags))) return NULL;
    if (version != 0) return NULL;
    return new AP4_DcfStringAtom(type, size, version, flags, stream);
}

/*----------------------------------------------------------------------
|   AP4_DcfStringAtom::AP4_DcfStringAtom
+---------------------------------------------------------------------*/
AP4_DcfStringAtom::AP4_DcfStringAtom(Type type, const char* value) :
    AP4_Atom(type, AP4_FULL_ATOM_HEADER_SIZE, 0, 0),
    m_Value(value)
{
    m_Size32 += m_Value.GetLength();
}

/*----------------------------------------------------------------------
|   AP4_DcfStringAtom::AP4_DcfStringAtom
+---------------------------------------------------------------------*/
AP4_DcfStringAtom::AP4_DcfStringAtom(Type            type, 
                                     AP4_UI32        size, 
                                     AP4_UI08        version,
                                     AP4_UI32        flags,
                                     AP4_ByteStream& stream) :
    AP4_Atom(type, size, version, flags)
{
    if (size > AP4_FULL_ATOM_HEADER_SIZE) {
        AP4_UI32 value_size = size-(AP4_FULL_ATOM_HEADER_SIZE);
        char* value = new char[value_size];
        stream.Read(value, value_size);
        m_Value.Assign(value, value_size);
        delete[] value;
    }
}

/*----------------------------------------------------------------------
|   AP4_DcfStringAtom::WriteFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_DcfStringAtom::WriteFields(AP4_ByteStream& stream)
{
    if (m_Value.GetLength()) stream.Write(m_Value.GetChars(), m_Value.GetLength());
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_DcfStringAtom::InspectFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_DcfStringAtom::InspectFields(AP4_AtomInspector& inspector)
{
    inspector.AddField("value", m_Value.GetChars());
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_DcfdAtom::Create
+---------------------------------------------------------------------*/
AP4_DcfdAtom*
AP4_DcfdAtom::Create(AP4_UI32 size, AP4_ByteStream& stream) 
{
    AP4_UI08 version;
    AP4_UI32 flags;
    if (AP4_FAILED(AP4_Atom::ReadFullHeader(stream, version, flags))) return NULL;
    if (version != 0) return NULL;
    if (size != AP4_FULL_ATOM_HEADER_SIZE+4) return NULL;
    return new AP4_DcfdAtom(version, flags, stream);
}

/*----------------------------------------------------------------------
|   AP4_DcfdAtom::AP4_DcfdAtom
+---------------------------------------------------------------------*/
AP4_DcfdAtom::AP4_DcfdAtom(AP4_UI08        version,
                           AP4_UI32        flags,
                           AP4_ByteStream& stream) :
    AP4_Atom(AP4_ATOM_TYPE_DCFD, AP4_FULL_ATOM_HEADER_SIZE+4, version, flags),
    m_Duration(0)
{
    stream.ReadUI32(m_Duration);
}

/*----------------------------------------------------------------------
|   AP4_DcfdAtom::AP4_DcfdAtom
+---------------------------------------------------------------------*/
AP4_DcfdAtom::AP4_DcfdAtom(AP4_UI32 duration) :
    AP4_Atom(AP4_ATOM_TYPE_DCFD, AP4_FULL_ATOM_HEADER_SIZE+4, 0, 0),
    m_Duration(duration)
{
}

/*----------------------------------------------------------------------
|   AP4_DcfdAtom::WriteFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_DcfdAtom::WriteFields(AP4_ByteStream& stream)
{
    stream.WriteUI32(m_Duration);
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_DcfdAtom::InspectFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_DcfdAtom::InspectFields(AP4_AtomInspector& inspector)
{
    inspector.AddField("duration", m_Duration);
    return AP4_SUCCESS;
}
