/* 
 *	Copyright (C) 2003-2006 Gabest
 *	http://www.gabest.org
 *
 *  This Program 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.
 *   
 *  This Program 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#pragma once

#include <atlbase.h>
#include <atlcoll.h>

namespace MatroskaWriter
{
	typedef unsigned __int64 QWORD;

	class CID
	{
	protected:
		DWORD m_id;
		QWORD HeaderSize(QWORD len);
		HRESULT HeaderWrite(IStream* pStream);

	public:
		CID(DWORD id);
		DWORD GetID() {return m_id;}
		virtual QWORD Size(bool fWithHeader = true);
		virtual HRESULT Write(IStream* pStream);
	};

	class CLength : public CID
	{
		UINT64 m_len;
	public: 
		CLength(UINT64 len = 0) : CID(0), m_len(len) {}
		operator UINT64() {return m_len;}
		QWORD Size(bool fWithHeader = false);
		HRESULT Write(IStream* pStream);
	};

	class CBinary : public CAtlArray<BYTE>, public CID
	{
	public:
		CBinary(DWORD id) : CID(id) {}
		CBinary& operator = (const CBinary& b) {Copy(b); return(*this);}
		operator BYTE*() {return (BYTE*)GetData();}
		CBinary& Set(CStringA str) {SetCount(str.GetLength()+1); strcpy((char*)GetData(), str); return(*this);}
//		CBinary& Set(CStringA str) {SetCount(str.GetLength()); memcpy((char*)GetData(), str, str.GetLength()); return(*this);}
		QWORD Size(bool fWithHeader = true);
		HRESULT Write(IStream* pStream);
	};

	class CANSI : public CStringA, public CID
	{
	public: 
		CANSI(DWORD id) : CID(id) {}
		CANSI& Set(CStringA str) {CStringA::operator = (str); return(*this);}
		QWORD Size(bool fWithHeader = true);
		HRESULT Write(IStream* pStream);
	};

	class CUTF8 : public CStringW, public CID
	{
	public: 
		CUTF8(DWORD id) : CID(id) {}
		CUTF8& Set(CStringW str) {CStringW::operator = (str); return(*this);}
		QWORD Size(bool fWithHeader = true); 
		HRESULT Write(IStream* pStream);
	};

	template<class T, class BASE>
	class CSimpleVar : public CID
	{
	protected:
		T m_val;
		bool m_fSet;
	public:
		explicit CSimpleVar(DWORD id, T val = 0) : CID(id), m_val(val) {m_fSet = !!val;}
		operator T() {return m_val;}
		BASE& Set(T val) {m_val = val; m_fSet = true; return(*(BASE*)this);}
		void UnSet() {m_fSet = false;}
		QWORD Size(bool fWithHeader = true);
		HRESULT Write(IStream* pStream);
	};

	class CUInt : public CSimpleVar<UINT64, CUInt>
	{
	public:
		explicit CUInt(DWORD id, UINT64 val = 0) : CSimpleVar<UINT64, CUInt>(id, val) {}
		QWORD Size(bool fWithHeader = true); 
		HRESULT Write(IStream* pStream);
	};

	class CInt : public CSimpleVar<INT64, CInt>
	{
	public: 
		explicit CInt(DWORD id, INT64 val = 0) : CSimpleVar<INT64, CInt>(id, val) {}
		QWORD Size(bool fWithHeader = true);
		HRESULT Write(IStream* pStream);
	};

	class CByte : public CSimpleVar<BYTE, CByte>
	{
	public: 
		explicit CByte(DWORD id, BYTE val = 0) : CSimpleVar<BYTE, CByte>(id, val) {}
	};

	class CShort : public CSimpleVar<short, CShort>
	{
	public: 
		explicit CShort(DWORD id, short val = 0) : CSimpleVar<short, CShort>(id, val) {}
	};

	class CFloat : public CSimpleVar<float, CFloat>
	{
	public: 
		explicit CFloat(DWORD id, float val = 0) : CSimpleVar<float, CFloat>(id, val) {}
	};

	template<class T>
	class CNode : public CAutoPtrList<T>
	{
	public: 
		QWORD Size(bool fWithHeader = true)
		{
			QWORD len = 0;
			POSITION pos = GetHeadPosition();
			while(pos) len += GetNext(pos)->Size(fWithHeader);
			return len;
		}
		HRESULT Write(IStream* pStream)
		{
			HRESULT hr;
			POSITION pos = GetHeadPosition();
			while(pos) if(FAILED(hr = GetNext(pos)->Write(pStream))) return hr;
			return S_OK;
		}
	};

	class EBML : public CID
	{
	public:
		CUInt EBMLVersion, EBMLReadVersion;
		CUInt EBMLMaxIDLength, EBMLMaxSizeLength;
		CANSI DocType;
		CUInt DocTypeVersion, DocTypeReadVersion;

		EBML(DWORD id = 0x1A45DFA3);
		QWORD Size(bool fWithHeader = true);
		HRESULT Write(IStream* pStream);
	};

		class Info : public CID
		{
		public:
			CBinary SegmentUID, PrevUID, NextUID;
			CUTF8 SegmentFilename, PrevFilename, NextFilename;
			CUInt TimeCodeScale; // [ns], default: 1.000.000
			CFloat Duration;
			CInt DateUTC;
			CUTF8 Title, MuxingApp, WritingApp;

			Info(DWORD id = 0x1549A966);
			QWORD Size(bool fWithHeader = true);
			HRESULT Write(IStream* pStream);
		};

				class Video : public CID
				{
				public:
					CUInt FlagInterlaced, StereoMode;
					CUInt PixelWidth, PixelHeight, DisplayWidth, DisplayHeight, DisplayUnit;
					CUInt AspectRatioType;
					CUInt ColourSpace;
					CFloat GammaValue;
					CFloat FramePerSec;

					Video(DWORD id = 0xE0);
					QWORD Size(bool fWithHeader = true);
					HRESULT Write(IStream* pStream);
				};

				class Audio : public CID
				{
				public:
					CFloat SamplingFrequency;
					CFloat OutputSamplingFrequency;
					CUInt Channels;
					CBinary ChannelPositions;
					CUInt BitDepth;

					Audio(DWORD id = 0xE1);
					QWORD Size(bool fWithHeader = true);
					HRESULT Write(IStream* pStream);
				};

			class TrackEntry : public CID
			{
			public:
				enum {TypeVideo = 1, TypeAudio = 2, TypeComplex = 3, TypeLogo = 0x10, TypeSubtitle = 0x11, TypeControl = 0x20};
				CUInt TrackNumber, TrackUID, TrackType;
				CUInt FlagEnabled, FlagDefault, FlagLacing;
				CUInt MinCache, MaxCache;
				CUTF8 Name;
				CANSI Language;
				CBinary CodecID;
				CBinary CodecPrivate;
				CUTF8 CodecName;
				CUTF8 CodecSettings;
				CANSI CodecInfoURL;
				CANSI CodecDownloadURL;
				CUInt CodecDecodeAll;
				CUInt TrackOverlay;
				CUInt DefaultDuration;
				enum {NoDesc = 0, DescVideo = 1, DescAudio = 2};
				int DescType;
				Video v;
				Audio a;

				TrackEntry(DWORD id = 0xAE);
				QWORD Size(bool fWithHeader = true);
				HRESULT Write(IStream* pStream);
			};

		class Track : public CID
		{
		public:
			CNode<TrackEntry> TrackEntries;

			Track(DWORD id = 0x1654AE6B);
			QWORD Size(bool fWithHeader = true);
			HRESULT Write(IStream* pStream);
		};

				class CBlock : public CID
				{
				public:
					CLength TrackNumber;
					REFERENCE_TIME TimeCode, TimeCodeStop;
					CNode<CBinary> BlockData;

					CBlock(DWORD id = 0xA1);
					QWORD Size(bool fWithHeader = true);
					HRESULT Write(IStream* pStream);
				};

			class BlockGroup : public CID
			{
			public:
				CUInt BlockDuration;
				CUInt ReferencePriority;
				CInt ReferenceBlock;
				CInt ReferenceVirtual;
				CBinary CodecState;
				CBlock Block;
//				CNode<TimeSlice> TimeSlices;

				BlockGroup(DWORD id = 0xA0);
				QWORD Size(bool fWithHeader = true);
				HRESULT Write(IStream* pStream);
			};

		class Cluster : public CID
		{
		public:
			CUInt TimeCode, Position, PrevSize;
			CNode<BlockGroup> BlockGroups;

			Cluster(DWORD id = 0x1F43B675);
			QWORD Size(bool fWithHeader = true);
			HRESULT Write(IStream* pStream);
		};

/*					class CueReference : public CID
					{
					public:
						CUInt CueRefTime, CueRefCluster, CueRefNumber, CueRefCodecState;

						CueReference(DWORD id = 0xDB);
						QWORD Size(bool fWithHeader = true);
						HRESULT Write(IStream* pStream);
					};
*/
				class CueTrackPosition : public CID
				{
				public:
					CUInt CueTrack, CueClusterPosition, CueBlockNumber, CueCodecState;
//					CNode<CueReference> CueReferences;

					CueTrackPosition(DWORD id = 0xB7);
					QWORD Size(bool fWithHeader = true);
					HRESULT Write(IStream* pStream);
				};

			class CuePoint : public CID
			{
			public:
				CUInt CueTime;
				CNode<CueTrackPosition> CueTrackPositions;

				CuePoint(DWORD id = 0xBB);
				QWORD Size(bool fWithHeader = true);
				HRESULT Write(IStream* pStream);
			};

		class Cue : public CID
		{
		public:
			CNode<CuePoint> CuePoints;

			Cue(DWORD id = 0x1C53BB6B);
			QWORD Size(bool fWithHeader = true);
			HRESULT Write(IStream* pStream);
		};

				class SeekID : public CID
				{
					CID m_id;
				public:
					SeekID(DWORD id = 0x53AB);
					void Set(DWORD id) {m_id = id;}
					QWORD Size(bool fWithHeader = true);
					HRESULT Write(IStream* pStream);
				};

			class SeekHead : public CID
			{
			public:
				SeekID ID;
				CUInt Position;

				SeekHead(DWORD id = 0x4DBB);
				QWORD Size(bool fWithHeader = true);
				HRESULT Write(IStream* pStream);
			};

		class Seek : public CID
		{
		public:
			CNode<SeekHead> SeekHeads;

			Seek(DWORD id = 0x114D9B74);
			QWORD Size(bool fWithHeader = true);
			HRESULT Write(IStream* pStream);
		};

	class Segment : public CID
	{
	public:
		Segment(DWORD id = 0x18538067);
		QWORD Size(bool fWithHeader = true);
		HRESULT Write(IStream* pStream);
	};

	class Tags : public CID
	{
	public:
		// TODO

		Tags(DWORD id = 0x1254C367);
		QWORD Size(bool fWithHeader = true);
		HRESULT Write(IStream* pStream);
	};

	class Void : public CID
	{
		QWORD m_len;
	public:
		Void(QWORD len, DWORD id = 0xEC);
		QWORD Size(bool fWithHeader = true);
		HRESULT Write(IStream* pStream);
	};
}
