// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package videolayerselector

import (
	"github.com/livekit/livekit-server/pkg/sfu/buffer"
	"github.com/livekit/protocol/logger"
)

type Simulcast struct {
	*Base
}

func NewSimulcast(logger logger.Logger) *Simulcast {
	return &Simulcast{
		Base: NewBase(logger),
	}
}

func NewSimulcastFromOther(vls VideoLayerSelector) *Simulcast {
	switch vls := vls.(type) {
	case *Null:
		return &Simulcast{
			Base: vls.Base,
		}

	case *Simulcast:
		return &Simulcast{
			Base: vls.Base,
		}

	case *DependencyDescriptor:
		return &Simulcast{
			Base: vls.Base,
		}

	case *VP9:
		return &Simulcast{
			Base: vls.Base,
		}

	default:
		return nil
	}
}

func (s *Simulcast) IsOvershootOkay() bool {
	return true
}

func (s *Simulcast) Select(extPkt *buffer.ExtPacket, layer int32) (result VideoLayerSelectorResult) {
	populateSwitches := func(isActive bool, reason string) {
		result.IsSwitching = true

		if !isActive {
			result.IsResuming = true
		}

		if reason != "" {
			s.logger.Debugw(
				reason,
				"previous", s.previousLayer,
				"current", s.currentLayer,
				"previousTarget", s.previousTargetLayer,
				"target", s.targetLayer,
				"max", s.maxLayer,
				"layer", layer,
				"req", s.requestSpatial,
				"maxSeen", s.maxSeenLayer,
				"feed", extPkt.Packet.SSRC,
			)
		}
	}

	if s.currentLayer.Spatial != s.targetLayer.Spatial {
		currentLayer := s.currentLayer

		// Two things to check when not locked to target
		//   1. Opportunistic layer upgrade - needs a key frame
		//   2. Need to downgrade - needs a key frame
		isActive := s.currentLayer.IsValid()
		found := false
		reason := ""
		if extPkt.IsKeyFrame {
			if s.enableStartAtDesiredQuality && !isActive {
				// Initial acquisition: latch directly onto the target layer instead of
				// opportunistically latching onto the first key frame of any lower layer that
				// happens to arrive first. This avoids a visible low-quality -> high-quality ramp
				// (e.g. briefly decoding layer 0 before settling on a requested layer 2) for a
				// subscriber that requested the higher layer.
				//
				// The target is chosen by the allocator: during the initial-acquisition grace it
				// points at the requested layer (so we wait for it); if that layer never shows
				// up the grace expires and the allocator drops the target to the highest layer
				// actually seen, so we always end up latching onto a layer that is flowing.
				if layer == s.targetLayer.Spatial {
					reason = "acquiring target layer"
					found = true
				}
			} else {
				// default: opportunistically latch on to / step towards the target layer
				if layer > s.currentLayer.Spatial && layer <= s.targetLayer.Spatial {
					reason = "upgrading layer"
					found = true
				}

				if layer < s.currentLayer.Spatial && layer >= s.targetLayer.Spatial {
					reason = "downgrading layer"
					found = true
				}
			}

			if found {
				currentLayer.Spatial = layer
				currentLayer.Temporal = extPkt.VideoLayer.Temporal
			}
		}

		if found {
			s.previousLayer = s.currentLayer
			s.currentLayer = currentLayer

			s.previousTargetLayer = s.targetLayer
			if s.currentLayer.Spatial >= s.maxLayer.Spatial || s.currentLayer.Spatial == s.maxSeenLayer.Spatial {
				s.targetLayer.Spatial = s.currentLayer.Spatial
			}

			populateSwitches(isActive, reason)
		}
	}

	// if locked to higher than max layer due to overshoot, check if it can be dialed back
	if s.currentLayer.Spatial > s.maxLayer.Spatial && layer <= s.maxLayer.Spatial && extPkt.IsKeyFrame {
		s.previousLayer = s.currentLayer
		s.currentLayer.Spatial = layer

		s.previousTargetLayer = s.targetLayer
		if s.currentLayer.Spatial >= s.maxLayer.Spatial || s.currentLayer.Spatial == s.maxSeenLayer.Spatial {
			s.targetLayer.Spatial = layer
		}

		populateSwitches(true, "adjusting overshoot")
	}

	result.RTPMarker = extPkt.Packet.Marker
	result.IsSelected = layer == s.currentLayer.Spatial
	result.IsRelevant = false
	return
}
