package rtmp

import (
	"errors"
	"fmt"
	"io"

	"github.com/langhuihui/gomem"
	"m7s.live/v5/pkg/codec"
)

type (
	AudioCodecID byte
	VideoCodecID byte
	H264Ctx      struct {
		*codec.H264Ctx
		SequenceFrame VideoFrame
	}
	H265Ctx struct {
		*codec.H265Ctx
		SequenceFrame VideoFrame
		Enhanced      bool
	}
	AACCtx struct {
		*codec.AACCtx
		SequenceFrame AudioFrame
	}
	OPUSCtx struct {
		codec.OPUSCtx
	}
	AV1Ctx struct {
		*codec.AV1Ctx
		SequenceFrame                    VideoFrame
		Version                          byte
		SeqProfile                       byte
		SeqLevelIdx0                     byte
		SeqTier0                         byte
		HighBitdepth                     byte
		TwelveBit                        byte
		MonoChrome                       byte
		ChromaSubsamplingX               byte
		ChromaSubsamplingY               byte
		ChromaSamplePosition             byte
		InitialPresentationDelayPresent  byte
		InitialPresentationDelayMinusOne byte
	}
)

const (
	ADTS_HEADER_SIZE              = 7
	CodecID_AAC      AudioCodecID = 0xA
	CodecID_PCMA     AudioCodecID = 7
	CodecID_PCMU     AudioCodecID = 8
	CodecID_OPUS     AudioCodecID = 0xC
	CodecID_H264     VideoCodecID = 7
	CodecID_H265     VideoCodecID = 0xC
	CodecID_AV1      VideoCodecID = 0xD
)

func (codecId AudioCodecID) String() string {
	switch codecId {
	case CodecID_AAC:
		return "aac"
	case CodecID_PCMA:
		return "pcma"
	case CodecID_PCMU:
		return "pcmu"
	case CodecID_OPUS:
		return "opus"
	}
	return "unknow"
}

func ParseAudioCodec(name codec.FourCC) AudioCodecID {
	switch name {
	case codec.FourCC_MP4A:
		return CodecID_AAC
	case codec.FourCC_ALAW:
		return CodecID_PCMA
	case codec.FourCC_ULAW:
		return CodecID_PCMU
	case codec.FourCC_OPUS:
		return CodecID_OPUS
	}
	return 0
}

func (codecId VideoCodecID) String() string {
	switch codecId {
	case CodecID_H264:
		return "h264"
	case CodecID_H265:
		return "h265"
	case CodecID_AV1:
		return "av1"
	}
	return "unknow"
}

func ParseVideoCodec(name codec.FourCC) VideoCodecID {
	switch name {
	case codec.FourCC_H264:
		return CodecID_H264
	case codec.FourCC_H265:
		return CodecID_H265
	case codec.FourCC_AV1:
		return CodecID_AV1
	}
	return 0
}

var ErrDecconfInvalid = errors.New("decode error")

var SamplingFrequencies = [...]int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0, 0}
var RTMP_AVC_HEAD = []byte{0x17, 0x00, 0x00, 0x00, 0x00, 0x01, 0x42, 0x00, 0x1E, 0xFF}

var ErrHevc = errors.New("hevc parse config error")

var (
	ErrInvalidMarker       = errors.New("invalid marker value found in AV1CodecConfigurationRecord")
	ErrInvalidVersion      = errors.New("unsupported AV1CodecConfigurationRecord version")
	ErrNonZeroReservedBits = errors.New("non-zero reserved bits found in AV1CodecConfigurationRecord")
)

func (ctx *AACCtx) GetSequenceFrame() *AudioFrame {
	return &ctx.SequenceFrame
}

func (ctx *H264Ctx) GetSequenceFrame() *VideoFrame {
	return &ctx.SequenceFrame
}

func (ctx *H265Ctx) GetSequenceFrame() *VideoFrame {
	return &ctx.SequenceFrame
}

func (p *AV1Ctx) GetInfo() string {
	return fmt.Sprintf("% 02X", p.ConfigOBUs)
}

func (p *AV1Ctx) Unmarshal(data *gomem.MemoryReader) (err error) {
	if data.Length < 4 {
		err = io.ErrShortWrite
		return
	}
	var b byte
	b, err = data.ReadByte()
	if err != nil {
		return
	}
	Marker := b >> 7
	if Marker != 1 {
		return ErrInvalidMarker
	}
	p.Version = b & 0x7F
	if p.Version != 1 {
		return ErrInvalidVersion
	}
	b, err = data.ReadByte()
	if err != nil {
		return
	}

	p.SeqProfile = b >> 5
	p.SeqLevelIdx0 = b & 0x1F

	b, err = data.ReadByte()
	if err != nil {
		return
	}

	p.SeqTier0 = b >> 7
	p.HighBitdepth = (b >> 6) & 0x01
	p.TwelveBit = (b >> 5) & 0x01
	p.MonoChrome = (b >> 4) & 0x01
	p.ChromaSubsamplingX = (b >> 3) & 0x01
	p.ChromaSubsamplingY = (b >> 2) & 0x01
	p.ChromaSamplePosition = b & 0x03

	b, err = data.ReadByte()
	if err != nil {
		return
	}

	if b>>5 != 0 {
		return ErrNonZeroReservedBits
	}
	p.InitialPresentationDelayPresent = (b >> 4) & 0x01
	if p.InitialPresentationDelayPresent == 1 {
		p.InitialPresentationDelayMinusOne = b & 0x0F
	} else {
		if b&0x0F != 0 {
			return ErrNonZeroReservedBits
		}
		p.InitialPresentationDelayMinusOne = 0
	}
	if data.Length > 0 {
		p.ConfigOBUs, err = data.ReadBytes(data.Length)
	}
	return nil
}
