// Package rtsp provides RTSP utilities.
package rtsp

import (
	"time"

	"github.com/bluenviron/gortsplib/v5"
	"github.com/bluenviron/gortsplib/v5/pkg/description"
	"github.com/bluenviron/gortsplib/v5/pkg/format"
	"github.com/bluenviron/mediamtx/internal/conf"
	"github.com/bluenviron/mediamtx/internal/logger"
	"github.com/bluenviron/mediamtx/internal/stream"
	"github.com/pion/rtp"
)

type ntpState int

const (
	ntpStateInitial ntpState = iota
	ntpStateReplace
	ntpStateAvailable
)

type rtspSource interface {
	PacketPTS(*description.Media, *rtp.Packet) (int64, bool)
	PacketNTP(*description.Media, *rtp.Packet) (time.Time, bool)
	OnPacketRTP(*description.Media, format.Format, gortsplib.OnPacketRTPFunc)
}

// ToStream maps a RTSP stream to a MediaMTX stream.
func ToStream(
	source rtspSource,
	medias []*description.Media,
	pathConf *conf.Path,
	strm *stream.Stream,
	log logger.Writer,
) {
	for _, medi := range medias {
		for _, forma := range medi.Formats {
			cmedi := medi
			cforma := forma

			var ntpStat ntpState

			if !pathConf.UseAbsoluteTimestamp {
				ntpStat = ntpStateReplace
			}

			handleNTP := func(pkt *rtp.Packet) (time.Time, bool) {
				switch ntpStat {
				case ntpStateReplace:
					return time.Now(), true

				case ntpStateInitial:
					ntp, avail := source.PacketNTP(cmedi, pkt)
					if !avail {
						log.Log(logger.Warn, "received RTP packet without absolute time, skipping it")
						return time.Time{}, false
					}

					ntpStat = ntpStateAvailable
					return ntp, true

				default: // ntpStateAvailable
					ntp, avail := source.PacketNTP(cmedi, pkt)
					if !avail {
						panic("should not happen")
					}

					return ntp, true
				}
			}

			source.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) {
				pts, ok := source.PacketPTS(cmedi, pkt)
				if !ok {
					return
				}

				ntp, ok := handleNTP(pkt)
				if !ok {
					return
				}

				strm.WriteRTPPacket(cmedi, cforma, pkt, ntp, pts)
			})
		}
	}
}
