// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package stun

import (
	
	
	
	
)

var (
	// ErrUnknownType indicates an error with Unknown info.
	ErrUnknownType = errors.New("Unknown")

	// ErrSchemeType indicates the scheme type could not be parsed.
	ErrSchemeType = errors.New("unknown scheme type")

	// ErrSTUNQuery indicates query arguments are provided in a STUN URL.
	ErrSTUNQuery = errors.New("queries not supported in stun address")

	// ErrInvalidQuery indicates an malformed query is provided.
	ErrInvalidQuery = errors.New("invalid query")

	// ErrHost indicates malformed hostname is provided.
	ErrHost = errors.New("invalid hostname")

	// ErrPort indicates malformed port is provided.
	ErrPort = errors.New("invalid port")

	// ErrProtoType indicates an unsupported transport type was provided.
	ErrProtoType = errors.New("invalid transport protocol type")
)

// SchemeType indicates the type of server used in the ice.URL structure.
type SchemeType int

const (
	// SchemeTypeUnknown indicates an unknown or unsupported scheme.
	SchemeTypeUnknown SchemeType = iota

	// SchemeTypeSTUN indicates the URL represents a STUN server.
	SchemeTypeSTUN

	// SchemeTypeSTUNS indicates the URL represents a STUNS (secure) server.
	SchemeTypeSTUNS

	// SchemeTypeTURN indicates the URL represents a TURN server.
	SchemeTypeTURN

	// SchemeTypeTURNS indicates the URL represents a TURNS (secure) server.
	SchemeTypeTURNS
)

// NewSchemeType defines a procedure for creating a new SchemeType from a raw
// string naming the scheme type.
func ( string) SchemeType {
	switch  {
	case "stun":
		return SchemeTypeSTUN
	case "stuns":
		return SchemeTypeSTUNS
	case "turn":
		return SchemeTypeTURN
	case "turns":
		return SchemeTypeTURNS
	default:
		return SchemeTypeUnknown
	}
}

func ( SchemeType) () string {
	switch  {
	case SchemeTypeSTUN:
		return "stun"
	case SchemeTypeSTUNS:
		return "stuns"
	case SchemeTypeTURN:
		return "turn"
	case SchemeTypeTURNS:
		return "turns"
	default:
		return ErrUnknownType.Error()
	}
}

// ProtoType indicates the transport protocol type that is used in the ice.URL
// structure.
type ProtoType int

const (
	// ProtoTypeUnknown indicates an unknown or unsupported protocol.
	ProtoTypeUnknown ProtoType = iota

	// ProtoTypeUDP indicates the URL uses a UDP transport.
	ProtoTypeUDP

	// ProtoTypeTCP indicates the URL uses a TCP transport.
	ProtoTypeTCP
)

// NewProtoType defines a procedure for creating a new ProtoType from a raw
// string naming the transport protocol type.
func ( string) ProtoType {
	switch  {
	case "udp":
		return ProtoTypeUDP
	case "tcp":
		return ProtoTypeTCP
	default:
		return ProtoTypeUnknown
	}
}

func ( ProtoType) () string {
	switch  {
	case ProtoTypeUDP:
		return "udp"
	case ProtoTypeTCP:
		return "tcp"
	default:
		return ErrUnknownType.Error()
	}
}

// URI represents a STUN (rfc7064) or TURN (rfc7065) URI
type URI struct {
	Scheme   SchemeType
	Host     string
	Port     int
	Username string
	Password string
	Proto    ProtoType
}

// ParseURI parses a STUN or TURN urls following the ABNF syntax described in
// https://tools.ietf.org/html/rfc7064 and https://tools.ietf.org/html/rfc7065
// respectively.
func ( string) (*URI, error) { //nolint:gocognit
	,  := url.Parse()
	if  != nil {
		return nil, 
	}

	var  URI
	.Scheme = NewSchemeType(.Scheme)
	if .Scheme == SchemeTypeUnknown {
		return nil, ErrSchemeType
	}

	var  string
	if .Host, ,  = net.SplitHostPort(.Opaque);  != nil {
		var  *net.AddrError
		if errors.As(, &) {
			if .Err == "missing port in address" {
				 := .Scheme.String() + ":" + .Opaque
				switch {
				case .Scheme == SchemeTypeSTUN || .Scheme == SchemeTypeTURN:
					 += ":3478"
					if .RawQuery != "" {
						 += "?" + .RawQuery
					}
					return ()
				case .Scheme == SchemeTypeSTUNS || .Scheme == SchemeTypeTURNS:
					 += ":5349"
					if .RawQuery != "" {
						 += "?" + .RawQuery
					}
					return ()
				}
			}
		}
		return nil, 
	}

	if .Host == "" {
		return nil, ErrHost
	}

	if .Port,  = strconv.Atoi();  != nil {
		return nil, ErrPort
	}

	switch .Scheme {
	case SchemeTypeSTUN:
		,  := url.ParseQuery(.RawQuery)
		if  != nil || len() > 0 {
			return nil, ErrSTUNQuery
		}
		.Proto = ProtoTypeUDP
	case SchemeTypeSTUNS:
		,  := url.ParseQuery(.RawQuery)
		if  != nil || len() > 0 {
			return nil, ErrSTUNQuery
		}
		.Proto = ProtoTypeTCP
	case SchemeTypeTURN:
		,  := parseProto(.RawQuery)
		if  != nil {
			return nil, 
		}

		.Proto = 
		if .Proto == ProtoTypeUnknown {
			.Proto = ProtoTypeUDP
		}
	case SchemeTypeTURNS:
		,  := parseProto(.RawQuery)
		if  != nil {
			return nil, 
		}

		.Proto = 
		if .Proto == ProtoTypeUnknown {
			.Proto = ProtoTypeTCP
		}

	case SchemeTypeUnknown:
	}

	return &, nil
}

func parseProto( string) (ProtoType, error) {
	,  := url.ParseQuery()
	if  != nil || len() > 1 {
		return ProtoTypeUnknown, ErrInvalidQuery
	}

	var  ProtoType
	if  := .Get("transport");  != "" {
		if  = NewProtoType();  == ProtoType(0) {
			return ProtoTypeUnknown, ErrProtoType
		}
		return , nil
	}

	if len() > 0 {
		return ProtoTypeUnknown, ErrInvalidQuery
	}

	return , nil
}

func ( URI) () string {
	 := .Scheme.String() + ":" + net.JoinHostPort(.Host, strconv.Itoa(.Port))
	if .Scheme == SchemeTypeTURN || .Scheme == SchemeTypeTURNS {
		 += "?transport=" + .Proto.String()
	}
	return 
}

// IsSecure returns whether the this URL's scheme describes secure scheme or not.
func ( URI) () bool {
	return .Scheme == SchemeTypeSTUNS || .Scheme == SchemeTypeTURNS
}