// 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 rtc

import (
	"slices"
	"strconv"
	"strings"

	"github.com/livekit/protocol/livekit"
)

type ClientInfo struct {
	*livekit.ClientInfo
}

func (c ClientInfo) isFirefox() bool {
	return c.ClientInfo != nil && (strings.EqualFold(c.ClientInfo.Browser, "firefox") || strings.EqualFold(c.ClientInfo.Browser, "firefox mobile"))
}

func (c ClientInfo) isSafari() bool {
	return c.ClientInfo != nil && strings.EqualFold(c.ClientInfo.Browser, "safari")
}

func (c ClientInfo) isGo() bool {
	return c.ClientInfo != nil && c.ClientInfo.Sdk == livekit.ClientInfo_GO
}

func (c ClientInfo) isLinux() bool {
	return c.ClientInfo != nil && strings.EqualFold(c.ClientInfo.Os, "linux")
}

func (c ClientInfo) isAndroid() bool {
	return c.ClientInfo != nil && strings.EqualFold(c.ClientInfo.Os, "android")
}

func (c ClientInfo) isOBS() bool {
	return c.ClientInfo != nil && strings.Contains(c.ClientInfo.Browser, "OBS")
}

func (c ClientInfo) SupportsAudioRED() bool {
	return !c.isFirefox() && !c.isSafari()
}

func (c ClientInfo) SupportsPrflxOverRelay() bool {
	return !c.isFirefox()
}

// GoSDK(pion) relies on rtp packets to fire ontrack event, browsers and native (libwebrtc) rely on sdp
func (c ClientInfo) FireTrackByRTPPacket() bool {
	return c.isGo()
}

func (c ClientInfo) SupportsCodecChange() bool {
	return c.ClientInfo != nil && c.ClientInfo.Sdk != livekit.ClientInfo_GO && c.ClientInfo.Sdk != livekit.ClientInfo_UNKNOWN
}

func (c ClientInfo) CanHandleReconnectResponse() bool {
	if c.Sdk == livekit.ClientInfo_JS {
		// JS handles Reconnect explicitly in 1.6.3, prior to 1.6.4 it could not handle unknown responses
		if c.compareVersion("1.6.3") < 0 {
			return false
		}
	}
	return true
}

func (c ClientInfo) SupportsICETCP() bool {
	if c.ClientInfo == nil {
		return false
	}
	if c.ClientInfo.Sdk == livekit.ClientInfo_GO {
		// Go does not support active TCP
		return false
	}
	if c.ClientInfo.Sdk == livekit.ClientInfo_SWIFT {
		// ICE/TCP added in 1.0.5
		return c.compareVersion("1.0.5") >= 0
	}
	// most SDKs support ICE/TCP
	return true
}

func (c ClientInfo) SupportsChangeRTPSenderEncodingActive() bool {
	return !c.isFirefox()
}

func (c ClientInfo) ComplyWithCodecOrderInSDPAnswer() bool {
	return (!c.isLinux() && !c.isAndroid()) || !c.isFirefox()
}

// Rust SDK can't decode unknown signal message (TrackSubscribed and ErrorResponse)
func (c ClientInfo) SupportsTrackSubscribedEvent() bool {
	return c.ClientInfo.GetSdk() != livekit.ClientInfo_RUST || c.ClientInfo.GetProtocol() >= 10
}

func (c ClientInfo) SupportsRequestResponse() bool {
	return c.SupportsTrackSubscribedEvent()
}

func (c ClientInfo) SupportsSctpZeroChecksum() bool {
	return c.ClientInfo.GetSdk() != livekit.ClientInfo_UNKNOWN &&
		(!c.isGo() || c.compareVersion("2.4.0") >= 0)
}

func (c ClientInfo) SupportsTransceiverReuse() bool {
	return !c.isSafari()
}

func (c ClientInfo) HasCapability(cap livekit.ClientInfo_Capability) bool {
	if c.ClientInfo == nil {
		return false
	}
	return slices.Contains(c.ClientInfo.Capabilities, cap)
}

func (c ClientInfo) SupportsPacketTrailer() bool {
	return c.HasCapability(livekit.ClientInfo_CAP_PACKET_TRAILER)
}

// compareVersion compares a semver against the current client SDK version
// returning 1 if current version is greater than version
// 0 if they are the same, and -1 if it's an earlier version
func (c ClientInfo) compareVersion(version string) int {
	if c.ClientInfo == nil {
		return -1
	}
	parts0 := strings.Split(c.ClientInfo.Version, ".")
	parts1 := strings.Split(version, ".")
	ints0 := make([]int, 3)
	ints1 := make([]int, 3)
	for i := range 3 {
		if len(parts0) > i {
			ints0[i], _ = strconv.Atoi(parts0[i])
		}
		if len(parts1) > i {
			ints1[i], _ = strconv.Atoi(parts1[i])
		}
		if ints0[i] > ints1[i] {
			return 1
		} else if ints0[i] < ints1[i] {
			return -1
		}
	}
	return 0
}
