// Copyright 2024 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 (
	"testing"

	"math/rand"

	"github.com/stretchr/testify/require"

	"github.com/livekit/mediatransportutil/pkg/utils"
	"github.com/livekit/protocol/logger"
)

func TestFrameNumberWrapper(t *testing.T) {

	logger.InitFromConfig(&logger.Config{Level: "debug"}, t.Name())

	fnWrap := &FrameNumberWrapper{logger: logger.GetLogger()}

	fnWrapAround := utils.NewWrapAround[uint16, uint64](utils.WrapAroundParams{IsRestartAllowed: false})

	firstF := uint16(1000)

	testFrameOrder := func(frame uint16, isKeyFrame bool, frame2 uint16, isKeyFrame2, expectInorder bool) {
		frameUnwrap := fnWrapAround.Update(frame).ExtendedVal
		wrappedFrame := uint16(fnWrap.UpdateAndGet(frameUnwrap, isKeyFrame))

		// make sure wrap around always get in order frame number
		fnWrapAround.Update(frame + (frame2-frame)/2)

		frame2Unwrap := fnWrapAround.Update(frame2).ExtendedVal
		wrappedFrame2 := uint16(fnWrap.UpdateAndGet(frame2Unwrap, isKeyFrame2))
		// keeps order
		require.Equal(t, expectInorder, inOrder(wrappedFrame2, wrappedFrame), "frame %d, frame2 %d, wrappedFrame %d, wrapped Frame2 %d, frameUnwrap %d, frame2Unwrap %d", frame, frame2, wrappedFrame, wrappedFrame2, frameUnwrap, frame2Unwrap)
		// frame number diff should be the same if frame2 is not a key frame
		if !isKeyFrame2 {
			require.Equal(t, frame2-frame, wrappedFrame2-wrappedFrame)
		}
	}

	secondF := getFrame(firstF, true)
	testFrameOrder(firstF, true, secondF, false, true)

	// non key frame keeps diff and order
	for i := 0; i < 100; i++ {
		// frame in order
		firstF = secondF
		secondF = getFrame(firstF, true)
		testFrameOrder(firstF, false, secondF, false, true)

		// frame out of order
		firstF = secondF
		secondF = getFrame(firstF, false)
		// it is possible that an out of order non-keyframe has been converted to in order frame number if the diff is 32768
		// that is ok because the client can't decode in such case and always need to wait for the key frame.
		// so it is just a failure of test case and increase the frame number here.
		if secondF-firstF == 0x8000 {
			secondF++
		}
		testFrameOrder(firstF, false, secondF, false, false)

		// key frame in order
		firstF = secondF
		secondF = getFrame(firstF, true)
		testFrameOrder(firstF, false, secondF, true, true)

		// frame in order
		firstF = secondF
		secondF = getFrame(firstF, true)
		testFrameOrder(firstF, false, secondF, false, true)

		// key frame out of order but should be in order after wrap around
		firstF = secondF
		secondF = getFrame(firstF, false)
		testFrameOrder(firstF, false, secondF, true, true)
	}
}

func inOrder(a, b uint16) bool {
	return a-b < 0x8000 || (a-b == 0x8000 && a > b)
}

func getFrame(base uint16, inorder bool) uint16 {
	if inorder {
		return base + uint16(rand.Intn(0x8000))
	}

	for {
		ret := base + uint16(rand.Intn(0x8000)) + 0x8000
		if !inOrder(ret, base) {
			return ret
		}
	}
}
