/*
 * 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 __MP4_PROPERTY_INCLUDED__
#define __MP4_PROPERTY_INCLUDED__

// forward declarations
class MP4Atom;
class MP4File;

class MP4Descriptor;
MP4ARRAY_DECL(MP4Descriptor, MP4Descriptor*);

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

class MP4Property {
public:
	MP4Property(const char *name = NULL);

	virtual ~MP4Property() { }

	MP4Atom* GetParentAtom() {
		return m_pParentAtom;
	}
	virtual void SetParentAtom(MP4Atom* pParentAtom) {
		m_pParentAtom = pParentAtom;
	}

	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 u_int32_t GetCount() = 0;
	virtual void SetCount(u_int32_t count) = 0;

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

	virtual void Read(MP4File* pFile, u_int32_t index = 0) = 0;

	virtual void Write(MP4File* pFile, u_int32_t index = 0) = 0;

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

protected:
	MP4Atom* m_pParentAtom;
	const char* m_name;
	bool m_readOnly;
	bool m_implicit;
};

MP4ARRAY_DECL(MP4Property, MP4Property*);

class MP4IntegerProperty : public MP4Property {
protected:
	MP4IntegerProperty(char* name)
		: MP4Property(name) { };

public:
	u_int64_t GetValue(u_int32_t index = 0);

	void SetValue(u_int64_t value, u_int32_t index = 0);

	void InsertValue(u_int64_t value, u_int32_t index = 0);

	void DeleteValue(u_int32_t index = 0);

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

template <class val_t, u_int8_t size, MP4PropertyType prop_type>
class MP4IntegerPropertyT : public MP4IntegerProperty { 
	public: 
		MP4IntegerPropertyT(char* name) 
			: MP4IntegerProperty(name) { 
			SetCount(1); 
			m_values[0] = 0; 
		} 
		
		MP4PropertyType GetType() { 
			//return Integer##xsize##Property; 
			return prop_type;
		} 
		
		u_int32_t GetCount() { 
			return m_values.Size(); 
		} 

		void SetCount(u_int32_t count) { 
			m_values.Resize(count); 
		} 
		
		val_t GetValue(u_int32_t index = 0) { 
			return m_values[index]; 
		}
		
		void SetValue(val_t value, u_int32_t index = 0) 
		{ 
			if (m_readOnly) { 
				throw new MP4Error(EACCES, "property is read-only", m_name); \
			} 
			m_values[index] = value; 
		} 
		void AddValue(val_t value) { 
			m_values.Add(value); 
		} 
		void InsertValue(val_t value, u_int32_t index) { 
			m_values.Insert(value, index); 
		} 
		void DeleteValue(u_int32_t index) { 
			m_values.Delete(index); 
		} 
		void IncrementValue(int32_t increment = 1, u_int32_t index = 0) { 
			m_values[index] += increment; 
		} 
		void Read(MP4File* pFile, u_int32_t index = 0) { 
			if (m_implicit) { 
				return; 
			} 
			m_values[index] = (val_t)pFile->ReadUInt(size/8);
		} 
		
		void Write(MP4File* pFile, u_int32_t index = 0) { 
			if (m_implicit) { 
				return; 
			} 
			pFile->WriteUInt((u_int64_t)m_values[index], size/8);
		} 
	
	protected: 
		MP4TArray<val_t> m_values;
	};

#define MP4INTEGER_PROPERTY_DECL(val_t, xsize)  typedef MP4IntegerPropertyT<val_t, xsize, Integer##xsize##Property> MP4Integer##xsize##Property;

MP4INTEGER_PROPERTY_DECL(u_int8_t, 8);
MP4INTEGER_PROPERTY_DECL(u_int16_t, 16);
MP4INTEGER_PROPERTY_DECL(u_int32_t, 24);
MP4INTEGER_PROPERTY_DECL(u_int32_t, 32);
MP4INTEGER_PROPERTY_DECL(u_int64_t, 64);

class MP4BitfieldProperty : public MP4Integer64Property {
public:
	MP4BitfieldProperty(char* name, u_int8_t numBits)
		: MP4Integer64Property(name) {
		ASSERT(numBits != 0);
		ASSERT(numBits <= 64);
		m_numBits = numBits;
	}

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

	void Read(MP4File* pFile, u_int32_t index = 0);
	void Write(MP4File* pFile, u_int32_t index = 0);

protected:
	u_int8_t m_numBits;
};

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

	MP4PropertyType GetType() {
		return Float32Property;
	}

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

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

	void SetValue(float value, u_int32_t index = 0) {
		if (m_readOnly) {
			throw new MP4Error(EACCES, "property is read-only", m_name);
		}
		m_values[index] = value;
	}

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

	void InsertValue(float value, u_int32_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* pFile, u_int32_t index = 0);
	void Write(MP4File* pFile, u_int32_t index = 0);

protected:
	bool m_useFixed16Format;
	bool m_useFixed32Format;
	MP4Float32Array m_values;
};

class MP4StringProperty : public MP4Property {
public:
	MP4StringProperty(char* name, 
	  bool useCountedFormat = false, bool useUnicode = false);

	~MP4StringProperty();

	MP4PropertyType GetType() {
		return StringProperty;
	}

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

	void SetCount(u_int32_t count);

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

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

	void AddValue(const char* value) {
		u_int32_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;
	}

	u_int32_t GetFixedLength() {
		return m_fixedLength;
	}

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

	void Read(MP4File* pFile, u_int32_t index = 0);
	void Write(MP4File* pFile, u_int32_t index = 0);

protected:
	bool m_useCountedFormat;
	bool m_useExpandedCount;
	bool m_useUnicode;
	u_int32_t m_fixedLength;

	MP4StringArray m_values;
};

class MP4BytesProperty : public MP4Property {
public:
	MP4BytesProperty(char* name, u_int32_t valueSize = 0,
                         u_int32_t defaultValueSize = 0);

	~MP4BytesProperty();

	MP4PropertyType GetType() {
		return BytesProperty;
	}

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

	void SetCount(u_int32_t count);

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

	void CopyValue(u_int8_t* pValue, u_int32_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 u_int8_t* pValue, u_int32_t valueSize, 
		u_int32_t index = 0);

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

	u_int32_t GetValueSize(u_int32_t valueSize, u_int32_t index = 0) {
		return m_valueSizes[index];
	}

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

	u_int32_t GetFixedSize() {
		return m_fixedValueSize;
	}

	void SetFixedSize(u_int32_t fixedSize);

	void Read(MP4File* pFile, u_int32_t index = 0);
	void Write(MP4File* pFile, u_int32_t index = 0);

protected:
	u_int32_t		m_fixedValueSize;
	u_int32_t		m_defaultValueSize;
	MP4Integer32Array	m_valueSizes;
	MP4BytesArray		m_values;
};

class MP4TableProperty : public MP4Property {
public:
	MP4TableProperty(char* name, MP4IntegerProperty* pCountProperty);

	~MP4TableProperty();

	MP4PropertyType GetType() {
		return TableProperty;
	}

	void SetParentAtom(MP4Atom* pParentAtom) {
		m_pParentAtom = pParentAtom;
		for (u_int32_t i = 0; i < m_pProperties.Size(); i++) {
			m_pProperties[i]->SetParentAtom(pParentAtom);
		}
	}

	void AddProperty(MP4Property* pProperty);

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

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

	void Read(MP4File* pFile, u_int32_t index = 0);
	void Write(MP4File* pFile, u_int32_t index = 0);

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

protected:
	virtual void ReadEntry(MP4File* pFile, u_int32_t index);
	virtual void WriteEntry(MP4File* pFile, u_int32_t index);

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

protected:
	MP4IntegerProperty*	m_pCountProperty;
	MP4PropertyArray	m_pProperties;
};

class MP4DescriptorProperty : public MP4Property {
public:
	MP4DescriptorProperty(char* name = NULL, 
	  u_int8_t tagsStart = 0, u_int8_t tagsEnd = 0,
	  bool mandatory = false, bool onlyOne = false);

	~MP4DescriptorProperty();

	MP4PropertyType GetType() {
		return DescriptorProperty;
	}

	void SetParentAtom(MP4Atom* pParentAtom);

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

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

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

	MP4Descriptor* AddDescriptor(u_int8_t tag);

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

	void DeleteDescriptor(u_int32_t index);

	void Generate();
	void Read(MP4File* pFile, u_int32_t index = 0);
	void Write(MP4File* pFile, u_int32_t index = 0);

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

protected:
	virtual MP4Descriptor* CreateDescriptor(u_int8_t tag);

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

protected:
	u_int8_t			m_tagsStart;
	u_int8_t			m_tagsEnd;
	u_int64_t			m_sizeLimit;
	bool				m_mandatory;
	bool				m_onlyOne;
	MP4DescriptorArray	m_pDescriptors;
};

class MP4QosQualifierProperty : public MP4DescriptorProperty {
public:
	MP4QosQualifierProperty(char* name = NULL, 
	  u_int8_t tagsStart = 0, u_int8_t tagsEnd = 0,
	  bool mandatory = false, bool onlyOne = false) :
	MP4DescriptorProperty(name, tagsStart, tagsEnd, mandatory, onlyOne) { }

protected:
	MP4Descriptor* CreateDescriptor(u_int8_t tag);
};

#endif /* __MP4_PROPERTY_INCLUDED__ */