/*
NAME
  flv.go

DESCRIPTION
  See Readme.md

AUTHORS
  Saxon A. Nelson-Milton <saxon@ausocean.org>
  Dan Kortschak <dan@ausocean.org>

LICENSE
  flv.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)

  It is free software: you can redistribute it and/or modify them
  under the terms of the GNU General Public License as published by the
  Free Software Foundation, either version 3 of the License, or (at your
  option) any later version.

  It 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 revid in gpl.txt. If not, see http://www.gnu.org/licenses.
*/

// See https://wwwimages2.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10.pdf
// for format specification.

package flv

import "encoding/binary"

const (
	maxVideoTagSize = 10000
	maxAudioTagSize = 10000
)

const (
	VideoTagType         = 9
	AudioTagType         = 8
	KeyFrameType         = 1
	InterFrameType       = 2
	H264                 = 7
	AVCNALU              = 1
	SequenceHeader       = 0
	DataHeaderLength     = 5
	NoTimestampExtension = 0
	AACAudioFormat       = 10
	PCMAudioFormat       = 0
)

const (
	sizeofFLVTagHeader = 11
	sizeofPrevTagSize  = 4
)

const version = 0x01

// FLV is big-endian.
var order = binary.BigEndian

// orderPutUint24 is a binary.BigEndian method look-alike for
// writing 24 bit words to a byte slice.
func orderPutUint24(b []byte, v uint32) {
	_ = b[2] // early bounds check to guarantee safety of writes below
	b[0] = byte(v >> 16)
	b[1] = byte(v >> 8)
	b[2] = byte(v)
}

var flvHeaderCode = []byte{'F', 'L', 'V', version}

type Header struct {
	HasAudio bool
	HasVideo bool
}

func (h *Header) Bytes() []byte {
	// See https://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
	// section E.2.
	const headerLength = 9
	b := [headerLength]byte{
		0: 'F', 1: 'L', 2: 'V', 3: version,
		4: btb(h.HasAudio)<<2 | btb(h.HasVideo),
		8: headerLength, // order.PutUint32(b[5:9], headerLength)
	}
	return b[:]
}

type VideoTag struct {
	TagType           uint8
	DataSize          uint32
	Timestamp         uint32
	TimestampExtended uint8
	FrameType         uint8
	Codec             uint8
	PacketType        uint8
	CompositionTime   uint32
	Data              []byte
	PrevTagSize       uint32
}

func (t *VideoTag) Bytes() []byte {
	// FIXME(kortschak): This should probably be an encoding.BinaryMarshaler.
	// This will allow handling of invalid field values.

	b := make([]byte, t.DataSize+sizeofFLVTagHeader+sizeofPrevTagSize)

	b[0] = t.TagType
	orderPutUint24(b[1:4], t.DataSize)
	orderPutUint24(b[4:7], t.Timestamp)
	b[7] = t.TimestampExtended
	b[11] = t.FrameType<<4 | t.Codec
	b[12] = t.PacketType
	orderPutUint24(b[13:16], t.CompositionTime)
	copy(b[16:], t.Data)
	order.PutUint32(b[len(b)-4:], t.PrevTagSize)

	return b
}

type AudioTag struct {
	TagType           uint8
	DataSize          uint32
	Timestamp         uint32
	TimestampExtended uint8
	SoundFormat       uint8
	SoundRate         uint8
	SoundSize         bool
	SoundType         bool
	Data              []byte
	PrevTagSize       uint32
}

func (t *AudioTag) Bytes() []byte {
	// FIXME(kortschak): This should probably be an encoding.BinaryMarshaler.
	// This will allow handling of invalid field values.

	b := make([]byte, t.DataSize+sizeofFLVTagHeader+sizeofPrevTagSize)

	b[0] = t.TagType
	orderPutUint24(b[1:4], t.DataSize)
	orderPutUint24(b[4:7], t.Timestamp)
	b[7] = t.TimestampExtended
	b[11] = t.SoundFormat<<4 | t.SoundRate<<2 | btb(t.SoundSize)<<1 | btb(t.SoundType)
	copy(b[12:], t.Data)
	order.PutUint32(b[len(b)-4:], t.PrevTagSize)

	return b
}

func btb(b bool) byte {
	if b {
		return 1
	}
	return 0
}