/*
NAME
  rtp.go - provides a data structure intended to encapsulate the properties
  of an rtp packet and also functions to allow manipulation of these packets.

DESCRIPTION
  See Readme.md

AUTHOR
  Saxon A. Nelson-Milton <saxon@ausocean.org>

LICENSE
  rtp.go is Copyright (C) 2018 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 [GNU licenses](http://www.gnu.org/licenses).
*/

/*
See https://tools.ietf.org/html/rfc6184 and https://tools.ietf.org/html/rfc3550
for rtp-h264 and rtp standards.
*/
package rtp

import (
	"encoding/binary"
)

const (
	rtpVer           = 2                                // Version of RTP that this package is compatible with.
	defaultHeadSize  = 12                               // Header size of an rtp packet.
	defPayloadSize   = sendSize                         // Default payload size for the rtp packet.
	defPktSize       = defaultHeadSize + defPayloadSize // Default packet size is header size + payload size.
	optionalFieldIdx = 12                               // This is the idx of optional fields including CSRC and extension header in an RTP packet.
)

// Pkt provides fields consistent with RFC3550 definition of an rtp packet
// The padding indicator does not need to be set manually, only the padding length
type Packet struct {
	V         uint8           // Version (currently 2).
	p         bool            // Padding indicator (0 => padding, 1 => padding).
	X         bool            // Extension header indicator.
	CC        uint8           // CSRC count.
	M         bool            // Marker bit.
	PT        uint8           // Packet type.
	SN        uint16          // Synch number.
	TS        uint32          // Timestamp.
	SSRC      uint32          // Synchronisation source identifier.
	CSRC      [][4]byte       // Contributing source identifier.
	Extension ExtensionHeader // Header extension.
	Payload   []byte          // Payload data.
	Padding   []byte          // No of bytes of padding.
}

// ExtensionHeader header provides fields for an RTP packet extension header.
type ExtensionHeader struct {
	ID     uint16
	Header [][4]byte
}

// Bytes provides a byte slice of the packet
func (p *Packet) Bytes(buf []byte) []byte {
	// Calculate the required length for the RTP packet.
	headerExtensionLen := 0
	if p.X {
		headerExtensionLen = int(4 + 4*len(p.Extension.Header))
	}
	requiredPktLen := defaultHeadSize + int(4*p.CC) + headerExtensionLen + len(p.Payload) + len(p.Padding)

	// Create new space if no buffer is given, or it doesn't have sufficient capacity.
	if buf == nil || requiredPktLen > cap(buf) {
		buf = make([]byte, requiredPktLen, defPktSize)
	}
	buf = buf[:requiredPktLen]

	// Start encoding fields into the buffer.
	buf[0] = p.V<<6 | asByte(p.p)<<5 | asByte(p.X)<<4 | p.CC
	buf[1] = asByte(p.M)<<7 | p.PT
	binary.BigEndian.PutUint16(buf[2:4], p.SN)
	binary.BigEndian.PutUint32(buf[4:8], p.TS)
	binary.BigEndian.PutUint32(buf[8:12], p.SSRC)

	// If there is a CSRC count, add the CSRC to the buffer.
	if p.CC != 0 {
		if p.CC != uint8(len(p.CSRC)) {
			panic("CSRC count in RTP packet is incorrect")
		}
		for i := 0; i < int(p.CC); i++ {
			copy(buf[12+i*4:], p.CSRC[i][:])
		}
	}

	// This is our current index for writing to the buffer.
	idx := int(12 + 4*p.CC)

	// If there is an extension field, add this to the buffer.
	if p.X {
		binary.BigEndian.PutUint16(buf[idx:idx+2], p.Extension.ID)
		idx += 2
		binary.BigEndian.PutUint16(buf[idx:idx+2], uint16(len(p.Extension.Header)))
		idx += 2
		for i := 0; i < len(p.Extension.Header); i++ {
			copy(buf[idx+i*4:], p.Extension.Header[i][:])
		}
		idx += len(p.Extension.Header) * 4
	}

	// If there is payload, add to the buffer.
	if p.Payload != nil {
		copy(buf[idx:], p.Payload)
		idx += len(p.Payload)
	}

	// Finally, if there is padding, add to the buffer.
	if p.Padding != nil {
		copy(buf[idx:], p.Padding)
	}

	return buf
}

func asByte(b bool) byte {
	if b {
		return 0x01
	}
	return 0x00
}