/*
 * 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
 */

#ifndef MP4V2_IMPL_MP4PROPERTY_H
#define MP4V2_IMPL_MP4PROPERTY_H

namespace mp4v2 { namespace impl {

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

// forward declarations
class MP4Atom;

class MP4Descriptor;
typedef MP4Array<MP4Descriptor*> MP4DescriptorArray;

enum MP4PropertyType {
    Integer8Property,
    Integer16Property,
    Integer24Property,
    Integer32Property,
    Integer64Property,
    Float32Property,
    StringProperty,
    BytesProperty,
    TableProperty,
    DescriptorProperty,
    LanguageCodeProperty,
    BasicTypeProperty
};

class MP4Property {
public:
    MP4Property(MP4Atom& parentAtom, const char *name = NULL);

    virtual ~MP4Property() { }

    MP4Atom& GetParentAtom() {
        return m_parentAtom;
    }

    const char *GetName() {
        return m_name;
    }

    virtual MP4PropertyType GetType() = 0;

    bool IsReadOnly() {
        return m_readOnly;
    }
    void SetReadOnly(bool value = true) {
        m_readOnly = value;
    }

    bool IsImplicit() {
        return m_implicit;
    }
    void SetImplicit(bool value = true) {
        m_implicit = value;
    }

    virtual uint32_t GetCount() = 0;
    virtual void SetCount(uint32_t count) = 0;

    virtual void Generate() { /* default is a no-op */ };

    virtual void Read(MP4File& file, uint32_t index = 0) = 0;

    virtual void Write(MP4File& file, uint32_t index = 0) = 0;

    virtual void Dump(uint8_t indent,
                      bool dumpImplicits, uint32_t index = 0) = 0;

    virtual bool FindProperty(const char* name,
                              MP4Property** ppProperty, uint32_t* pIndex = NULL);

protected:
    MP4Atom& m_parentAtom;
    const char* m_name;
    bool m_readOnly;
    bool m_implicit;

private:
    MP4Property();
    MP4Property ( const MP4Property &src );
    MP4Property &operator= ( const MP4Property &src );
};

typedef MP4Array<MP4Property*> MP4PropertyArray;

class MP4IntegerProperty : public MP4Property {
protected:
    MP4IntegerProperty(MP4Atom& parentAtom, const char* name)
            : MP4Property(parentAtom, name) { };

public:
    uint64_t GetValue(uint32_t index = 0);

    void SetValue(uint64_t value, uint32_t index = 0);

    void InsertValue(uint64_t value, uint32_t index = 0);

    void DeleteValue(uint32_t index = 0);

    void IncrementValue(int32_t increment = 1, uint32_t index = 0);

private:
    MP4IntegerProperty();
    MP4IntegerProperty ( const MP4IntegerProperty &src );
    MP4IntegerProperty &operator= ( const MP4IntegerProperty &src );
};

template <class type, int size> class MP4SizedIntegerProperty : public MP4IntegerProperty {
public:
    MP4SizedIntegerProperty(MP4Atom& parentAtom, const char* name)
        : MP4IntegerProperty(parentAtom, name) {
        SetCount(1);
        m_values[0] = 0;
    }

    MP4PropertyType GetType() {
        switch (size) {
            case 8: return Integer8Property;
            case 16: return Integer16Property;
            case 24: return Integer24Property;
            case 32: return Integer32Property;
            case 64: return Integer64Property;
        }
        return Integer32Property;
    }

    uint32_t GetCount() {
        return m_values.Size();
    }

    void SetCount(uint32_t count) {
        m_values.Resize(count);
    }

    type GetValue(uint32_t index = 0) {
        return m_values[index];
    }

    void SetValue(type value, uint32_t index = 0) {
        if (m_readOnly) { 
            ostringstream msg;
            msg << "property is read-only: " << m_name;
            throw new PLATFORM_EXCEPTION(msg.str().c_str(), EACCES);
        }
        m_values[index] = value;
    }

    void AddValue(type value) {
        m_values.Add(value);
    }

    void InsertValue(type value, uint32_t index) {
        m_values.Insert(value, index);
    }

    void DeleteValue(uint32_t index) {
        m_values.Delete(index);
    }

    void IncrementValue(int32_t increment = 1, uint32_t index = 0) {
        m_values[index] += increment;
    }

    void Read(MP4File& file, uint32_t index = 0) {
        if (m_implicit) {
            return;
        }
        m_values[index] = file.ReadUInt<type, size>();
    }

    void Write(MP4File& file, uint32_t index = 0) {
        if (m_implicit) {
            return;
        }
        file.WriteUInt<type, size>(m_values[index]);
    }

    void Dump(uint8_t indent,
        bool dumpImplicits, uint32_t index = 0);

protected:
    MP4Array<type> m_values;

private:
    MP4SizedIntegerProperty();
    MP4SizedIntegerProperty ( const MP4SizedIntegerProperty &src );
    MP4SizedIntegerProperty &operator= ( const MP4SizedIntegerProperty &src );
};

typedef MP4SizedIntegerProperty<uint8_t, 8> MP4Integer8Property;
typedef MP4SizedIntegerProperty<uint16_t, 16> MP4Integer16Property;
typedef MP4SizedIntegerProperty<uint32_t, 24> MP4Integer24Property;
typedef MP4SizedIntegerProperty<uint32_t, 32> MP4Integer32Property;
typedef MP4SizedIntegerProperty<uint64_t, 64> MP4Integer64Property;

class MP4BitfieldProperty : public MP4Integer64Property {
public:
    MP4BitfieldProperty(MP4Atom& parentAtom, const char* name, uint8_t numBits)
            : MP4Integer64Property(parentAtom, name) {
        ASSERT(numBits != 0);
        ASSERT(numBits <= 64);
        m_numBits = numBits;
    }

    uint8_t GetNumBits() {
        return m_numBits;
    }
    void SetNumBits(uint8_t numBits) {
        m_numBits = numBits;
    }

    void Read(MP4File& file, uint32_t index = 0);
    void Write(MP4File& file, uint32_t index = 0);
    void Dump(uint8_t indent,
              bool dumpImplicits, uint32_t index = 0);

protected:
    uint8_t m_numBits;

private:
    MP4BitfieldProperty();
    MP4BitfieldProperty ( const MP4BitfieldProperty &src );
    MP4BitfieldProperty &operator= ( const MP4BitfieldProperty &src );
};

class MP4Float32Property : public MP4Property {
public:
    MP4Float32Property(MP4Atom& parentAtom, const char* name)
            : MP4Property(parentAtom, name) {
        m_useFixed16Format = false;
        m_useFixed32Format = false;
        SetCount(1);
        m_values[0] = 0.0;
    }

    MP4PropertyType GetType() {
        return Float32Property;
    }

    uint32_t GetCount() {
        return m_values.Size();
    }
    void SetCount(uint32_t count) {
        m_values.Resize(count);
    }

    float GetValue(uint32_t index = 0) {
        return m_values[index];
    }

    void SetValue(float value, uint32_t index = 0) {
        if (m_readOnly) {
            ostringstream msg;
            msg << "property is read-only: " << m_name;
            throw new PLATFORM_EXCEPTION(msg.str().c_str(), EACCES);
        }
        m_values[index] = value;
    }

    void AddValue(float value) {
        m_values.Add(value);
    }

    void InsertValue(float value, uint32_t index) {
        m_values.Insert(value, index);
    }

    bool IsFixed16Format() {
        return m_useFixed16Format;
    }

    void SetFixed16Format(bool useFixed16Format = true) {
        m_useFixed16Format = useFixed16Format;
    }

    bool IsFixed32Format() {
        return m_useFixed32Format;
    }

    void SetFixed32Format(bool useFixed32Format = true) {
        m_useFixed32Format = useFixed32Format;
    }

    void Read(MP4File& file, uint32_t index = 0);
    void Write(MP4File& file, uint32_t index = 0);
    void Dump(uint8_t indent,
              bool dumpImplicits, uint32_t index = 0);

protected:
    bool m_useFixed16Format;
    bool m_useFixed32Format;
    MP4Float32Array m_values;

private:
    MP4Float32Property();
    MP4Float32Property ( const MP4Float32Property &src );
    MP4Float32Property &operator= ( const MP4Float32Property &src );
};

class MP4StringProperty : public MP4Property {
public:
    MP4StringProperty(MP4Atom& parentAtom, const char* name,
                      bool useCountedFormat = false, bool useUnicode = false, bool arrayMode = false);

    ~MP4StringProperty();

    MP4PropertyType GetType() {
        return StringProperty;
    }

    uint32_t GetCount() {
        return m_values.Size();
    }

    void SetCount(uint32_t count);

    const char* GetValue(uint32_t index = 0) {
        return m_values[index];
    }

    void SetValue(const char* value, uint32_t index = 0);

    void AddValue(const char* value) {
        uint32_t count = GetCount();
        SetCount(count + 1);
        SetValue(value, count);
    }

    bool IsCountedFormat() {
        return m_useCountedFormat;
    }

    void SetCountedFormat(bool useCountedFormat) {
        m_useCountedFormat = useCountedFormat;
    }

    bool IsExpandedCountedFormat() {
        return m_useExpandedCount;
    }

    void SetExpandedCountedFormat(bool useExpandedCount) {
        m_useExpandedCount = useExpandedCount;
    }

    bool IsUnicode() {
        return m_useUnicode;
    }

    void SetUnicode(bool useUnicode) {
        m_useUnicode = useUnicode;
    }

    uint32_t GetFixedLength() {
        return m_fixedLength;
    }

    void SetFixedLength(uint32_t fixedLength) {
        m_fixedLength = fixedLength;
    }

    void Read(MP4File& file, uint32_t index = 0);
    void Write(MP4File& file, uint32_t index = 0);
    void Dump(uint8_t indent,
              bool dumpImplicits, uint32_t index = 0);

protected:
    bool m_arrayMode; // during read/write ignore index and read/write full array
    bool m_useCountedFormat;
    bool m_useExpandedCount;
    bool m_useUnicode;
    uint32_t m_fixedLength;

    MP4StringArray m_values;

private:
    MP4StringProperty();
    MP4StringProperty ( const MP4StringProperty &src );
    MP4StringProperty &operator= ( const MP4StringProperty &src );
};

class MP4BytesProperty : public MP4Property {
public:
    MP4BytesProperty(MP4Atom& parentAtom, const char* name, uint32_t valueSize = 0,
                     uint32_t defaultValueSize = 0);

    ~MP4BytesProperty();

    MP4PropertyType GetType() {
        return BytesProperty;
    }

    uint32_t GetCount() {
        return m_values.Size();
    }

    void SetCount(uint32_t count);

    void GetValue(uint8_t** ppValue, uint32_t* pValueSize,
                  uint32_t index = 0) {
        // N.B. caller must free memory
        *ppValue = (uint8_t*)MP4Malloc(m_valueSizes[index]);
        memcpy(*ppValue, m_values[index], m_valueSizes[index]);
        *pValueSize = m_valueSizes[index];
    }

    char* GetValueStringAlloc( uint32_t index = 0 ) {
        char* buf = (char*)MP4Malloc( m_valueSizes[index] + 1 );
        memcpy( buf, m_values[index], m_valueSizes[index] );
        buf[m_valueSizes[index]] = '\0';
        return buf;
    }

    bool CompareToString( const string& s, uint32_t index = 0 ) {
        return string( (const char*)m_values[index], m_valueSizes[index] ) != s;
    }

    void CopyValue(uint8_t* pValue, uint32_t index = 0) {
        // N.B. caller takes responsbility for valid pointer
        // and sufficient memory at the destination
        memcpy(pValue, m_values[index], m_valueSizes[index]);
    }

    void SetValue(const uint8_t* pValue, uint32_t valueSize,
                  uint32_t index = 0);

    void AddValue(const uint8_t* pValue, uint32_t valueSize) {
        uint32_t count = GetCount();
        SetCount(count + 1);
        SetValue(pValue, valueSize, count);
    }

    uint32_t GetValueSize( uint32_t index = 0 ) {
        return m_valueSizes[index];
    }

    void SetValueSize(uint32_t valueSize, uint32_t index = 0);

    uint32_t GetFixedSize() {
        return m_fixedValueSize;
    }

    void SetFixedSize(uint32_t fixedSize);

    void Read(MP4File& file, uint32_t index = 0);
    void Write(MP4File& file, uint32_t index = 0);
    void Dump(uint8_t indent,
              bool dumpImplicits, uint32_t index = 0);

protected:
    uint32_t        m_fixedValueSize;
    uint32_t        m_defaultValueSize;
    MP4Integer32Array   m_valueSizes;
    MP4BytesArray       m_values;

private:
    MP4BytesProperty();
    MP4BytesProperty ( const MP4BytesProperty &src );
    MP4BytesProperty &operator= ( const MP4BytesProperty &src );
};

class MP4TableProperty : public MP4Property {
public:
    MP4TableProperty(MP4Atom& parentAtom, const char* name, MP4IntegerProperty* pCountProperty);

    ~MP4TableProperty();

    MP4PropertyType GetType() {
        return TableProperty;
    }

    void AddProperty(MP4Property* pProperty);

    MP4Property* GetProperty(uint32_t index) {
        return m_pProperties[index];
    }

    virtual uint32_t GetCount() {
        return m_pCountProperty->GetValue();
    }
    virtual void SetCount(uint32_t count) {
        m_pCountProperty->SetValue(count);
    }

    void Read(MP4File& file, uint32_t index = 0);
    void Write(MP4File& file, uint32_t index = 0);
    void Dump(uint8_t indent,
              bool dumpImplicits, uint32_t index = 0);

    bool FindProperty(const char* name,
                      MP4Property** ppProperty, uint32_t* pIndex = NULL);

protected:
    virtual void ReadEntry(MP4File& file, uint32_t index);
    virtual void WriteEntry(MP4File& file, uint32_t index);

    bool FindContainedProperty(const char* name,
                               MP4Property** ppProperty, uint32_t* pIndex);

protected:
    MP4IntegerProperty* m_pCountProperty;
    MP4PropertyArray    m_pProperties;

private:
    MP4TableProperty();
    MP4TableProperty ( const MP4TableProperty &src );
    MP4TableProperty &operator= ( const MP4TableProperty &src );
};

class MP4DescriptorProperty : public MP4Property {
public:
    MP4DescriptorProperty(MP4Atom& parentAtom, const char* name = NULL,
                          uint8_t tagsStart = 0, uint8_t tagsEnd = 0,
                          bool mandatory = false, bool onlyOne = false);

    ~MP4DescriptorProperty();

    MP4PropertyType GetType() {
        return DescriptorProperty;
    }

    void SetParentAtom(MP4Atom* pParentAtom);

    void SetSizeLimit(uint64_t sizeLimit) {
        m_sizeLimit = sizeLimit;
    }

    uint32_t GetCount() {
        return m_pDescriptors.Size();
    }
    void SetCount(uint32_t count) {
        m_pDescriptors.Resize(count);
    }

    void SetTags(uint8_t tagsStart, uint8_t tagsEnd = 0) {
        m_tagsStart = tagsStart;
        m_tagsEnd = tagsEnd ? tagsEnd : tagsStart;
    }

    MP4Descriptor* AddDescriptor(uint8_t tag);

    void AppendDescriptor(MP4Descriptor* pDescriptor) {
        m_pDescriptors.Add(pDescriptor);
    }

    void DeleteDescriptor(uint32_t index);

    void Generate();
    void Read(MP4File& file, uint32_t index = 0);
    void Write(MP4File& file, uint32_t index = 0);
    void Dump(uint8_t indent,
              bool dumpImplicits, uint32_t index = 0);

    bool FindProperty(const char* name,
                      MP4Property** ppProperty, uint32_t* pIndex = NULL);

protected:
    virtual MP4Descriptor* CreateDescriptor(MP4Atom& parentAtom, uint8_t tag);

    bool FindContainedProperty(const char* name,
                               MP4Property** ppProperty, uint32_t* pIndex);

protected:
    uint8_t         m_tagsStart;
    uint8_t         m_tagsEnd;
    uint64_t            m_sizeLimit;
    bool                m_mandatory;
    bool                m_onlyOne;
    MP4DescriptorArray  m_pDescriptors;

private:
    MP4DescriptorProperty();
    MP4DescriptorProperty ( const MP4DescriptorProperty &src );
    MP4DescriptorProperty &operator= ( const MP4DescriptorProperty &src );
};

class MP4QosQualifierProperty : public MP4DescriptorProperty {
public:
    MP4QosQualifierProperty(MP4Atom& parentAtom, const char* name = NULL,
                            uint8_t tagsStart = 0, uint8_t tagsEnd = 0,
                            bool mandatory = false, bool onlyOne = false) :
            MP4DescriptorProperty(parentAtom, name, tagsStart, tagsEnd, mandatory, onlyOne) { }

protected:
    MP4Descriptor* CreateDescriptor(MP4Atom& parentAtom, uint8_t tag);

private:
    MP4QosQualifierProperty();
    MP4QosQualifierProperty ( const MP4QosQualifierProperty &src );
    MP4QosQualifierProperty &operator= ( const MP4QosQualifierProperty &src );
};

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

/// ISO-639-2/T language code.
/// Language codes are 3-alpha (always lowercase) codes which are then
/// offset using 0x60 and packed as 5-bit values into 16-bits, most
/// significant bit is zero-padding.

class MP4LanguageCodeProperty : public MP4Property {
private:
    bmff::LanguageCode _value;

public:
    explicit MP4LanguageCodeProperty( MP4Atom& parentAtom, const char* , bmff::LanguageCode = bmff::ILC_UND );
    MP4LanguageCodeProperty( MP4Atom& parentAtom, const char* , const string& );

    MP4PropertyType GetType();
    uint32_t        GetCount();
    void            SetCount( uint32_t );
    void            Read( MP4File&, uint32_t = 0 );
    void            Write( MP4File&, uint32_t = 0 );
    void            Dump( uint8_t, bool, uint32_t = 0 );

    bmff::LanguageCode GetValue();
    void               SetValue( bmff::LanguageCode );

private:
    MP4LanguageCodeProperty();
    MP4LanguageCodeProperty ( const MP4LanguageCodeProperty &src );
    MP4LanguageCodeProperty &operator= ( const MP4LanguageCodeProperty &src );
};

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

class MP4BasicTypeProperty : public MP4Property {
private:
    itmf::BasicType _value;

public:
    explicit MP4BasicTypeProperty( MP4Atom& parentAtom, const char* , itmf::BasicType = itmf::BT_UNDEFINED );

    MP4PropertyType GetType();
    uint32_t        GetCount();
    void            SetCount( uint32_t );
    void            Read( MP4File&, uint32_t = 0 );
    void            Write( MP4File&, uint32_t = 0 );
    void            Dump( uint8_t, bool, uint32_t = 0 );
    itmf::BasicType GetValue();
    void            SetValue( itmf::BasicType );

private:
    MP4BasicTypeProperty();
    MP4BasicTypeProperty ( const MP4BasicTypeProperty &src );
    MP4BasicTypeProperty &operator= ( const MP4BasicTypeProperty &src );
};

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

}} // namespace mp4v2::impl

#endif // MP4V2_IMPL_MP4PROPERTY_H
