package iso

const (
	Ftyp                        = "ftyp"
	Moov                        = "moov"
	MoovMvhd                    = "mvhd"
	MoovTrak                    = "trak"
	MoovTrakTkhd                = "tkhd"
	MoovTrakMdia                = "mdia"
	MoovTrakMdiaMdhd            = "mdhd"
	MoovTrakMdiaHdlr            = "hdlr"
	MoovTrakMdiaMinf            = "minf"
	MoovTrakMdiaMinfVmhd        = "vmhd"
	MoovTrakMdiaMinfSmhd        = "smhd"
	MoovTrakMdiaMinfDinf        = "dinf"
	MoovTrakMdiaMinfDinfDref    = "dref"
	MoovTrakMdiaMinfDinfDrefUrl = "url "
	MoovTrakMdiaMinfStbl        = "stbl"
	MoovTrakMdiaMinfStblStsd    = "stsd"
	MoovTrakMdiaMinfStblStts    = "stts"
	MoovTrakMdiaMinfStblStsc    = "stsc"
	MoovTrakMdiaMinfStblStsz    = "stsz"
	MoovTrakMdiaMinfStblStco    = "stco"
	MoovMvex                    = "mvex"
	MoovMvexTrex                = "trex"
	Moof                        = "moof"
	MoofMfhd                    = "mfhd"
	MoofTraf                    = "traf"
	MoofTrafTfhd                = "tfhd"
	MoofTrafTfdt                = "tfdt"
	MoofTrafTrun                = "trun"
	Mdat                        = "mdat"
)

const (
	sampleIsNonSync  = 0x10000
	sampleDependsOn1 = 0x1000000
	sampleDependsOn2 = 0x2000000

	SampleVideoIFrame    = sampleDependsOn2
	SampleVideoNonIFrame = sampleDependsOn1 | sampleIsNonSync
	SampleAudio          = sampleDependsOn2 //sampleIsNonSync
)

func (m *Movie) WriteFileType() {
	m.StartAtom(Ftyp)
	m.WriteString("iso5")
	m.WriteUint32(512)
	m.WriteString("iso5")
	m.WriteString("iso6")
	m.WriteString("mp41")
	m.EndAtom()
}

func (m *Movie) WriteMovieHeader() {
	m.StartAtom(MoovMvhd)
	m.Skip(1)           // version
	m.Skip(3)           // flags
	m.Skip(4)           // create time
	m.Skip(4)           // modify time
	m.WriteUint32(1000) // time scale
	m.Skip(4)           // duration
	m.WriteFloat32(1)   // preferred rate
	m.WriteFloat16(1)   // preferred volume
	m.Skip(10)          // reserved
	m.WriteMatrix()
	m.Skip(6 * 4)             // predefined?
	m.WriteUint32(0xFFFFFFFF) // next track ID
	m.EndAtom()
}

func (m *Movie) WriteTrackHeader(id uint32, width, height uint16) {
	const (
		TkhdTrackEnabled   = 0x0001
		TkhdTrackInMovie   = 0x0002
		TkhdTrackInPreview = 0x0004
		TkhdTrackInPoster  = 0x0008
	)

	// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-32963
	m.StartAtom(MoovTrakTkhd)
	m.Skip(1) // version
	m.WriteUint24(TkhdTrackEnabled | TkhdTrackInMovie)
	m.Skip(4)         // create time
	m.Skip(4)         // modify time
	m.WriteUint32(id) // trackID
	m.Skip(4)         // reserved
	m.Skip(4)         // duration
	m.Skip(8)         // reserved
	m.Skip(2)         // layer
	if width > 0 {
		m.Skip(2)
		m.Skip(2)
	} else {
		m.WriteUint16(1)  // alternate group
		m.WriteFloat16(1) // volume
	}
	m.Skip(2) // reserved
	m.WriteMatrix()
	if width > 0 {
		m.WriteFloat32(float64(width))
		m.WriteFloat32(float64(height))
	} else {
		m.Skip(4)
		m.Skip(4)
	}
	m.EndAtom()
}

func (m *Movie) WriteMediaHeader(timescale uint32) {
	// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-32999
	m.StartAtom(MoovTrakMdiaMdhd)
	m.Skip(1)                // version
	m.Skip(3)                // flags
	m.Skip(4)                // creation time
	m.Skip(4)                // modification time
	m.WriteUint32(timescale) // timescale
	m.Skip(4)                // duration
	m.WriteUint16(0x55C4)    // language (Unspecified)
	m.Skip(2)                // quality
	m.EndAtom()
}

func (m *Movie) WriteMediaHandler(s, name string) {
	// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-33004
	m.StartAtom(MoovTrakMdiaHdlr)
	m.Skip(1) // version
	m.Skip(3) // flags
	m.Skip(4)
	m.WriteString(s)    // handler type (4 byte!)
	m.Skip(3 * 4)       // reserved
	m.WriteString(name) // handler name (any len)
	m.Skip(1)           // end string
	m.EndAtom()
}

func (m *Movie) WriteVideoMediaInfo() {
	// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-33012
	m.StartAtom(MoovTrakMdiaMinfVmhd)
	m.Skip(1)        // version
	m.WriteUint24(1) // flags (You should always set this flag to 1)
	m.Skip(2)        // graphics mode
	m.Skip(3 * 2)    // op color
	m.EndAtom()
}

func (m *Movie) WriteAudioMediaInfo() {
	m.StartAtom(MoovTrakMdiaMinfSmhd)
	m.Skip(1) // version
	m.Skip(3) // flags
	m.Skip(4) // balance
	m.EndAtom()
}

func (m *Movie) WriteDataInfo() {
	// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25680
	m.StartAtom(MoovTrakMdiaMinfDinf)
	m.StartAtom(MoovTrakMdiaMinfDinfDref)
	m.Skip(1)        // version
	m.Skip(3)        // flags
	m.WriteUint32(1) // childrens

	m.StartAtom(MoovTrakMdiaMinfDinfDrefUrl)
	m.Skip(1)        // version
	m.WriteUint24(1) // flags (self reference)
	m.EndAtom()

	m.EndAtom() // DREF
	m.EndAtom() // DINF
}

func (m *Movie) WriteSampleTable(writeSampleDesc func()) {
	// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-33040
	m.StartAtom(MoovTrakMdiaMinfStbl)

	m.StartAtom(MoovTrakMdiaMinfStblStsd)
	m.Skip(1)        // version
	m.Skip(3)        // flags
	m.WriteUint32(1) // entry count
	writeSampleDesc()
	m.EndAtom()

	m.StartAtom(MoovTrakMdiaMinfStblStts)
	m.Skip(1) // version
	m.Skip(3) // flags
	m.Skip(4) // entry count
	m.EndAtom()

	m.StartAtom(MoovTrakMdiaMinfStblStsc)
	m.Skip(1) // version
	m.Skip(3) // flags
	m.Skip(4) // entry count
	m.EndAtom()

	m.StartAtom(MoovTrakMdiaMinfStblStsz)
	m.Skip(1) // version
	m.Skip(3) // flags
	m.Skip(4) // sample size
	m.Skip(4) // entry count
	m.EndAtom()

	m.StartAtom(MoovTrakMdiaMinfStblStco)
	m.Skip(1) // version
	m.Skip(3) // flags
	m.Skip(4) // entry count
	m.EndAtom()

	m.EndAtom()
}

func (m *Movie) WriteTrackExtend(id uint32) {
	m.StartAtom(MoovMvexTrex)
	m.Skip(1)         // version
	m.Skip(3)         // flags
	m.WriteUint32(id) // trackID
	m.WriteUint32(1)  // default sample description index
	m.Skip(4)         // default sample duration
	m.Skip(4)         // default sample size
	m.Skip(4)         // default sample flags
	m.EndAtom()
}

func (m *Movie) WriteVideoTrack(id uint32, codec string, timescale uint32, width, height uint16, conf []byte) {
	m.StartAtom(MoovTrak)
	m.WriteTrackHeader(id, width, height)

	m.StartAtom(MoovTrakMdia)
	m.WriteMediaHeader(timescale)
	m.WriteMediaHandler("vide", "VideoHandler")

	m.StartAtom(MoovTrakMdiaMinf)
	m.WriteVideoMediaInfo()
	m.WriteDataInfo()
	m.WriteSampleTable(func() {
		m.WriteVideo(codec, width, height, conf)
	})
	m.EndAtom() // MINF

	m.EndAtom() // MDIA
	m.EndAtom() // TRAK
}

func (m *Movie) WriteAudioTrack(id uint32, codec string, timescale uint32, channels uint16, conf []byte) {
	m.StartAtom(MoovTrak)
	m.WriteTrackHeader(id, 0, 0)

	m.StartAtom(MoovTrakMdia)
	m.WriteMediaHeader(timescale)
	m.WriteMediaHandler("soun", "SoundHandler")

	m.StartAtom(MoovTrakMdiaMinf)
	m.WriteAudioMediaInfo()
	m.WriteDataInfo()
	m.WriteSampleTable(func() {
		m.WriteAudio(codec, channels, timescale, conf)
	})
	m.EndAtom() // MINF

	m.EndAtom() // MDIA
	m.EndAtom() // TRAK
}

const (
	TfhdDefaultSampleDuration = 0x000008
	TfhdDefaultSampleSize     = 0x000010
	TfhdDefaultSampleFlags    = 0x000020
	TfhdDefaultBaseIsMoof     = 0x020000
)

const (
	TrunDataOffset       = 0x000001
	TrunFirstSampleFlags = 0x000004
	TrunSampleDuration   = 0x0000100
	TrunSampleSize       = 0x0000200
	TrunSampleFlags      = 0x0000400
	TrunSampleCTS        = 0x0000800
)

func (m *Movie) WriteMovieFragment(seq, tid, duration, size, flags uint32, dts uint64, cts uint32) {
	m.StartAtom(Moof)

	m.StartAtom(MoofMfhd)
	m.Skip(1)          // version
	m.Skip(3)          // flags
	m.WriteUint32(seq) // sequence number
	m.EndAtom()

	m.StartAtom(MoofTraf)

	m.StartAtom(MoofTrafTfhd)
	m.Skip(1) // version
	m.WriteUint24(
		TfhdDefaultSampleDuration |
			TfhdDefaultSampleSize |
			TfhdDefaultSampleFlags |
			TfhdDefaultBaseIsMoof,
	)
	m.WriteUint32(tid)      // track id
	m.WriteUint32(duration) // default sample duration
	m.WriteUint32(size)     // default sample size
	m.WriteUint32(flags)    // default sample flags
	m.EndAtom()

	m.StartAtom(MoofTrafTfdt)
	m.WriteBytes(1)    // version
	m.Skip(3)          // flags
	m.WriteUint64(dts) // base media decode time
	m.EndAtom()

	m.StartAtom(MoofTrafTrun)
	m.Skip(1) // version

	if cts == 0 {
		m.WriteUint24(TrunDataOffset) // flags
		m.WriteUint32(1)              // sample count

		// data offset: current pos + uint32 len + MDAT header len
		m.WriteUint32(uint32(len(m.b)) + 4 + 8)
	} else {
		m.WriteUint24(TrunDataOffset | TrunSampleCTS)
		m.WriteUint32(1)

		// data offset: current pos + uint32 len + CTS + MDAT header len
		m.WriteUint32(uint32(len(m.b)) + 4 + 4 + 8)
		m.WriteUint32(cts)
	}

	m.EndAtom() // TRUN

	m.EndAtom() // TRAF

	m.EndAtom() // MOOF
}

func (m *Movie) WriteData(b []byte) {
	m.StartAtom(Mdat)
	m.Write(b)
	m.EndAtom()
}
