/*
 * 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 MPEG4IP.
 *
 * The Initial Developer of the Original Code is Cisco Systems Inc.
 * Portions created by Cisco Systems Inc. are
 * Copyright (C) Cisco Systems Inc. 2001.  All Rights Reserved.
 *
 * Contributor(s):
 *      Dave Mackie     dmackie@cisco.com
 *      Kona Blend      kona8lend@@gmail.com
 */

#include "src/impl.h"

namespace mp4v2 { namespace impl {

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

MP4Property::MP4Property(MP4Atom& parentAtom, const char* name)
    : m_parentAtom(parentAtom)
{
    m_name = name;
    m_readOnly = false;
    m_implicit = false;
}

bool MP4Property::FindProperty(const char* name,
                               MP4Property** ppProperty, uint32_t* pIndex)
{
    if (name == NULL) {
        return false;
    }

    if (!strcasecmp(m_name, name)) {
        log.verbose1f("\"%s\": FindProperty: matched %s", 
                      m_parentAtom.GetFile().GetFilename().c_str(), name);
        *ppProperty = this;
        return true;
    }
    return false;
}

// Integer Property

uint64_t MP4IntegerProperty::GetValue(uint32_t index)
{
    switch (this->GetType()) {
    case Integer8Property:
        return ((MP4Integer8Property*)this)->GetValue(index);
    case Integer16Property:
        return ((MP4Integer16Property*)this)->GetValue(index);
    case Integer24Property:
        return ((MP4Integer24Property*)this)->GetValue(index);
    case Integer32Property:
        return ((MP4Integer32Property*)this)->GetValue(index);
    case Integer64Property:
        return ((MP4Integer64Property*)this)->GetValue(index);
    default:
        ASSERT(false);
    }
    return (0);
}

void MP4IntegerProperty::SetValue(uint64_t value, uint32_t index)
{
    switch (this->GetType()) {
    case Integer8Property:
        ((MP4Integer8Property*)this)->SetValue(value, index);
        break;
    case Integer16Property:
        ((MP4Integer16Property*)this)->SetValue(value, index);
        break;
    case Integer24Property:
        ((MP4Integer24Property*)this)->SetValue(value, index);
        break;
    case Integer32Property:
        ((MP4Integer32Property*)this)->SetValue(value, index);
        break;
    case Integer64Property:
        ((MP4Integer64Property*)this)->SetValue(value, index);
        break;
    default:
        ASSERT(false);
    }
}

void MP4IntegerProperty::InsertValue(uint64_t value, uint32_t index)
{
    switch (this->GetType()) {
    case Integer8Property:
        ((MP4Integer8Property*)this)->InsertValue(value, index);
        break;
    case Integer16Property:
        ((MP4Integer16Property*)this)->InsertValue(value, index);
        break;
    case Integer24Property:
        ((MP4Integer24Property*)this)->InsertValue(value, index);
        break;
    case Integer32Property:
        ((MP4Integer32Property*)this)->InsertValue(value, index);
        break;
    case Integer64Property:
        ((MP4Integer64Property*)this)->InsertValue(value, index);
        break;
    default:
        ASSERT(false);
    }
}

void MP4IntegerProperty::DeleteValue(uint32_t index)
{
    switch (this->GetType()) {
    case Integer8Property:
        ((MP4Integer8Property*)this)->DeleteValue(index);
        break;
    case Integer16Property:
        ((MP4Integer16Property*)this)->DeleteValue(index);
        break;
    case Integer24Property:
        ((MP4Integer24Property*)this)->DeleteValue(index);
        break;
    case Integer32Property:
        ((MP4Integer32Property*)this)->DeleteValue(index);
        break;
    case Integer64Property:
        ((MP4Integer64Property*)this)->DeleteValue(index);
        break;
    default:
        ASSERT(false);
    }
}

void MP4IntegerProperty::IncrementValue(int32_t increment, uint32_t index)
{
    SetValue(GetValue() + increment);
}

void MP4Integer8Property::Dump(uint8_t indent,
                               bool dumpImplicits, uint32_t index)
{
    if (m_implicit && !dumpImplicits) {
        return;
    }
    if (index != 0)
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %u (0x%02x)",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, index, m_values[index], m_values[index]);
    else
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %u (0x%02x)",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, m_values[index], m_values[index]);
}

void MP4Integer16Property::Dump(uint8_t indent,
                                bool dumpImplicits, uint32_t index)
{
    if (m_implicit && !dumpImplicits) {
        return;
    }
    if (index != 0)
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %u (0x%04x)",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, index, m_values[index], m_values[index]);
    else
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %u (0x%04x)",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, m_values[index], m_values[index]);
}

void MP4Integer24Property::Dump(uint8_t indent,
                                bool dumpImplicits, uint32_t index)
{
    if (m_implicit && !dumpImplicits) {
        return;
    }
    if (index != 0)
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %u (0x%06x)",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, index, m_values[index], m_values[index]);
    else
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %u (0x%06x)",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, m_values[index], m_values[index]);
}

void MP4Integer32Property::Dump(uint8_t indent,
                                bool dumpImplicits, uint32_t index)
{
    if (m_implicit && !dumpImplicits) {
        return;
    }
    if (index != 0)
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %u (0x%08x)",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, index, m_values[index], m_values[index]);
    else
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %u (0x%08x)",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, m_values[index], m_values[index]);
}

void MP4Integer64Property::Dump(uint8_t indent,
                                bool dumpImplicits, uint32_t index)
{
    if (m_implicit && !dumpImplicits) {
        return;
    }
    if (index != 0)
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %" PRIu64 " (0x%016" PRIx64 ")",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, index, m_values[index], m_values[index]);
    else
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %" PRIu64 " (0x%016" PRIx64 ")",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, m_values[index], m_values[index]);
}

// MP4BitfieldProperty

void MP4BitfieldProperty::Read(MP4File& file, uint32_t index)
{
    if (m_implicit) {
        return;
    }
    m_values[index] = file.ReadBits(m_numBits);
}

void MP4BitfieldProperty::Write(MP4File& file, uint32_t index)
{
    if (m_implicit) {
        return;
    }
    file.WriteBits(m_values[index], m_numBits);
}

void MP4BitfieldProperty::Dump(uint8_t indent,
                               bool dumpImplicits, uint32_t index)
{
    if (m_implicit && !dumpImplicits) {
        return;
    }
    uint8_t hexWidth = m_numBits / 4;
    if (hexWidth == 0 || (m_numBits % 4)) {
        hexWidth++;
    }
    if (index != 0)
        log.dump(indent, MP4_LOG_VERBOSE1,
                 "\"%s\": %s[%u] = %" PRIu64 " (0x%0*" PRIx64 ") <%u bits>",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, index, m_values[index], (int)hexWidth, m_values[index], m_numBits);
    else
        log.dump(indent, MP4_LOG_VERBOSE1,
                 "\"%s\": %s = %" PRIu64 " (0x%0*" PRIx64 ") <%u bits>",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, m_values[index], (int)hexWidth, m_values[index], m_numBits);
}

// MP4Float32Property

void MP4Float32Property::Read(MP4File& file, uint32_t index)
{
    if (m_implicit) {
        return;
    }
    if (m_useFixed16Format) {
        m_values[index] = file.ReadFixed16();
    } else if (m_useFixed32Format) {
        m_values[index] = file.ReadFixed32();
    } else {
        m_values[index] = file.ReadFloat();
    }
}

void MP4Float32Property::Write(MP4File& file, uint32_t index)
{
    if (m_implicit) {
        return;
    }
    if (m_useFixed16Format) {
        file.WriteFixed16(m_values[index]);
    } else if (m_useFixed32Format) {
        file.WriteFixed32(m_values[index]);
    } else {
        file.WriteFloat(m_values[index]);
    }
}

void MP4Float32Property::Dump(uint8_t indent,
                              bool dumpImplicits, uint32_t index)
{
    if (m_implicit && !dumpImplicits) {
        return;
    }
    if (index != 0)
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u] = %f",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, index, m_values[index]);
    else
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %f",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, m_values[index]);
}

// MP4StringProperty

MP4StringProperty::MP4StringProperty(
    MP4Atom& parentAtom,
    const char* name,
    bool        useCountedFormat,
    bool        useUnicode,
    bool        arrayMode )

    : MP4Property( parentAtom, name )
    , m_arrayMode        ( arrayMode )
    , m_useCountedFormat ( useCountedFormat )
    , m_useExpandedCount ( false )
    , m_useUnicode       ( useUnicode )
    , m_fixedLength      ( 0 )
{
    SetCount( 1 );
    m_values[0] = NULL;
}

MP4StringProperty::~MP4StringProperty()
{
    uint32_t count = GetCount();
    for (uint32_t i = 0; i < count; i++) {
        MP4Free(m_values[i]);
    }
}

void MP4StringProperty::SetCount(uint32_t count)
{
    uint32_t oldCount = m_values.Size();

    m_values.Resize(count);

    for (uint32_t i = oldCount; i < count; i++) {
        m_values[i] = NULL;
    }
}

void MP4StringProperty::SetValue(const char* value, uint32_t index)
{
    if (m_readOnly) {
        ostringstream msg;
        msg << "property " << m_name << "is read-only";
        throw new PlatformException(msg.str().c_str(), EACCES, __FILE__, __LINE__, __FUNCTION__ );
    }

    MP4Free(m_values[index]);

    if (m_fixedLength) {
        m_values[index] = (char*)MP4Calloc(m_fixedLength + 1);
        if (value) {
            strncpy(m_values[index], value, m_fixedLength);
        }
    } else {
        if (value) {
            m_values[index] = MP4Stralloc(value);
        } else {
            m_values[index] = NULL;
        }
    }
}

void MP4StringProperty::Read( MP4File& file, uint32_t index )
{
    if( m_implicit )
        return;

    uint32_t begin = index;
    uint32_t max   = index + 1;

    if( m_arrayMode ) {
        begin = 0;
        max   = GetCount();
    }

    for( uint32_t i = begin; i < max; i++ ) {
        char*& value = m_values[i];

        // Generally a default atom setting, e.g. see atom_avc1.cpp, "JVT/AVC Coding"; we'll leak this string if
        // we don't free.  Note that this code checks for null before calling free and sets the pointer to null
        // after freeing it, to prevent a double free in case an exception occurs before the value is reassigned.
        MP4Free( value );
        value = NULL;

        if( m_useCountedFormat ) {
            value = file.ReadCountedString( (m_useUnicode ? 2 : 1), m_useExpandedCount, m_fixedLength );
        }
        else if( m_fixedLength ) {
            value = (char*)MP4Calloc( m_fixedLength + 1 );
            file.ReadBytes( (uint8_t*)value, m_fixedLength );
        }
        else {
            value = file.ReadString();
        }
    }
}

void MP4StringProperty::Write( MP4File& file, uint32_t index )
{
    if( m_implicit )
        return;

    uint32_t begin = index;
    uint32_t max   = index + 1;

    if( m_arrayMode ) {
        begin = 0;
        max   = GetCount();
    }

    for( uint32_t i = begin; i < max; i++ ) {
        char*& value = m_values[i];

        if( m_useCountedFormat ) {
            file.WriteCountedString( value, (m_useUnicode ? 2 : 1), m_useExpandedCount, m_fixedLength );
        }
        else if( m_fixedLength ) {
            file.WriteBytes( (uint8_t*)value, m_fixedLength );
        }
        else {
            file.WriteString( value );
        }
    }
}

void MP4StringProperty::Dump( uint8_t indent, bool dumpImplicits, uint32_t index )
{
    if( m_implicit && !dumpImplicits )
        return;

    if( !m_arrayMode ) {
        char indexd[32];
        if( index != 0 )
            snprintf( indexd, 32, "[%u]", index );
        else
            indexd[0] = '\0';

        if( m_useUnicode )
            log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s%s = %ls",
                     m_parentAtom.GetFile().GetFilename().c_str(),
                     m_name, indexd, (wchar_t*)m_values[index] );
        else
            log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s%s = %s",
                     m_parentAtom.GetFile().GetFilename().c_str(),
                     m_name, indexd, m_values[index] );
    }
    else if( log.verbosity >= MP4_LOG_VERBOSE2 )
    {
        const uint32_t max = GetCount();

        log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s (size=%u)",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, max );

        for( uint32_t i = 0; i < max; i++ ) {
            char*& value = m_values[i];

            if( m_useUnicode )
                log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s[%u] = %ls",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, i, (wchar_t*)value );
            else
                log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s[%u] = %s",
                         m_parentAtom.GetFile().GetFilename().c_str(),
                         m_name, i, value );
        }
    }
    else {
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": <table entries suppressed>",
                 m_parentAtom.GetFile().GetFilename().c_str() );
    }
}

// MP4BytesProperty

MP4BytesProperty::MP4BytesProperty(MP4Atom& parentAtom, const char* name, uint32_t valueSize,
                                   uint32_t defaultValueSize)
        : MP4Property(parentAtom, name)
        , m_fixedValueSize(0)
        , m_defaultValueSize(defaultValueSize)
{
    SetCount(1);
    m_values[0] = (uint8_t*)MP4Calloc(valueSize);
    m_valueSizes[0] = valueSize;
}

MP4BytesProperty::~MP4BytesProperty()
{
    uint32_t count = GetCount();
    for (uint32_t i = 0; i < count; i++) {
        MP4Free(m_values[i]);
    }
}

void MP4BytesProperty::SetCount(uint32_t count)
{
    uint32_t oldCount = m_values.Size();

    m_values.Resize(count);
    m_valueSizes.Resize(count);

    for (uint32_t i = oldCount; i < count; i++) {
        m_values[i] = NULL;
        m_valueSizes[i] = m_defaultValueSize;
    }
}

void MP4BytesProperty::SetValue(const uint8_t* pValue, uint32_t valueSize,
                                uint32_t index)
{
    if (m_readOnly) {
        ostringstream msg;
        msg << "property " << m_name << "is read-only";
        throw new PlatformException(msg.str().c_str(), EACCES, __FILE__, __LINE__, __FUNCTION__ );
    }
    if (m_fixedValueSize) {
        if (valueSize > m_fixedValueSize) {
            ostringstream msg;
            msg << GetParentAtom().GetType() << "." << GetName() << " value size " << valueSize << " exceeds fixed value size " << m_fixedValueSize;
            throw new Exception(msg.str().c_str(), __FILE__, __LINE__, __FUNCTION__ );
        }
        if (m_values[index] == NULL) {
            m_values[index] = (uint8_t*)MP4Calloc(m_fixedValueSize);
            m_valueSizes[index] = m_fixedValueSize;
        }
        if (pValue) {
            memcpy(m_values[index], pValue, valueSize);
        }
    } else {
        MP4Free(m_values[index]);
        if (pValue) {
            m_values[index] = (uint8_t*)MP4Malloc(valueSize);
            memcpy(m_values[index], pValue, valueSize);
            m_valueSizes[index] = valueSize;
        } else {
            m_values[index] = NULL;
            m_valueSizes[index] = 0;
        }
    }
}

void MP4BytesProperty::SetValueSize(uint32_t valueSize, uint32_t index)
{
    if (m_fixedValueSize) {
        throw new Exception("can't change size of fixed sized property",
                            __FILE__, __LINE__, __FUNCTION__ );
    }
    if (m_values[index] != NULL) {
        m_values[index] = (uint8_t*)MP4Realloc(m_values[index], valueSize);
    }
    m_valueSizes[index] = valueSize;
}

void MP4BytesProperty::SetFixedSize(uint32_t fixedSize)
{
    m_fixedValueSize = 0;
    for (uint32_t i = 0; i < GetCount(); i++) {
        SetValueSize(fixedSize, i);
    }
    m_fixedValueSize = fixedSize;
}

void MP4BytesProperty::Read(MP4File& file, uint32_t index)
{
    if (m_implicit) {
        return;
    }
    MP4Free(m_values[index]);
    m_values[index] = (uint8_t*)MP4Malloc(m_valueSizes[index]);
    file.ReadBytes(m_values[index], m_valueSizes[index]);
}

void MP4BytesProperty::Write(MP4File& file, uint32_t index)
{
    if (m_implicit) {
        return;
    }
    file.WriteBytes(m_values[index], m_valueSizes[index]);
}

void MP4BytesProperty::Dump(uint8_t indent,
                            bool dumpImplicits, uint32_t index)
{
    if( m_implicit && !dumpImplicits )
        return;

    const uint32_t size  = m_valueSizes[index];
    const uint8_t* const value = m_values[index];

    if( size == 0 ) {
        log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s = <%u bytes>",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, size );
        return;
    }

    if( size <= 16 ) {
        ostringstream oss;
        ostringstream text;

        oss << "  ";
        for( uint32_t i = 0; i < size; i++ ) {
            if( i )
                oss << ' ';
            oss << hex << setw(2) << setfill('0') << right << static_cast<uint32_t>(value[i]);
            text << (isprint( static_cast<int>(value[i]) ) ? static_cast<char>(value[i]) : '.');
        }

        oss << "  |" << text.str() << "|";

        log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s = <%u bytes>%s",
                 m_parentAtom.GetFile().GetFilename().c_str(),
                 m_name, size, oss.str().c_str() );
        return;
    }

    // specialization for ilst item data always show all bytes except for covr
    bool showall = false;
    MP4Atom* const datac = m_parentAtom.GetParentAtom(); // data container
    MP4Atom* const datacc = datac->GetParentAtom();
    if( datacc &&
        ATOMID( datacc->GetType() ) == ATOMID( "ilst" ) &&
        ATOMID( datac->GetType() ) != ATOMID( "covr" ) )
    {
        showall = true;
    }

    uint32_t adjsize;
    bool supressed;

    if( showall ||
        size < 128 || log.verbosity >= MP4_LOG_VERBOSE2 )
    {
        adjsize = size;
        supressed = false;
    }
    else {
        adjsize = 128;
        supressed = true;
    }

    ostringstream oss;
    ostringstream text;

    log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s = <%u bytes>",
             m_parentAtom.GetFile().GetFilename().c_str(),
             m_name, size );
    log.hexDump(indent, MP4_LOG_VERBOSE2, value, adjsize, "\"%s\": %s",
                m_parentAtom.GetFile().GetFilename().c_str(),
                m_name);

    if( supressed ) {
        log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": <remaining bytes supressed>",
                 m_parentAtom.GetFile().GetFilename().c_str() );
    }
}

// MP4TableProperty

MP4TableProperty::MP4TableProperty(MP4Atom& parentAtom, const char* name, MP4IntegerProperty* pCountProperty)
        : MP4Property(parentAtom, name)
{
    m_pCountProperty = pCountProperty;
    m_pCountProperty->SetReadOnly();
}

MP4TableProperty::~MP4TableProperty()
{
    for (uint32_t i = 0; i < m_pProperties.Size(); i++) {
        delete m_pProperties[i];
    }
}

void MP4TableProperty::AddProperty(MP4Property* pProperty)
{
    ASSERT(pProperty);
    ASSERT(pProperty->GetType() != TableProperty);
    ASSERT(pProperty->GetType() != DescriptorProperty);
    m_pProperties.Add(pProperty);
    pProperty->SetCount(0);
}

bool MP4TableProperty::FindProperty(const char *name,
                                    MP4Property** ppProperty, uint32_t* pIndex)
{
    ASSERT(m_name);

    // check if first component of name matches ourselves
    if (!MP4NameFirstMatches(m_name, name)) {
        return false;
    }

    // check if the specified table entry exists
    uint32_t index;
    bool haveIndex = MP4NameFirstIndex(name, &index);
    if (haveIndex) {
        if (index >= GetCount()) {
            return false;
        }
        if (pIndex) {
            *pIndex = index;
        }
    }
    
    log.verbose1f("\"%s\": FindProperty: matched %s", 
                  m_parentAtom.GetFile().GetFilename().c_str(), name);

    // get name of table property
    const char *tablePropName = MP4NameAfterFirst(name);
    if (tablePropName == NULL) {
        if (!haveIndex) {
            *ppProperty = this;
            return true;
        }
        return false;
    }

    // check if this table property exists
    return FindContainedProperty(tablePropName, ppProperty, pIndex);
}

bool MP4TableProperty::FindContainedProperty(const char *name,
        MP4Property** ppProperty, uint32_t* pIndex)
{
    uint32_t numProperties = m_pProperties.Size();

    for (uint32_t i = 0; i < numProperties; i++) {
        if (m_pProperties[i]->FindProperty(name, ppProperty, pIndex)) {
            return true;
        }
    }
    return false;
}

struct FastRead32Attr
{
   typedef uint32_t              PropertyType;
   typedef MP4Integer32Property  MP4PropertyType;
   static uint32_t ReverseBytes( const uint32_t& x ) { return MP4V2_BYTESWAP_32( x ); }   
};

struct FastRead64Attr
{
   typedef uint64_t              PropertyType;
   typedef MP4Integer64Property  MP4PropertyType;
   static uint64_t ReverseBytes( const uint64_t& x ) { return MP4V2_BYTESWAP_64( x ); }   
};

template <class ATTR>
bool FastReadAttr( MP4File& file, MP4PropertyArray& properties, int32_t numEntries )
{
   uint8_t buf[10000]; // use stack, since allocating on heap is slow
   
   uint32_t numProperties              = properties.Size();
   uint32_t propertySize               = sizeof( typename ATTR::PropertyType );
   uint32_t entrySize                  = propertySize * numProperties;
   int32_t  numEntriesThatFitInBuffer  = sizeof(buf) / entrySize;
   
   typename ATTR::PropertyType* p = NULL;
   for (int32_t i = 0; i < numEntries; i++) 
   {    
      if ( i % numEntriesThatFitInBuffer == 0 ) // refresh the buffer if necessary
      {
         int numEntriesLeft   = numEntries - i;
         int numEntriesToRead = numEntriesLeft < numEntriesThatFitInBuffer ? numEntriesLeft : numEntriesThatFitInBuffer;
         file.ReadBytes( buf, numEntriesToRead * entrySize ); 
         p = (typename ATTR::PropertyType*) buf;
      }
      for (uint32_t j = 0; j < numProperties; j++, p++) 
      {
         ((typename ATTR::MP4PropertyType*) properties[j])->SetValue( ATTR::ReverseBytes( *p ), i );
      }
   }
   return true;
}

bool MP4TableProperty::FastRead(MP4File& file)
{   
   uint32_t numProperties = m_pProperties.Size();
   if ( numProperties <= 0 )
      return false;
   
   MP4PropertyType  propType = m_pProperties[0]->GetType();

   // make sure all property types match
   for (uint32_t j = 0; j < numProperties; j++)
      if ( m_pProperties[j]->GetType() != propType )
         return false;

   // make sure no properties are implicit
   for (uint32_t j = 0; j < numProperties; j++)
      if ( m_pProperties[j]->IsImplicit() )
         return false;
   
   // make sure no properties are read-only
   for (uint32_t j = 0; j < numProperties; j++)
      if ( m_pProperties[j]->IsReadOnly() )
         return false;

   uint32_t numEntries = GetCount();
   
   if ( propType == Integer32Property )
   {
      return FastReadAttr<FastRead32Attr>( file, m_pProperties, numEntries );
   }
   else if ( propType == Integer64Property )
   {      
      return FastReadAttr<FastRead64Attr>( file, m_pProperties, numEntries );
   }

   return false;
}

void MP4TableProperty::Read(MP4File& file, uint32_t index)
{
    ASSERT(index == 0);

    if (m_implicit) {
        return;
    }
       
    uint32_t numProperties = m_pProperties.Size();

    if (numProperties == 0) {
        WARNING(numProperties == 0);
        return;
    }

    uint32_t numEntries = GetCount();

    /* for each property set size */
    for (uint32_t j = 0; j < numProperties; j++) {
        m_pProperties[j]->SetCount(numEntries);
    }
        
    bool fastReadSucceeded = FastRead(file);    
    if ( !fastReadSucceeded )
    {
        for (uint32_t i = 0; i < numEntries; i++) {
            ReadEntry(file, i);
        }
    }
}

void MP4TableProperty::ReadEntry(MP4File& file, uint32_t index)
{
    for (uint32_t j = 0; j < m_pProperties.Size(); j++) {
        m_pProperties[j]->Read(file, index);
    }
}

void MP4TableProperty::Write(MP4File& file, uint32_t index)
{
    ASSERT(index == 0);

    if (m_implicit) {
        return;
    }

    uint32_t numProperties = m_pProperties.Size();

    if (numProperties == 0) {
        WARNING(numProperties == 0);
        return;
    }

    uint32_t numEntries = GetCount();

    if (m_pProperties[0]->GetCount() != numEntries) {
        log.errorf("%s: \"%s\": %s %s \"%s\"table entries %u doesn't match count %u",
                   __FUNCTION__, m_parentAtom.GetFile().GetFilename().c_str(),
                   GetParentAtom().GetType(),
                   GetName(), m_pProperties[0]->GetName(),
                   m_pProperties[0]->GetCount(), numEntries);

        ASSERT(m_pProperties[0]->GetCount() == numEntries);
    }

    for (uint32_t i = 0; i < numEntries; i++) {
        WriteEntry(file, i);
    }
}

void MP4TableProperty::WriteEntry(MP4File& file, uint32_t index)
{
    for (uint32_t j = 0; j < m_pProperties.Size(); j++) {
        m_pProperties[j]->Write(file, index);
    }
}

void MP4TableProperty::Dump(uint8_t indent,
                            bool dumpImplicits, uint32_t index)
{
    ASSERT(index == 0);

    // implicit tables just can't be dumped
    if (m_implicit) {
        return;
    }

    uint32_t numProperties = m_pProperties.Size();

    if (numProperties == 0) {
        WARNING(numProperties == 0);
        return;
    }

    uint32_t numEntries = GetCount();

    for (uint32_t i = 0; i < numEntries; i++) {
        for (uint32_t j = 0; j < numProperties; j++) {
            m_pProperties[j]->Dump(indent + 1, dumpImplicits, i);
        }
    }
}

// MP4DescriptorProperty

MP4DescriptorProperty::MP4DescriptorProperty(MP4Atom& parentAtom, const char* name,
        uint8_t tagsStart, uint8_t tagsEnd, bool mandatory, bool onlyOne)
        : MP4Property(parentAtom, name)
{
    SetTags(tagsStart, tagsEnd);
    m_sizeLimit = 0;
    m_mandatory = mandatory;
    m_onlyOne = onlyOne;
}

MP4DescriptorProperty::~MP4DescriptorProperty()
{
    for (uint32_t i = 0; i < m_pDescriptors.Size(); i++) {
        delete m_pDescriptors[i];
    }
}

MP4Descriptor* MP4DescriptorProperty::AddDescriptor(uint8_t tag)
{
    // check that tag is in expected range
    ASSERT(tag >= m_tagsStart && tag <= m_tagsEnd);

    MP4Descriptor* pDescriptor = CreateDescriptor(m_parentAtom, tag);
    ASSERT(pDescriptor);

    m_pDescriptors.Add(pDescriptor);

    return pDescriptor;
}

void MP4DescriptorProperty::DeleteDescriptor(uint32_t index)
{
    delete m_pDescriptors[index];
    m_pDescriptors.Delete(index);
}

void MP4DescriptorProperty::Generate()
{
    // generate a default descriptor
    // if it is mandatory, and single
    if (m_mandatory && m_onlyOne) {
        MP4Descriptor* pDescriptor =
            AddDescriptor(m_tagsStart);
        pDescriptor->Generate();
    }
}

bool MP4DescriptorProperty::FindProperty(const char *name,
        MP4Property** ppProperty, uint32_t* pIndex)
{
    // we're unnamed, so just check contained properties
    if (m_name == NULL || !strcmp(m_name, "")) {
        return FindContainedProperty(name, ppProperty, pIndex);
    }

    // check if first component of name matches ourselves
    if (!MP4NameFirstMatches(m_name, name)) {
        return false;
    }

    // check if the specific descriptor entry exists
    uint32_t descrIndex;
    bool haveDescrIndex = MP4NameFirstIndex(name, &descrIndex);

    if (haveDescrIndex && descrIndex >= GetCount()) {
        return false;
    }

    log.verbose1f("\"%s\": matched %s",
                  m_parentAtom.GetFile().GetFilename().c_str(),
                  name);

    // get name of descriptor property
    name = MP4NameAfterFirst(name);
    if (name == NULL) {
        if (!haveDescrIndex) {
            *ppProperty = this;
            return true;
        }
        return false;
    }

    /* check rest of name */
    if (haveDescrIndex) {
        return m_pDescriptors[descrIndex]->FindProperty(name,
                ppProperty, pIndex);
    } else {
        return FindContainedProperty(name, ppProperty, pIndex);
    }
}

bool MP4DescriptorProperty::FindContainedProperty(const char *name,
        MP4Property** ppProperty, uint32_t* pIndex)
{
    for (uint32_t i = 0; i < m_pDescriptors.Size(); i++) {
        if (m_pDescriptors[i]->FindProperty(name, ppProperty, pIndex)) {
            return true;
        }
    }
    return false;
}

void MP4DescriptorProperty::Read(MP4File& file, uint32_t index)
{
    ASSERT(index == 0);

    if (m_implicit) {
        return;
    }

    uint64_t start = file.GetPosition();

    while (true) {
        // enforce size limitation
        if (m_sizeLimit && file.GetPosition() >= start + m_sizeLimit) {
            break;
        }

        uint8_t tag;
        try {
            file.PeekBytes(&tag, 1);
        }
        catch (Exception* x) {
            if (file.GetPosition() >= file.GetSize()) {
                // EOF
                delete x;
                break;
            }
            throw x;
        }

        // check if tag is in desired range
        if (tag < m_tagsStart || tag > m_tagsEnd) {
            break;
        }

        MP4Descriptor* pDescriptor =
            AddDescriptor(tag);

        pDescriptor->Read(file);
    }

    // warnings
    if (m_mandatory && m_pDescriptors.Size() == 0) {
        log.warningf("%s: \"%s\": Mandatory descriptor 0x%02x missing",
                     __FUNCTION__, GetParentAtom().GetFile().GetFilename().c_str(), m_tagsStart);
    } else if (m_onlyOne && m_pDescriptors.Size() > 1) {
        log.warningf("%s: \"%s\": Descriptor 0x%02x has more than one instance",
                     __FUNCTION__, GetParentAtom().GetFile().GetFilename().c_str(), m_tagsStart);
    }
}

void MP4DescriptorProperty::Write(MP4File& file, uint32_t index)
{
    ASSERT(index == 0);

    if (m_implicit) {
        return;
    }

    for (uint32_t i = 0; i < m_pDescriptors.Size(); i++) {
        m_pDescriptors[i]->Write(file);
    }
}

void MP4DescriptorProperty::Dump(uint8_t indent,
                                 bool dumpImplicits, uint32_t index)
{
    ASSERT(index == 0);

    if (m_implicit && !dumpImplicits) {
        return;
    }

    if (m_name) {
        if (index != 0)
            log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s[%u]",
                     m_parentAtom.GetFile().GetFilename().c_str(),
                     m_name, index);
        else
            log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s",
                     m_parentAtom.GetFile().GetFilename().c_str(),
                     m_name);
        indent++;
    }

    for (uint32_t i = 0; i < m_pDescriptors.Size(); i++) {
        m_pDescriptors[i]->Dump(indent, dumpImplicits);
    }
}

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

MP4LanguageCodeProperty::MP4LanguageCodeProperty( MP4Atom& parentAtom, const char* name, bmff::LanguageCode value )
    : MP4Property( parentAtom, name )
{
    SetValue( value );
}

MP4LanguageCodeProperty::MP4LanguageCodeProperty( MP4Atom& parentAtom, const char* name, const string& code )
    : MP4Property( parentAtom, name )
{
    SetValue( bmff::enumLanguageCode.toType( code ));
}

void
MP4LanguageCodeProperty::Dump( uint8_t indent, bool dumpImplicits, uint32_t index )
{
    uint16_t data = 0;

    string svalue;
    bmff::enumLanguageCode.toString( _value, svalue );
    if( svalue.length() == 3 ) {
        data = (((svalue[0] - 0x60) & 0x001f) << 10)
             | (((svalue[1] - 0x60) & 0x001f) <<  5)
             | (((svalue[2] - 0x60) & 0x001f)      );
    }

    log.dump(indent, MP4_LOG_VERBOSE2, "\"%s\": %s = %s (0x%04x)",
             m_parentAtom.GetFile().GetFilename().c_str(),
             m_name, bmff::enumLanguageCode.toString( _value, true ).c_str(), data );
}

uint32_t
MP4LanguageCodeProperty::GetCount()
{
    return 1;
}

MP4PropertyType
MP4LanguageCodeProperty::GetType()
{
    return LanguageCodeProperty;
}

bmff::LanguageCode
MP4LanguageCodeProperty::GetValue()
{
    return _value;
}

void
MP4LanguageCodeProperty::Read( MP4File& file, uint32_t index )
{
    uint16_t data = file.ReadBits( 16 );

    char code[3];
    code[0] = ((data & 0x7c00) >> 10) + 0x60;
    code[1] = ((data & 0x03e0) >>  5) + 0x60;
    code[2] = ((data & 0x001f)      ) + 0x60;

    SetValue( bmff::enumLanguageCode.toType( string( code, sizeof(code) )));
}

void
MP4LanguageCodeProperty::SetCount( uint32_t count )
{
    // do nothing; count is always 1
}

void
MP4LanguageCodeProperty::SetValue( bmff::LanguageCode value )
{
    _value = value;
}

void
MP4LanguageCodeProperty::Write( MP4File& file, uint32_t index )
{
    uint16_t data = 0;

    string svalue;
    bmff::enumLanguageCode.toString( _value, svalue );
    if( svalue.length() == 3 ) {
        data = (((svalue[0] - 0x60) & 0x001f) << 10)
             | (((svalue[1] - 0x60) & 0x001f) <<  5)
             | (((svalue[2] - 0x60) & 0x001f)      );
    }

    file.WriteBits( data, 16 );
}

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

MP4BasicTypeProperty::MP4BasicTypeProperty( MP4Atom& parentAtom, const char* name, itmf::BasicType type )
    : MP4Property( parentAtom, name )
{
    SetValue( type );
}

void
MP4BasicTypeProperty::Dump( uint8_t indent, bool dumpImplicits, uint32_t index )
{
    log.dump(indent, MP4_LOG_VERBOSE1, "\"%s\": %s = %s (0x%02x)",
             m_parentAtom.GetFile().GetFilename().c_str(), m_name,
             itmf::enumBasicType.toString( _value, true ).c_str(), _value );
}

uint32_t
MP4BasicTypeProperty::GetCount()
{
    return 1;
}

MP4PropertyType
MP4BasicTypeProperty::GetType()
{
    return BasicTypeProperty;
}

itmf::BasicType
MP4BasicTypeProperty::GetValue()
{
    return _value;
}

void
MP4BasicTypeProperty::Read( MP4File& file, uint32_t index )
{
    SetValue( static_cast<itmf::BasicType>( file.ReadBits( 8 )));
}

void
MP4BasicTypeProperty::SetCount( uint32_t count )
{
    // do nothing; count is always 1
}

void
MP4BasicTypeProperty::SetValue( itmf::BasicType value )
{
    _value = value;
}

void
MP4BasicTypeProperty::Write( MP4File& file, uint32_t index )
{
    file.WriteBits( _value, 8 );
}

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

}} // namespace mp4v2::impl
