package rtmp

import (
	"bytes"
	"encoding/binary"
	"io"
	"net"
	"time"

	"github.com/deepch/vdk/codec/h264parser"
	"github.com/langhuihui/gomem"

	. "m7s.live/v5/pkg"
	"m7s.live/v5/pkg/codec"
	"m7s.live/v5/pkg/util"
)

type VideoFrame RTMPData

// 过滤掉异常的 NALU
func (avcc *VideoFrame) filterH264(naluSizeLen int) {
	reader := avcc.NewReader()
	lenReader := reader.NewReader()
	reader.Skip(5)
	var afterFilter gomem.Memory
	lenReader.RangeN(5, afterFilter.PushOne)
	allocator := avcc.GetAllocator()
	var hasBadNalu bool
	for {
		naluLen, err := reader.ReadBE(naluSizeLen)
		if err != nil {
			break
		}
		var lenBuffer net.Buffers
		lenReader.RangeN(naluSizeLen, func(b []byte) {
			lenBuffer = append(lenBuffer, b)
		})
		lenReader.Skip(int(naluLen))
		var naluBuffer net.Buffers
		reader.RangeN(int(naluLen), func(b []byte) {
			naluBuffer = append(naluBuffer, b)
		})
		badType := codec.ParseH264NALUType(naluBuffer[0][0])
		// 替换之前打印 badType 的逻辑，解码并打印 SliceType
		if badType == 5 { // NALU type for Coded slice of a non-IDR picture or Coded slice of an IDR picture
			naluData := bytes.Join(naluBuffer, nil) // bytes 包已导入
			if len(naluData) > 0 {
				// h264parser 包已导入 as "github.com/deepch/vdk/codec/h264parser"
				// ParseSliceHeaderFromNALU 返回的第一个值就是 SliceType
				sliceType, err := h264parser.ParseSliceHeaderFromNALU(naluData)
				if err == nil {
					println("Decoded SliceType:", sliceType.String())
				} else {
					println("Error parsing H.264 slice header:", err.Error())
				}
			} else {
				println("NALU data is empty, cannot parse H.264 slice header.")
			}
		}

		switch badType {
		case 5, 6, 7, 8, 1, 2, 3, 4:
			afterFilter.Push(lenBuffer...)
			afterFilter.Push(naluBuffer...)
		default:
			hasBadNalu = true
			if allocator != nil {
				for _, nalu := range lenBuffer {
					allocator.Free(nalu)
				}
				for _, nalu := range naluBuffer {
					allocator.Free(nalu)
				}
			}
		}
	}
	if hasBadNalu {
		avcc.Memory = afterFilter
	}
}

func (avcc *VideoFrame) filterH265(naluSizeLen int) {
	//TODO
}

func (avcc *VideoFrame) CheckCodecChange() (err error) {
	old := avcc.ICodecCtx
	if avcc.Size <= 10 {
		err = io.ErrShortBuffer
		return
	}
	reader := avcc.NewReader()
	var b0 byte
	b0, err = reader.ReadByte()
	if err != nil {
		return
	}
	enhanced := b0&0b1000_0000 != 0 // https://veovera.github.io/enhanced-rtmp/docs/enhanced/enhanced-rtmp-v1.pdf
	avcc.IDR = b0&0b0111_0000>>4 == 1
	packetType := b0 & 0b1111
	codecId := VideoCodecID(b0 & 0x0F)
	var fourCC codec.FourCC
	parseSequence := func() (err error) {
		avcc.IDR = false
		switch fourCC {
		case codec.FourCC_H264:
			if old != nil && avcc.Memory.Equal(&old.(*H264Ctx).SequenceFrame.Memory) {
				avcc.ICodecCtx = old
				break
			}
			newCtx := &H264Ctx{}
			newCtx.SequenceFrame.CopyFrom(&avcc.Memory)
			newCtx.SequenceFrame.BaseSample = &BaseSample{}
			newCtx.H264Ctx, err = codec.NewH264CtxFromRecord(newCtx.SequenceFrame.Buffers[0][reader.Offset():])
			if err == nil {
				avcc.ICodecCtx = newCtx
			} else {
				return
			}
		case codec.FourCC_H265:
			if old != nil && avcc.Memory.Equal(&old.(*H265Ctx).SequenceFrame.Memory) {
				avcc.ICodecCtx = old
				break
			}
			newCtx := H265Ctx{
				Enhanced: enhanced,
			}
			newCtx.SequenceFrame.CopyFrom(&avcc.Memory)
			newCtx.SequenceFrame.BaseSample = &BaseSample{}
			newCtx.H265Ctx, err = codec.NewH265CtxFromRecord(newCtx.SequenceFrame.Buffers[0][reader.Offset():])
			if err == nil {
				avcc.ICodecCtx = newCtx
			} else {
				return
			}
		case codec.FourCC_AV1:
			var newCtx AV1Ctx
			if err = newCtx.Unmarshal(&reader); err == nil {
				avcc.ICodecCtx = &newCtx
			} else {
				return
			}
		}
		return ErrSkip
	}
	if enhanced {
		reader.Read(fourCC[:])
		switch packetType {
		case PacketTypeSequenceStart:
			err = parseSequence()
			return
		case PacketTypeCodedFrames:
			switch old.(type) {
			case *H265Ctx:
				var cts uint32
				if cts, err = reader.ReadBE(3); err != nil {
					return err
				}
				avcc.CTS = time.Duration(cts) * time.Millisecond
				// avcc.filterH265(int(ctx.RecordInfo.LengthSizeMinusOne) + 1)
			case *AV1Ctx:
				// return avcc.parseAV1(reader)
			}
		case PacketTypeCodedFramesX:
			// avcc.filterH265(int(old.(*H265Ctx).RecordInfo.LengthSizeMinusOne) + 1)
		}
	} else {
		b0, err = reader.ReadByte() //sequence frame flag
		if err != nil {
			return
		}
		if codecId == CodecID_H265 {
			fourCC = codec.FourCC_H265
		} else {
			fourCC = codec.FourCC_H264
		}
		var cts uint32
		cts, err = reader.ReadBE(3)
		if err != nil {
			return
		}
		avcc.CTS = time.Duration(cts) * time.Millisecond
		if b0 == 0 {
			if err = parseSequence(); err != nil {
				return
			}
		} else {
			// switch ctx := old.(type) {
			// case *codec.H264Ctx:
			// 	avcc.filterH264(int(ctx.RecordInfo.LengthSizeMinusOne) + 1)
			// case *H265Ctx:
			// 	avcc.filterH265(int(ctx.RecordInfo.LengthSizeMinusOne) + 1)
			// }
			// if avcc.Size <= 5 {
			// 	return old, ErrSkip
			// }
		}
	}
	return
}

func (avcc *VideoFrame) parseH264(ctx *H264Ctx, reader *gomem.MemoryReader) (err error) {
	return avcc.ParseAVCC(reader, int(ctx.RecordInfo.LengthSizeMinusOne)+1)
}

func (avcc *VideoFrame) parseH265(ctx *H265Ctx, reader *gomem.MemoryReader) (err error) {
	return avcc.ParseAVCC(reader, int(ctx.RecordInfo.LengthSizeMinusOne)+1)
}

func (avcc *VideoFrame) parseAV1(reader *gomem.MemoryReader) error {
	var obus OBUs
	if err := obus.ParseAVCC(reader); err != nil {
		return err
	}
	avcc.Raw = &obus
	return nil
}

func (avcc *VideoFrame) Demux() error {
	reader := avcc.NewReader()
	b0, err := reader.ReadByte()
	if err != nil {
		return err
	}

	enhanced := b0&0b1000_0000 != 0 // https://veovera.github.io/enhanced-rtmp/docs/enhanced/enhanced-rtmp-v1.pdf
	// frameType := b0 & 0b0111_0000 >> 4
	packetType := b0 & 0b1111

	if enhanced {
		err = reader.Skip(4) // fourcc
		if err != nil {
			return err
		}
		switch packetType {
		case PacketTypeSequenceStart:
			// see Parse()
			return nil
		case PacketTypeCodedFrames:
			switch ctx := avcc.ICodecCtx.(type) {
			case *H265Ctx:
				var cts uint32
				if cts, err = reader.ReadBE(3); err != nil {
					return err
				}
				avcc.CTS = time.Duration(cts) * time.Millisecond
				err = avcc.parseH265(ctx, &reader)
			case *AV1Ctx:
				err = avcc.parseAV1(&reader)
			}
		case PacketTypeCodedFramesX: // no cts
			err = avcc.parseH265(avcc.ICodecCtx.(*H265Ctx), &reader)
		}
		return err
	} else {
		b0, err = reader.ReadByte() //sequence frame flag
		if err != nil {
			return err
		}
		var cts uint32
		if cts, err = reader.ReadBE(3); err != nil {
			return err
		}
		avcc.SetCTS32(cts)
		switch ctx := avcc.ICodecCtx.(type) {
		case *H265Ctx:
			if b0 == 0 {
				// nalus.Append(ctx.VPS())
				// nalus.Append(ctx.SPS())
				// nalus.Append(ctx.PPS())
			} else {
				err = avcc.parseH265(ctx, &reader)
				return err
			}

		case *H264Ctx:
			if b0 == 0 {
				// nalus.Append(ctx.SPS())
				// nalus.Append(ctx.PPS())
			} else {
				err = avcc.parseH264(ctx, &reader)
				return err
			}
		}
		return err
	}
}

func (avcc *VideoFrame) muxOld26x(codecID VideoCodecID, fromBase *Sample) {
	nalus := fromBase.GetNalus()
	avcc.InitRecycleIndexes(nalus.Count()) // Recycle partial data
	head := avcc.NextN(5)
	head[0] = util.Conditional[byte](fromBase.IDR, 0x10, 0x20) | byte(codecID)
	head[1] = 1
	util.PutBE(head[2:5], fromBase.CTS/time.Millisecond) // cts
	for nalu := range nalus.RangePoint {
		naluLenM := avcc.NextN(4)
		naluLen := uint32(nalu.Size)
		binary.BigEndian.PutUint32(naluLenM, naluLen)
		// if nalu.Size != len(util.ConcatBuffers(nalu.Buffers)) {
		// 	panic("nalu size mismatch")
		// }
		avcc.Push(nalu.Buffers...)
	}
}

func (avcc *VideoFrame) Mux(fromBase *Sample) (err error) {
	switch c := fromBase.GetBase().(type) {
	case *AV1Ctx:
		panic(c)
	case *codec.H264Ctx:
		if avcc.ICodecCtx == nil {
			ctx := &H264Ctx{H264Ctx: c}
			ctx.SequenceFrame.PushOne(append([]byte{0x17, 0, 0, 0, 0}, c.Record...))
			ctx.SequenceFrame.BaseSample = &BaseSample{}
			avcc.ICodecCtx = ctx
		}
		avcc.muxOld26x(CodecID_H264, fromBase)
	case *codec.H265Ctx:
		if true {
			if avcc.ICodecCtx == nil {
				ctx := &H265Ctx{H265Ctx: c, Enhanced: true}
				b := make(util.Buffer, len(ctx.Record)+5)
				if ctx.Enhanced {
					b[0] = 0b1001_0000 | byte(PacketTypeSequenceStart)
					copy(b[1:], codec.FourCC_H265[:])
				} else {
					b[0], b[1], b[2], b[3], b[4] = 0x1C, 0, 0, 0, 0
				}
				copy(b[5:], ctx.Record)
				ctx.SequenceFrame.PushOne(b)
				ctx.SequenceFrame.BaseSample = &BaseSample{}
				avcc.ICodecCtx = ctx
			}
			nalus := fromBase.Raw.(*Nalus)
			avcc.InitRecycleIndexes(nalus.Count()) // Recycle partial data
			head := avcc.NextN(8)
			if fromBase.IDR {
				head[0] = 0b1001_0000 | byte(PacketTypeCodedFrames)
			} else {
				head[0] = 0b1010_0000 | byte(PacketTypeCodedFrames)
			}
			copy(head[1:], codec.FourCC_H265[:])
			util.PutBE(head[5:8], fromBase.CTS/time.Millisecond) // cts
			for nalu := range nalus.RangePoint {
				naluLenM := avcc.NextN(4)
				naluLen := uint32(nalu.Size)
				binary.BigEndian.PutUint32(naluLenM, naluLen)
				avcc.Push(nalu.Buffers...)
			}
		} else {
			avcc.muxOld26x(CodecID_H265, fromBase)
		}
	}
	return
}
