package localserver

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strconv"
	sync "sync"
	"sync/atomic"
	"time"

	"github.com/google/uuid"
	"github.com/pion/rtcp"
	"github.com/pion/webrtc/v3"
)

const (
	NAME                       = "name"
	ID                         = "ID"
	SDP                        = "sdp"
	CANDIDATE                  = "webrtc_candidate"
	SQUAD_ID                   = "squadId"
	FROM                       = "from"
	TO                         = "to"
	STOP_CALL                  = "stop_call"
	LIST_HOSTED_SQUADS_BY_HOST = "list_hosted_squads_by_host"
)

type Squad struct {
	ID      string
	Members []string
}

type WebRTCCallManager struct {
	stream                  SignalingService_LinkClient
	middlewares             []WebrtcCallEventManager
	ID                      string
	LocalSD                 map[string]*webrtc.SessionDescription
	RTCPeerConnections      map[string]*RTCPeerConnection
	AudioTransceiver        map[string][]*PeerSender
	VideoTransceiver        map[string][]*PeerSender
	DataChannels            map[string]*DataChannel
	PendingCandidates       map[string][]*webrtc.ICECandidate
	RemoteTracks            map[string][]*RemoteTrack
	Squads                  map[string]*Squad
	SquadMapMux             *sync.RWMutex
	CandidateChannel        chan *IncomingCandidate
	CandidateMux            *sync.RWMutex
	RemoteTracksMux         *sync.RWMutex
	RTCPeerConnectionMapMux *sync.RWMutex
	DataChannelMapMux       *sync.RWMutex
	LocalSDMapMux           *sync.RWMutex
	AudioSenderMux          *sync.RWMutex
	VideoSenderMux          *sync.RWMutex
}

type IncomingCandidate struct {
	For       string
	Candidate *webrtc.ICECandidateInit
}

type RTCPeerConnection struct {
	*webrtc.PeerConnection
	id              string
	makingOffer     bool
	negotiate       func(string, string)
	makingOfferLock *sync.Mutex
}

type DataChannel struct {
	DataChannel                       *webrtc.DataChannel
	bufferedAmountLowThresholdReached <-chan struct{}
	l                                 *uint32
}

type PeerSender struct {
	ID          string
	Transceiver *webrtc.RTPTransceiver
	Sender      *webrtc.RTPSender
}

type CallEvent struct {
	EventId string                 `json:"eventId"`
	From    string                 `json:"from"`
	Data    []byte                 `json:"data"`
	Payload map[string]interface{} `json:"payload"`
}

type RemoteTrack struct {
	ID    string
	Track *webrtc.TrackLocalStaticRTP
	rdv   *int32
}

type OnICECandidateFunc func(string, *webrtc.ICECandidate) error

func NewWebRTCCallManager(id string, token string, eventHandlers ...WebrtcCallEventManager) (webRTCCallManager *WebRTCCallManager, err error) {
	squadList, err := loadHostedSquads(token, id)
	if err != nil {
		return
	}
	squads := make(map[string]*Squad)
	for _, squad := range squadList {
		squads[squad.ID] = squad
	}
	logger.Println(squads)
	webRTCCallManager = &WebRTCCallManager{
		middlewares:             eventHandlers,
		ID:                      id,
		AudioTransceiver:        make(map[string][]*PeerSender),
		VideoTransceiver:        make(map[string][]*PeerSender),
		DataChannels:            make(map[string]*DataChannel),
		PendingCandidates:       make(map[string][]*webrtc.ICECandidate),
		LocalSD:                 make(map[string]*webrtc.SessionDescription),
		RTCPeerConnections:      make(map[string]*RTCPeerConnection),
		RemoteTracks:            make(map[string][]*RemoteTrack),
		Squads:                  squads,
		SquadMapMux:             &sync.RWMutex{},
		RTCPeerConnectionMapMux: &sync.RWMutex{},
		LocalSDMapMux:           &sync.RWMutex{},
		CandidateMux:            &sync.RWMutex{},
		DataChannelMapMux:       &sync.RWMutex{},
		AudioSenderMux:          &sync.RWMutex{},
		VideoSenderMux:          &sync.RWMutex{},
		RemoteTracksMux:         &sync.RWMutex{},
	}
	return
}

func loadHostedSquads(token string, hostId string) (squads []*Squad, err error) {
	em := NewEncryptionManager()
	sig := em.SignRequestHMAC(hostId)
	body, err := json.Marshal(map[string]interface{}{
		"type":     LIST_HOSTED_SQUADS_BY_HOST,
		"mac":      sig,
		"from":     hostId,
		"peerType": "node",
		"payload": map[string]string{
			"host":      hostId,
			"lastIndex": "0",
		},
	})
	if err != nil {
		return
	}
	res, err := HTTPClient.Post("https://dev.zippytal.com/req", "application/json", bytes.NewBuffer(body))
	if err != nil {
		logger.Println("error come from there in webrtc call manager")
		return
	}
	bs, err := io.ReadAll(res.Body)
	if err != nil {
		return
	}
	var payload map[string]any
	if err = json.Unmarshal(bs, &payload); err != nil {
		return
	}
	b, err := json.Marshal(payload["squads"])
	if err != nil {
		return
	}
	err = json.Unmarshal(b, &squads)
	return
}

func (wm *WebRTCCallManager) sendSignalingMessage(messageType, from, to string, payload map[string]interface{}) (err error) {
	bs, err := json.Marshal(payload)
	if err != nil {
		return
	}
	err = wm.stream.Send(&SignalingMessage{
		Type:    messageType,
		From:    from,
		To:      to,
		Payload: bs,
	})
	return
}

func (wm *WebRTCCallManager) CreateOffer(ctx context.Context, target string, from string, cb OnICECandidateFunc) (err error) {
	peerConnection, err := wm.createPeerConnection(target, from, "aa5da62f-2800-4dd5-bf86-423cda048120", webrtc.SDPTypeOffer, cb)
	if err != nil {
		return
	}
	logger.Println("connection created")
	rawOffer, err := peerConnection.CreateOffer(nil)
	if err != nil {
		return
	}
	if err = peerConnection.SetLocalDescription(rawOffer); err != nil {
		return
	}
	wm.RTCPeerConnectionMapMux.Lock()
	logger.Println("adding for target", target)
	wm.RTCPeerConnections[target] = &RTCPeerConnection{
		PeerConnection:  peerConnection,
		makingOffer:     true,
		makingOfferLock: &sync.Mutex{},
		negotiate:       wm.negotiate,
	}
	wm.RTCPeerConnectionMapMux.Unlock()
	err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_OFFER), wm.ID, target, map[string]interface{}{
		"to":   target,
		"from": wm.ID,
		"sdp":  rawOffer.SDP,
	})
	return
}

func (wm *WebRTCCallManager) HandleOffer(ctx context.Context, from, to string, req map[string]string, cb OnICECandidateFunc) (err error) {
	done, errCh := make(chan struct{}), make(chan error)
	go func() {
		if _, ok := wm.Squads[req[SQUAD_ID]]; !ok {
			err = fmt.Errorf("no corresponding squad")
			errCh <- err
			return
		}
		peerConnection, err := wm.createPeerConnection(from, to, req[SQUAD_ID], webrtc.SDPTypeAnswer, cb)
		if err != nil {
			errCh <- err
			return
		}
		wm.RTCPeerConnectionMapMux.Lock()
		wm.RTCPeerConnections[from] = &RTCPeerConnection{
			PeerConnection:  peerConnection,
			makingOffer:     false,
			makingOfferLock: &sync.Mutex{},
			negotiate:       wm.negotiate,
		}
		wm.RTCPeerConnectionMapMux.Unlock()
		offer := webrtc.SessionDescription{
			Type: webrtc.SDPTypeOffer,
			SDP:  req[SDP],
		}
		if err = peerConnection.SetRemoteDescription(offer); err != nil {
			errCh <- err
			return
		}
		rawAnswer, err := peerConnection.CreateAnswer(nil)
		if err != nil {
			errCh <- err
			return
		}
		wm.LocalSDMapMux.Lock()
		wm.LocalSD[from] = &rawAnswer
		wm.LocalSDMapMux.Unlock()
		if err = peerConnection.SetLocalDescription(rawAnswer); err != nil {
			errCh <- err
			return
		}
		wm.SquadMapMux.Lock()
		wm.Squads[req[SQUAD_ID]].Members = append(wm.Squads[req[SQUAD_ID]].Members, from)
		wm.SquadMapMux.Unlock()
		if err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_ANSWER), wm.ID, from, map[string]interface{}{
			"to":   from,
			"from": wm.ID,
			"sdp":  rawAnswer.SDP,
		}); err != nil {
			errCh <- err
			return
		}
		done <- struct{}{}
	}()
	select {
	case <-done:
		return
	case err = <-errCh:
		return
	case <-ctx.Done():
		err = ctx.Err()
		return
	}
}

func (wm *WebRTCCallManager) HandleAnswer(ctx context.Context, from, to string, req map[string]string) (err error) {
	wm.RTCPeerConnectionMapMux.Lock()
	defer wm.RTCPeerConnectionMapMux.Unlock()
	defer func() {
		if r := recover(); err != nil {
			logger.Printf("recover from panic in handle answer : %v\n", r)
		}
	}()
	if _, ok := wm.RTCPeerConnections[from]; !ok {
		err = fmt.Errorf("no corresponding peer connection for id : %s", from)
		return
	}
	peerConnnection := wm.RTCPeerConnections[from]
	logger.Println("---------------------")
	logger.Println(req[SDP])
	logger.Println("---------------------")
	if err = peerConnnection.SetRemoteDescription(webrtc.SessionDescription{
		Type: webrtc.SDPTypeAnswer,
		SDP:  req[SDP],
	}); err != nil {
		logger.Println("error occured while setting remote description in handle answer")
		return
	}
	if err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_COUNTER_OFFER), wm.ID, from, map[string]interface{}{
		"from": wm.ID,
		"to":   from,
	}); err != nil {
		return
	}
	wm.CandidateMux.Lock()
	for _, candidate := range wm.PendingCandidates[from] {
		logger.Println("sending candidate from answer to", from)
		if err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_CANDIDATE), wm.ID, from, map[string]interface{}{
			"from":          wm.ID,
			"to":            from,
			"candidate":     candidate.ToJSON().Candidate,
			"sdpMid":        *candidate.ToJSON().SDPMid,
			"sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)),
		}); err != nil {
			wm.CandidateMux.Unlock()
			return
		}
	}
	wm.CandidateMux.Unlock()
	wm.CandidateMux.Lock()
	delete(wm.PendingCandidates, from)
	wm.CandidateMux.Unlock()
	wm.LocalSDMapMux.Lock()
	delete(wm.LocalSD, from)
	wm.LocalSDMapMux.Unlock()
	return
}

func (wm *WebRTCCallManager) HandleCounterOffer(ctx context.Context, from, to string, req map[string]string) (err error) {
	wm.RTCPeerConnectionMapMux.Lock()
	if _, ok := wm.RTCPeerConnections[from]; !ok {
		err = fmt.Errorf("no field corresponding peer connection for id %s", from)
		wm.RTCPeerConnectionMapMux.Unlock()
		return
	}
	logger.Println("handling counter offer")
	//connection := wm.RTCPeerConnections[from]
	wm.RTCPeerConnectionMapMux.Unlock()
	// wm.LocalSDMapMux.Lock()
	// if err = connection.SetLocalDescription(*wm.LocalSD[from]); err != nil {
	// 	wm.LocalSDMapMux.Unlock()
	// 	return
	// }
	// wm.LocalSDMapMux.Unlock()
	wm.CandidateMux.Lock()
	for _, candidate := range wm.PendingCandidates[from] {
		logger.Println("sending candidate to", from)
		if err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_CANDIDATE), wm.ID, from, map[string]interface{}{
			"from":          wm.ID,
			"to":            from,
			"candidate":     candidate.ToJSON().Candidate,
			"sdpMid":        *candidate.ToJSON().SDPMid,
			"sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)),
		}); err != nil {
			wm.CandidateMux.Unlock()
			return
		}
	}
	delete(wm.PendingCandidates, from)
	wm.CandidateMux.Unlock()
	wm.LocalSDMapMux.Lock()
	delete(wm.LocalSD, from)
	wm.LocalSDMapMux.Unlock()
	return
}

func (wm *WebRTCCallManager) createPeerConnection(target string, from string, squadId string, peerType webrtc.SDPType, cb OnICECandidateFunc) (peerConnection *webrtc.PeerConnection, err error) {
	defer func() {
		if r := recover(); err != nil {
			logger.Printf("recover from panic : %v\n", r)
		}
	}()
	config := webrtc.Configuration{
		ICEServers: []webrtc.ICEServer{
			{
				URLs: []string{"stun:stun.l.google.com:19302", "stun:stunserver.org:3478"},
			},
		},
		SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback,
	}

	peerConnection, err = webrtc.NewPeerConnection(config)
	if err != nil {
		return
	}
	logger.Println("---------------------------------------------------")
	if peerType == webrtc.SDPTypeAnswer {
		maxRetransmits := uint16(100)
		channel, err := peerConnection.CreateDataChannel("data", &webrtc.DataChannelInit{
			MaxRetransmits: &maxRetransmits,
		})
		if err != nil {
			return nil, err
		}
		channel.OnOpen(func() {
			logger.Println("channel opened")
			if chanErr := channel.SendText("yooo man this is open"); chanErr != nil {
				logger.Println(chanErr)
			}
		})
		channel.OnMessage(func(msg webrtc.DataChannelMessage) {
			var event CallEvent
			if err := json.Unmarshal(msg.Data, &event); err != nil {
				logger.Println(err)
				return
			}
			for _, handler := range wm.middlewares {
				if err := handler.HandleCallEvent(event.From, squadId, event.EventId, event.Payload, event.Data, wm); err != nil {
					logger.Println(err)
					continue
				}
			}
		})
		logger.Println("new channel for target : ", target)
		channel.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold)
		channel.OnBufferedAmountLow(func() {

		})
		wm.DataChannelMapMux.Lock()
		logger.Println(target)
		l := uint32(0)
		wm.DataChannels[target] = &DataChannel{
			DataChannel: channel,
			l:           &l,
		}
		wm.DataChannelMapMux.Unlock()
	} else {
		peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) {
			wm.DataChannelMapMux.Lock()
			l := uint32(0)
			wm.DataChannels[target] = &DataChannel{
				DataChannel: dc,
				l:           &l,
			}
			wm.DataChannelMapMux.Unlock()
			dc.OnOpen(func() {
				logger.Printf("got a new open datachannel %s\n", dc.Label())
			})
			dc.OnMessage(func(msg webrtc.DataChannelMessage) {
				var event CallEvent
				if err := json.Unmarshal(msg.Data, &event); err != nil {
					logger.Println(err)
					return
				}
				for _, handler := range wm.middlewares {
					if err := handler.HandleCallEvent(event.From, squadId, event.EventId, event.Payload, event.Data, wm); err != nil {
						logger.Println(err)
						continue
					}
				}
			})
		})
	}
	wm.RemoteTracksMux.RLock()
	for _, id := range wm.Squads[squadId].Members {
		if id != target {
			<-time.NewTimer(time.Millisecond * 50).C
			if _, ok := wm.RemoteTracks[id]; !ok {
				continue
			}
			for _, track := range wm.RemoteTracks[id] {
				transceiver, err := peerConnection.AddTransceiverFromKind(track.Track.Kind(), webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly})
				if err != nil {
					logger.Println("add track error")
					continue
				}
				if err := transceiver.Sender().ReplaceTrack(track.Track); err != nil {
					logger.Println("add track error")
					continue
				}
				if track.Track.Kind() == webrtc.RTPCodecTypeVideo {
					wm.VideoSenderMux.Lock()
					if len(wm.VideoTransceiver) == 0 {
						wm.VideoTransceiver[id] = []*PeerSender{{ID: target, Transceiver: transceiver}}
					} else {
						wm.VideoTransceiver[id] = append(wm.VideoTransceiver[id], &PeerSender{ID: target, Transceiver: transceiver})
					}
					wm.VideoSenderMux.Unlock()
				} else if track.Track.Kind() == webrtc.RTPCodecTypeAudio {
					wm.AudioSenderMux.Lock()
					if len(wm.AudioTransceiver) == 0 {
						wm.AudioTransceiver[id] = []*PeerSender{{ID: target, Transceiver: transceiver}}
					} else {
						wm.AudioTransceiver[id] = append(wm.AudioTransceiver[id], &PeerSender{ID: target, Transceiver: transceiver})
					}
					wm.AudioSenderMux.Unlock()
				}
				logger.Println("track added", track)
			}
		}
	}
	wm.RemoteTracksMux.RUnlock()
	peerConnection.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) {
		if pcs == webrtc.PeerConnectionStateClosed || pcs == webrtc.PeerConnectionStateDisconnected || pcs == webrtc.PeerConnectionStateFailed {
			logger.Println(pcs)
			wm.HandleLeavingMember(target, squadId)
		}
	})
	peerConnection.OnICEConnectionStateChange(func(is webrtc.ICEConnectionState) {
		logger.Printf("ICE connection state has changed %s\n", is.String())
		if is == webrtc.ICEConnectionStateDisconnected || is == webrtc.ICEConnectionStateFailed {
			logger.Println(is)
		}
	})
	peerConnection.OnTrack(func(tr *webrtc.TrackRemote, r *webrtc.RTPReceiver) {
		logger.Println("got new track")
		defer func() {
			if stopErr := r.Stop(); stopErr != nil {
				logger.Println(stopErr)
			}
		}()
		go func() {
			ticker := time.NewTicker(1500 * time.Millisecond)
			for range ticker.C {
				if rtcpSendErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(tr.SSRC())}}); rtcpSendErr != nil {
					logger.Println(rtcpSendErr)
					break
				}
			}
		}()
		uniqId := uuid.New()
		i := fmt.Sprintf("%s/%s", target, uniqId.String())
		localTrack, newTrackErr := webrtc.NewTrackLocalStaticRTP(tr.Codec().RTPCodecCapability, i, i)
		if newTrackErr != nil {
			return
		}
		logger.Println(localTrack)
		rtpbuf := make([]byte, 1400)
		flag := int32(0)
		remote := &RemoteTrack{ID: target, Track: localTrack, rdv: &flag}
		wm.RemoteTracksMux.Lock()
		if len(wm.RemoteTracks[target]) == 0 {
			wm.RemoteTracks[target] = []*RemoteTrack{remote}
		} else {
			wm.RemoteTracks[target] = append(wm.RemoteTracks[target], remote)
		}
		index := len(wm.RemoteTracks[target])
		logger.Println(index, wm.RemoteTracks)
		wm.RemoteTracksMux.Unlock()
		wm.SquadMapMux.RLock()
		for _, id := range wm.Squads[squadId].Members {
			if id != target {
				if _, ok := wm.RTCPeerConnections[id]; !ok {
					continue
				}
				connection := wm.RTCPeerConnections[id]
				transceiver, tranceiverErr := connection.AddTransceiverFromKind(localTrack.Kind(), webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly})
				if tranceiverErr != nil {
					logger.Println(tranceiverErr)
					continue
				}
				if replaceTrackErr := transceiver.Sender().ReplaceTrack(localTrack); replaceTrackErr != nil {
					logger.Println(replaceTrackErr)
					continue
				}
				go func() {
					rtcpBuf := make([]byte, 1500)
					for {
						if _, _, rtcpErr := transceiver.Sender().Read(rtcpBuf); rtcpErr != nil {
							return
						}
					}
				}()
				if localTrack.Kind() == webrtc.RTPCodecTypeAudio {
					wm.AudioSenderMux.Lock()
					if len(wm.AudioTransceiver) == 0 {
						wm.AudioTransceiver[target] = []*PeerSender{{ID: id, Transceiver: transceiver}}
					} else {
						wm.AudioTransceiver[target] = append(wm.AudioTransceiver[target], &PeerSender{ID: id, Transceiver: transceiver})
					}
					wm.AudioSenderMux.Unlock()
				} else if localTrack.Kind() == webrtc.RTPCodecTypeVideo {
					wm.VideoSenderMux.Lock()
					if len(wm.VideoTransceiver) == 0 {
						wm.VideoTransceiver[target] = []*PeerSender{{ID: id, Transceiver: transceiver}}
					} else {
						wm.VideoTransceiver[target] = append(wm.VideoTransceiver[target], &PeerSender{ID: id, Transceiver: transceiver})
					}
					wm.VideoSenderMux.Unlock()
				}
				go func() {
					<-time.NewTimer(time.Second).C
					connection.negotiate(id, squadId)
				}()
			}
		}
		wm.SquadMapMux.RUnlock()
		for {
			i, _, readErr := tr.Read(rtpbuf)
			if readErr != nil {
				logger.Println(readErr)
				break
			}
			f := atomic.LoadInt32(remote.rdv)
			if f == 0 {
				if _, writeErr := localTrack.Write(rtpbuf[:i]); writeErr != nil && !errors.Is(writeErr, io.ErrClosedPipe) {
					logger.Println(writeErr)
					break
				} else {
					_ = rtpbuf[:i]
				}
			}
		}
	})
	peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) {
		if i == nil {
			return
		}
		wm.CandidateMux.Lock()
		defer wm.CandidateMux.Unlock()
		desc := peerConnection.RemoteDescription()
		if desc == nil {
			logger.Println("generated candidate appended to list : ", i)
			wm.PendingCandidates[target] = append(wm.PendingCandidates[target], i)
		} else {
			logger.Println("generated candidate : ", i)
			if iceCandidateErr := cb(target, i); iceCandidateErr != nil {
				logger.Println(iceCandidateErr)
			}
		}
	})
	return
}

func (wm *WebRTCCallManager) HandleRennegotiationOffer(from string, sdp string) (err error) {
	wm.RTCPeerConnectionMapMux.Lock()
	defer wm.RTCPeerConnectionMapMux.Unlock()
	if _, ok := wm.RTCPeerConnections[from]; !ok {
		err = fmt.Errorf("no corresponding peer connection for id %s", from)
		return
	}
	wm.RTCPeerConnections[from].makingOfferLock.Lock()
	if wm.RTCPeerConnections[from].makingOffer {
		wm.RTCPeerConnections[from].makingOfferLock.Unlock()
		return fmt.Errorf("already making an offer or state is stable")
	}
	wm.RTCPeerConnections[from].makingOfferLock.Unlock()
	if err = wm.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil {
		return
	}
	localSd, err := wm.RTCPeerConnections[from].CreateAnswer(nil)
	if err != nil {
		return
	}
	if err = wm.RTCPeerConnections[from].SetLocalDescription(localSd); err != nil {
		return
	}
	if err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_ANSWER), wm.ID, from, map[string]interface{}{
		"to":  from,
		"sdp": localSd.SDP,
	}); err != nil {
		logger.Println(err)
		return
	}
	return
}

func (wm *WebRTCCallManager) HandleRennegotiationAnswer(from string, sdp string) (err error) {
	wm.RTCPeerConnectionMapMux.Lock()
	defer wm.RTCPeerConnectionMapMux.Unlock()
	wm.RTCPeerConnections[from].makingOfferLock.Lock()
	if wm.RTCPeerConnections[from].makingOffer {
		wm.RTCPeerConnections[from].makingOfferLock.Unlock()
		return fmt.Errorf("already making an offer or state is stable")
	}
	wm.RTCPeerConnections[from].makingOfferLock.Unlock()
	if _, ok := wm.RTCPeerConnections[from]; !ok {
		err = fmt.Errorf("no corresponding peer connection for id %s", from)
		return
	}
	err = wm.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer})
	return
}

func (wm *WebRTCCallManager) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) {
	wm.RTCPeerConnectionMapMux.Lock()
	defer wm.RTCPeerConnectionMapMux.Unlock()
	if candidate != nil {
		err = wm.RTCPeerConnections[from].AddICECandidate(*candidate)
	}
	return
}

func (wm *WebRTCCallManager) HandleLeavingMember(id string, squadId string) {
	wm.RTCPeerConnectionMapMux.Lock()
	if _, ok := wm.RTCPeerConnections[id]; !ok {
		wm.RTCPeerConnectionMapMux.Unlock()
		return
	}
	wm.RTCPeerConnectionMapMux.Unlock()
	defer func() {
		wm.RTCPeerConnectionMapMux.Lock()
		if _, ok := wm.RTCPeerConnections[id]; ok {
			if closeErr := wm.RTCPeerConnections[id].Close(); closeErr != nil {
				logger.Println("peer connection close error", closeErr)
			}
		}
		delete(wm.RTCPeerConnections, id)
		wm.RTCPeerConnectionMapMux.Unlock()
	}()
	logger.Printf("peer %s is leaving the squad\n", id)
	wm.DataChannelMapMux.Lock()
	if _, ok := wm.DataChannels[id]; ok {
		wm.DataChannels[id].DataChannel.Close()
	}
	delete(wm.DataChannels, id)
	wm.DataChannelMapMux.Unlock()
	wm.LocalSDMapMux.Lock()
	delete(wm.LocalSD, id)
	wm.LocalSDMapMux.Unlock()
	delete(wm.PendingCandidates, id)
	wm.RemoteTracksMux.Lock()
	delete(wm.RemoteTracks, id)
	wm.RemoteTracksMux.Unlock()
	wm.AudioSenderMux.Lock()
	for peerId, peerSender := range wm.AudioTransceiver {
		if peerId != id {
			logger.Println("senders", peerSender)
			c := 0
			for i, sender := range peerSender {
				if sender.ID == id {
					if senderErr := sender.Transceiver.Sender().Stop(); senderErr != nil {
						logger.Println(senderErr)
					}
					if transceiverErr := sender.Transceiver.Stop(); transceiverErr != nil {
						logger.Println("transceiverErr occured with video", transceiverErr)
					}
					peerSender[len(peerSender)-i-1], peerSender[i] = peerSender[i], peerSender[len(peerSender)-i-1]
					c++
				}
			}
			wm.AudioTransceiver[peerId] = wm.AudioTransceiver[peerId][:len(peerSender)-(c)]
			logger.Println(wm.AudioTransceiver[peerId])
		}
	}
	for _, transceiver := range wm.AudioTransceiver[id] {
		if senderErr := transceiver.Transceiver.Sender().Stop(); senderErr != nil {
			logger.Println(senderErr)
		}
		if stopErr := transceiver.Transceiver.Stop(); stopErr != nil {
			logger.Println("transceiver audio stop error", stopErr)
		}
	}
	delete(wm.AudioTransceiver, id)
	wm.AudioSenderMux.Unlock()
	wm.VideoSenderMux.Lock()
	for peerId, peerSender := range wm.VideoTransceiver {
		if peerId != id {
			c := 0
			logger.Println("senders", peerSender)
			for i, sender := range peerSender {
				if sender.ID == id {
					if senderErr := sender.Transceiver.Sender().Stop(); senderErr != nil {
						logger.Println(senderErr)
					}
					if transceiverErr := sender.Transceiver.Stop(); transceiverErr != nil {
						logger.Println("transceiverErr occured with video", transceiverErr)
					}
					peerSender[len(peerSender)-i-1], peerSender[i] = peerSender[i], peerSender[len(peerSender)-i-1]
					c++
				}
			}
			wm.VideoTransceiver[peerId] = wm.VideoTransceiver[peerId][:len(peerSender)-(c)]
			logger.Println(wm.VideoTransceiver[peerId])
		}
	}
	for _, transceiver := range wm.VideoTransceiver[id] {
		if senderErr := transceiver.Transceiver.Sender().Stop(); senderErr != nil {
			logger.Println(senderErr)
		}
		if stopErr := transceiver.Transceiver.Stop(); stopErr != nil {
			logger.Println("transceiver video stop error", stopErr)
		}
	}
	delete(wm.VideoTransceiver, id)
	wm.VideoSenderMux.Unlock()
	if _, ok := wm.Squads[squadId]; ok {
		wm.SquadMapMux.Lock()
		var index int
		for i, v := range wm.Squads[squadId].Members {
			if v == id {
				index = i
				break
			}
		}
		if len(wm.Squads[squadId].Members) < 2 {
			wm.Squads[squadId].Members = []string{}
		} else {
			wm.Squads[squadId].Members = append(wm.Squads[squadId].Members[:index], wm.Squads[squadId].Members[index+1:]...)
		}
		wm.SquadMapMux.Unlock()
	}
}

func (wm *WebRTCCallManager) negotiate(target string, squadId string) {
	wm.RTCPeerConnectionMapMux.Lock()
	defer wm.RTCPeerConnectionMapMux.Unlock()
	if _, ok := wm.RTCPeerConnections[target]; !ok {
		return
	}
	wm.RTCPeerConnections[target].makingOfferLock.Lock()
	wm.RTCPeerConnections[target].makingOffer = true
	wm.RTCPeerConnections[target].makingOfferLock.Unlock()
	defer func() {
		wm.RTCPeerConnections[target].makingOfferLock.Lock()
		wm.RTCPeerConnections[target].makingOffer = false
		wm.RTCPeerConnections[target].makingOfferLock.Unlock()
	}()
	wm.SquadMapMux.RLock()
	defer wm.SquadMapMux.RUnlock()
	for _, id := range wm.Squads[squadId].Members {
		if _, ok := wm.RTCPeerConnections[id]; !ok {
			continue
		}
		connection := wm.RTCPeerConnections[id]
		if connection.SignalingState() == webrtc.SignalingStateStable {
			localSd, err := connection.CreateOffer(nil)
			if err != nil {
				logger.Println(err)
				return
			}
			if err = connection.SetLocalDescription(localSd); err != nil {
				logger.Println(err)
				return
			}
			if err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_OFFER), wm.ID, id, map[string]interface{}{
				"to":  id,
				"sdp": localSd.SDP,
			}); err != nil {
				logger.Println(err)
				return
			}
		}
	}
}
