package core

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/pion/sdp/v3"
)

// Media take best from:
// - deepch/vdk/format/rtsp/sdp.Media
// - pion/sdp.MediaDescription
type Media struct {
	Kind      string   `json:"kind,omitempty"`      // video or audio
	Direction string   `json:"direction,omitempty"` // sendonly, recvonly
	Codecs    []*Codec `json:"codecs,omitempty"`

	ID string `json:"id,omitempty"` // MID for WebRTC, Control for RTSP
}

func (m *Media) String() string {
	s := fmt.Sprintf("%s, %s", m.Kind, m.Direction)
	for _, codec := range m.Codecs {
		name := codec.String()

		if strings.Contains(s, name) {
			continue
		}

		s += ", " + name
	}
	return s
}

func (m *Media) MarshalJSON() ([]byte, error) {
	return json.Marshal(m.String())
}

func (m *Media) Clone() *Media {
	clone := *m
	clone.Codecs = make([]*Codec, len(m.Codecs))
	for i, codec := range m.Codecs {
		clone.Codecs[i] = codec.Clone()
	}
	return &clone
}

func (m *Media) MatchMedia(remote *Media) (codec, remoteCodec *Codec) {
	// check same kind and opposite dirrection
	if m.Kind != remote.Kind ||
		m.Direction == DirectionSendonly && remote.Direction != DirectionRecvonly ||
		m.Direction == DirectionRecvonly && remote.Direction != DirectionSendonly {
		return nil, nil
	}

	for _, codec = range m.Codecs {
		for _, remoteCodec = range remote.Codecs {
			if codec.Match(remoteCodec) {
				return
			}
		}
	}

	return nil, nil
}

func (m *Media) MatchCodec(remote *Codec) *Codec {
	for _, codec := range m.Codecs {
		if codec.Match(remote) {
			return codec
		}
	}
	return nil
}

func (m *Media) MatchAll() bool {
	for _, codec := range m.Codecs {
		if codec.Name == CodecAll {
			return true
		}
	}
	return false
}

func (m *Media) Equal(media *Media) bool {
	if media.ID != "" {
		return m.ID == media.ID
	}
	return m.String() == media.String()
}

func GetKind(name string) string {
	switch name {
	case CodecH264, CodecH265, CodecVP8, CodecVP9, CodecAV1, CodecJPEG, CodecRAW:
		return KindVideo
	case CodecPCMU, CodecPCMA, CodecAAC, CodecOpus, CodecG722, CodecMP3, CodecPCM, CodecPCML, CodecELD, CodecFLAC:
		return KindAudio
	}
	return ""
}

func MarshalSDP(name string, medias []*Media) ([]byte, error) {
	sd := &sdp.SessionDescription{
		Origin: sdp.Origin{
			Username: "-", SessionID: 1, SessionVersion: 1,
			NetworkType: "IN", AddressType: "IP4", UnicastAddress: "0.0.0.0",
		},
		SessionName: sdp.SessionName(name),
		ConnectionInformation: &sdp.ConnectionInformation{
			NetworkType: "IN", AddressType: "IP4", Address: &sdp.Address{
				Address: "0.0.0.0",
			},
		},
		TimeDescriptions: []sdp.TimeDescription{
			{Timing: sdp.Timing{}},
		},
	}

	for _, media := range medias {
		if media.Codecs == nil {
			continue
		}

		codec := media.Codecs[0]

		switch codec.Name {
		case CodecELD:
			name = CodecAAC
		case CodecPCML:
			name = CodecPCM // beacuse we using pcm.LittleToBig for RTSP server
		default:
			name = codec.Name
		}

		md := &sdp.MediaDescription{
			MediaName: sdp.MediaName{
				Media:  media.Kind,
				Protos: []string{"RTP", "AVP"},
			},
		}
		md.WithCodec(codec.PayloadType, name, codec.ClockRate, uint16(codec.Channels), codec.FmtpLine)

		if media.Direction != "" {
			md.WithPropertyAttribute(media.Direction)
		}

		if media.ID != "" {
			md.WithValueAttribute("control", media.ID)
		}

		sd.MediaDescriptions = append(sd.MediaDescriptions, md)
	}

	return sd.Marshal()
}

func UnmarshalMedia(md *sdp.MediaDescription) *Media {
	m := &Media{
		Kind: md.MediaName.Media,
	}

	for _, attr := range md.Attributes {
		switch attr.Key {
		case DirectionSendonly, DirectionRecvonly, DirectionSendRecv:
			m.Direction = attr.Key
		case "control", "mid":
			m.ID = attr.Value
		}
	}

	for _, format := range md.MediaName.Formats {
		m.Codecs = append(m.Codecs, UnmarshalCodec(md, format))
	}

	return m
}

func ParseQuery(query map[string][]string) (medias []*Media) {
	// set media candidates from query list
	for key, values := range query {
		switch key {
		case KindVideo, KindAudio:
			for _, value := range values {
				media := &Media{Kind: key, Direction: DirectionSendonly}

				for _, name := range strings.Split(value, ",") {
					name = strings.ToUpper(name)

					// check aliases
					switch name {
					case "", "COPY":
						name = CodecAny
					case "MJPEG":
						name = CodecJPEG
					case "AAC":
						name = CodecAAC
					case "MP3":
						name = CodecMP3
					}

					media.Codecs = append(media.Codecs, &Codec{Name: name})
				}

				medias = append(medias, media)
			}
		}
	}

	return
}
