package localserver

import (
	"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 (
	AUDIO_CHANNEL_ACCESS_DENIED                ReqType = "audio_channel_access_denied"
	AUDIO_CHANNEL_STOP_CALL                    ReqType = "audio_channel_stop_call"
	AUDIO_CHANNEL_ACCESS_GRANTED               ReqType = "audio_channel_access_granted"
	AUDIO_CHANNEL_WEBRTC_OFFER                 ReqType = "audio_channel_offer"
	AUDIO_CHANNEL_WEBRTC_ANSWER                ReqType = "audio_channel_answer"
	AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER  ReqType = "audio_channel_rennegotiation_offer"
	AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER ReqType = "audio_channel_rennegotiation_answer"
	AUDIO_CHANNEL_WEBRTC_COUNTER_OFFER         ReqType = "audio_channel_webrtc_counter_offer"
	AUDIO_CHANNEL_WEBRTC_CANDIDATE             ReqType = "audio_channel_webrtc_candidate"
	AUDIO_CHANNEL_REMOVE_VIDEO                 ReqType = "audio_channel_remove_video"
	GET_AUDIO_CHANNEL_TRACKS                   ReqType = "audio_channel_get_tracks"
)

const (
	AUDIO_CHANNEL_USER_MUTE             = "audio_channel_user_mute"
	AUDIO_CHANNEL_USER_UNMUTE           = "audio_channel_user_unmute"
	AUDIO_CHANNEL_USER_SPEAKING         = "audio_channel_user_speaking"
	AUDIO_CHANNEL_USER_STOPPED_SPEAKING = "audio_channel_user_stopped_speaking"
)

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

type AudioChannel struct {
	ID                       string                                `json:"id"`
	Owner                    string                                `json:"owner"`
	ChannelType              string                                `json:"channelType"`
	Members                  []string                              `json:"members"`
	CurrentMembersId         []string                              `json:"currentMembersId"`
	CurrentMembers           map[string]*AudioChannelMember        `json:"currentMembers"`
	localSD                  map[string]*webrtc.SessionDescription `json:"-"`
	rtcPeerConnections       map[string]*ZoneRTCPeerConnection     `json:"-"`
	audioTransceiver         map[string][]*PeerSender              `json:"-"`
	audiChannelsDataChannels map[string]*DataChannel               `json:"-"`
	pendingCandidates        map[string][]*webrtc.ICECandidate     `json:"-"`
	remoteTracks             map[string][]*RemoteTrack             `json:"-"`
	middlewares              []interface{}                         `json:"-"`
	candidateFlag            *uint32                               `json:"-"`
	remoteTracksFlag         *uint32                               `json:"-"`
	rtcPeerConnectionMapFlag *uint32                               `json:"-"`
	dataChannelMapFlag       *uint32                               `json:"-"`
	localSDMapFlag           *uint32                               `json:"-"`
	audioSenderFlag          *uint32                               `json:"-"`
}

type AudioChannelOnICECandidateFunc = func(string, string, *webrtc.ICECandidate) error

func NewAudioChannel(id string, owner string, channelType string, members []string, currentMembersId []string, currentMembers map[string]*AudioChannelMember) (audioChannel *AudioChannel) {
	candidateFlag := uint32(0)
	remoteTracksFlag := uint32(0)
	rtcPeerConnectionMapFlag := uint32(0)
	dataChannelMapFlag := uint32(0)
	localSDMapFlag := uint32(0)
	audioSenderFlag := uint32(0)
	audioChannel = &AudioChannel{
		ID:                       id,
		Owner:                    owner,
		ChannelType:              channelType,
		Members:                  members,
		CurrentMembersId:         currentMembersId,
		CurrentMembers:           currentMembers,
		localSD:                  make(map[string]*webrtc.SessionDescription),
		rtcPeerConnections:       make(map[string]*ZoneRTCPeerConnection),
		audioTransceiver:         make(map[string][]*PeerSender),
		audiChannelsDataChannels: make(map[string]*DataChannel),
		pendingCandidates:        make(map[string][]*webrtc.ICECandidate),
		remoteTracks:             make(map[string][]*RemoteTrack),
		middlewares:              make([]interface{}, 0),
		candidateFlag:            &candidateFlag,
		remoteTracksFlag:         &remoteTracksFlag,
		rtcPeerConnectionMapFlag: &rtcPeerConnectionMapFlag,
		dataChannelMapFlag:       &dataChannelMapFlag,
		localSDMapFlag:           &localSDMapFlag,
		audioSenderFlag:          &audioSenderFlag,
	}
	return
}

func (ac *AudioChannel) HandleOffer(ctx context.Context, channelId string, userId string, sdp string, hostId string, sendDCMessage SendDCMessageFunc, cb AudioChannelOnICECandidateFunc) (done chan struct{}, errCh chan error) {
	done, errCh = make(chan struct{}), make(chan error)
	go func() {
		peerConnection, err := ac.createPeerConnection(userId, ac.ID, webrtc.SDPTypeAnswer, cb, sendDCMessage)
		if err != nil {
			errCh <- err
			return
		}
		_ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
			ac.rtcPeerConnections[userId] = &ZoneRTCPeerConnection{
				PeerConnection:  peerConnection,
				makingOffer:     false,
				makingOfferLock: &sync.Mutex{},
				negotiate:       ac.negotiate,
			}
			return
		})
		offer := webrtc.SessionDescription{
			Type: webrtc.SDPTypeOffer,
			SDP:  sdp,
		}
		if err = peerConnection.SetRemoteDescription(offer); err != nil {
			errCh <- err
			return
		}
		rawAnswer, err := peerConnection.CreateAnswer(nil)
		if err != nil {
			errCh <- err
			return
		}
		if err = peerConnection.SetLocalDescription(rawAnswer); err != nil {
			errCh <- err
			return
		}
		_, _ = sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_ANSWER), hostId, userId, map[string]interface{}{
			"to":        userId,
			"from":      ac.ID,
			"channelId": channelId,
			"sdp":       rawAnswer.SDP,
		})
		done <- struct{}{}
		logger.Println("handle offer done")
	}()
	return
}

func (ac *AudioChannel) HandleCounterOffer(ctx context.Context, userId string, sendDCMessage SendDCMessageFunc) (err error) {
	// if err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
	// 	if _, ok := ac.rtcPeerConnections[userId]; !ok {
	// 		err = fmt.Errorf("no field corresponding peer connection for id %s", userId)
	// 		return
	// 	}
	// 	logger.Println("handling counter offer")
	// connection := ac.rtcPeerConnections[userId]
	// err = atomicallyExecute(ac.localSDMapFlag, func() (err error) {
	// 	err = connection.SetLocalDescription(*ac.localSD[userId])
	// 	return
	// })
	// 	return
	// }); err != nil {
	// 	return
	// }
	// _ = atomicallyExecute(ac.localSDMapFlag, func() (err error) {
	// 	delete(ac.localSD, userId)
	// 	return
	// })
	if err = atomicallyExecute(ac.candidateFlag, func() (err error) {
		for _, candidate := range ac.pendingCandidates[userId] {
			logger.Println("sending candidate to", userId, candidate)
			d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_CANDIDATE), "", userId, map[string]interface{}{
				"from":          ac.ID,
				"to":            userId,
				"candidate":     candidate.ToJSON().Candidate,
				"sdpMid":        *candidate.ToJSON().SDPMid,
				"sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)),
			})
			select {
			case <-d:
			case err = <-e:
				return
			}
		}
		delete(ac.pendingCandidates, userId)
		return
	}); err != nil {
		return
	}
	return
}

func (ac *AudioChannel) HandleRennegotiationOffer(from string, sdp string, sendDCMessage SendDCMessageFunc) (err error) {
	err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
		if _, ok := ac.rtcPeerConnections[from]; !ok {
			err = fmt.Errorf("no corresponding peer connection for id %s", from)
			return
		}
		ac.rtcPeerConnections[from].makingOfferLock.Lock()
		if ac.rtcPeerConnections[from].makingOffer {
			ac.rtcPeerConnections[from].makingOfferLock.Unlock()
			return fmt.Errorf("already making an offer or state is stable")
		}
		ac.rtcPeerConnections[from].makingOfferLock.Unlock()
		if err = ac.rtcPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil {
			return
		}
		localSd, err := ac.rtcPeerConnections[from].CreateAnswer(nil)
		if err != nil {
			return
		}
		if err = ac.rtcPeerConnections[from].SetLocalDescription(localSd); err != nil {
			return
		}
		d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER), ac.ID, from, map[string]interface{}{
			"from": ac.ID,
			"to":   from,
			"sdp":  localSd.SDP,
		})
		select {
		case <-d:
		case err = <-e:
		}
		return
	})
	return
}

func (ac *AudioChannel) HandleRennegotiationAnswer(from string, sdp string) (err error) {
	logger.Println("---------------------handling rennego answer")
	err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
		ac.rtcPeerConnections[from].makingOfferLock.Lock()
		if ac.rtcPeerConnections[from].makingOffer {
			ac.rtcPeerConnections[from].makingOfferLock.Unlock()
			return fmt.Errorf("already making an offer or state is stable")
		}
		ac.rtcPeerConnections[from].makingOfferLock.Unlock()
		if _, ok := ac.rtcPeerConnections[from]; !ok {
			err = fmt.Errorf("no corresponding peer connection for id %s", from)
			return
		}
		err = ac.rtcPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer})
		return
	})
	return
}

func (ac *AudioChannel) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) {
	logger.Println("adding ice candidate", candidate)
	err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
		if _, ok := ac.rtcPeerConnections[from]; ok && candidate != nil {
			err = ac.rtcPeerConnections[from].AddICECandidate(*candidate)
		}
		return
	})
	return
}

func (ac *AudioChannel) createPeerConnection(target string, from string, peerType webrtc.SDPType, cb AudioChannelOnICECandidateFunc, sendDCMessage SendDCMessageFunc) (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
			}
			if e := ac.HandleDataChannelEvents(event.From, event.EventId, event.Payload); e != nil {
				logger.Println("*-------------- datachannel error: ", e)
			}
		})
		logger.Println("new channel for target : ", target)
		channel.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold)
		channel.OnBufferedAmountLow(func() {

		})
		_ = atomicallyExecute(ac.dataChannelMapFlag, func() (err error) {
			logger.Println(target)
			l := uint32(0)
			ac.audiChannelsDataChannels[target] = &DataChannel{
				DataChannel: channel,
				l:           &l,
			}
			return
		})
	} else {
		peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) {
			_ = atomicallyExecute(ac.dataChannelMapFlag, func() (err error) {
				l := uint32(0)
				ac.audiChannelsDataChannels[target] = &DataChannel{
					DataChannel: dc,
					l:           &l,
				}
				return
			})
			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
				}
				if e := ac.HandleDataChannelEvents(event.From, event.EventId, event.Payload); e != nil {
					logger.Println("*-------------- datachannel error: ", e)
				}
			})
		})
	}
	err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
		err = atomicallyExecute(ac.remoteTracksFlag, func() (err error) {
			logger.Println("------------------", ac.CurrentMembersId)
			for _, id := range ac.CurrentMembersId {
				logger.Println(id)
				if id != target {
					if _, ok := ac.remoteTracks[id]; !ok {
						continue
					}
					for _, track := range ac.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
						}

						_ = atomicallyExecute(ac.audioSenderFlag, func() (err error) {
							if len(ac.audioTransceiver) == 0 {
								ac.audioTransceiver[id] = []*PeerSender{{ID: target, Transceiver: transceiver}}
							} else {
								ac.audioTransceiver[id] = append(ac.audioTransceiver[id], &PeerSender{ID: target, Transceiver: transceiver})
							}
							return
						})
						logger.Println("track added", track)
					}
				}
			}
			return
		})
		return
	})
	peerConnection.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) {
		if pcs == webrtc.PeerConnectionStateClosed || pcs == webrtc.PeerConnectionStateDisconnected || pcs == webrtc.PeerConnectionStateFailed {
			logger.Println(pcs)
			//ac.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())
		logger.Println("*************************----------------", i, "-----------------------***************")
		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}
		_ = atomicallyExecute(ac.remoteTracksFlag, func() (err error) {
			if len(ac.remoteTracks[target]) == 0 {
				ac.remoteTracks[target] = []*RemoteTrack{remote}
			} else {
				ac.remoteTracks[target] = append(ac.remoteTracks[target], remote)
			}
			index := len(ac.remoteTracks[target])
			logger.Println(index, ac.remoteTracks)
			return
		})
		_ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
			for _, id := range ac.CurrentMembersId {
				if id != target {
					if _, ok := ac.rtcPeerConnections[id]; !ok {
						continue
					}
					connection := ac.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 {
						_ = atomicallyExecute(ac.audioSenderFlag, func() (err error) {
							if len(ac.audioTransceiver) == 0 {
								ac.audioTransceiver[target] = []*PeerSender{{ID: id, Transceiver: transceiver}}
							} else {
								ac.audioTransceiver[target] = append(ac.audioTransceiver[target], &PeerSender{ID: id, Transceiver: transceiver})
							}
							return
						})
					} else if localTrack.Kind() == webrtc.RTPCodecTypeVideo {
						logger.Println("track of wrong type")
					}
					go func() {
						<-time.After(time.Millisecond * 500)
						connection.negotiate(id, sendDCMessage)
					}()
				}
			}
			return
		})
		d := make(chan struct{})
		go func() {
			for {
				i, _, readErr := tr.Read(rtpbuf)
				if readErr != nil {
					logger.Println(readErr)
					break
				}
				//logger.Println(rtpbuf[:i])
				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]
					}
				}
			}
			d <- struct{}{}
		}()
		<-d
	})
	peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) {
		if i == nil {
			return
		}
		_ = atomicallyExecute(ac.candidateFlag, func() (err error) {
			desc := peerConnection.RemoteDescription()
			if desc == nil {
				logger.Println("generated candidate appended to list : ", i)
				ac.pendingCandidates[target] = append(ac.pendingCandidates[target], i)
			} else {
				logger.Println("generated candidate : ", i)
				if iceCandidateErr := cb(from, target, i); iceCandidateErr != nil {
					logger.Println(iceCandidateErr)
				}
			}
			return
		})
	})
	//peerConnection.OnNegotiationNeeded(func() {
	//logger.Println("---------------- rennego is needed -----------")
	// _ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
	// 	for _, id := range ac.CurrentMembersId {
	// 		logger.Println("----------------- sending renego to peer with id", id)
	// 		if _, ok := ac.rtcPeerConnections[id]; !ok {
	// 			continue
	// 		}
	// 		if peerConnection.SignalingState() == webrtc.SignalingStateStable {
	// 			localSd, localSdErr := peerConnection.CreateOffer(nil)
	// 			if localSdErr != nil {
	// 				logger.Println(localSdErr)
	// 				return localSdErr
	// 			}
	// 			if err = peerConnection.SetLocalDescription(localSd); err != nil {
	// 				logger.Println(err)
	// 				return
	// 			}
	// 			d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER), ac.ID, id, map[string]interface{}{
	// 				"from": ac.ID,
	// 				"to":   id,
	// 				"sdp":  localSd.SDP,
	// 			})
	// 			select {
	// 			case <-d:
	// 			case err = <-e:
	// 				logger.Println(err)
	// 			}
	// 		}
	// 	}
	// 	return
	// })
	//})
	return
}

func (ac *AudioChannel) HandleLeavingMember(id string) {
	if err := atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
		if _, ok := ac.rtcPeerConnections[id]; !ok {
			err = fmt.Errorf("no corresponding peerconnection for audio channel leaving member")
		}
		return
	}); err != nil {
		logger.Println(err)
	} else {
		defer func() {
			_ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
				if _, ok := ac.rtcPeerConnections[id]; ok {
					if closeErr := ac.rtcPeerConnections[id].Close(); closeErr != nil {
						err = closeErr
						logger.Println("peer connection close error", closeErr)
					}
				}
				delete(ac.rtcPeerConnections, id)
				return
			})
		}()
	}
	logger.Printf("peer %s is leaving the squad\n", id)
	_ = atomicallyExecute(ac.dataChannelMapFlag, func() (err error) {
		if _, ok := ac.audiChannelsDataChannels[id]; ok {
			ac.audiChannelsDataChannels[id].DataChannel.Close()
		}
		delete(ac.audiChannelsDataChannels, id)
		return
	})
	_ = atomicallyExecute(ac.localSDMapFlag, func() (err error) {
		delete(ac.localSD, id)
		return
	})
	_ = atomicallyExecute(ac.candidateFlag, func() (err error) {
		delete(ac.pendingCandidates, id)
		return
	})
	_ = atomicallyExecute(ac.audioSenderFlag, func() (err error) {
		for peerId, peerSender := range ac.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++
					}
				}
				ac.audioTransceiver[peerId] = ac.audioTransceiver[peerId][:len(peerSender)-(c)]
				logger.Println(ac.audioTransceiver[peerId])
			}
		}
		for _, transceiver := range ac.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(ac.audioTransceiver, id)
		return
	})
	_ = atomicallyExecute(ac.remoteTracksFlag, func() (err error) {
		delete(ac.remoteTracks, id)
		return
	})
}

func (ac *AudioChannel) negotiate(target string, sendDCMessage SendDCMessageFunc) {
	logger.Println("------------------negotiate is called")
	_ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
		if _, ok := ac.rtcPeerConnections[target]; !ok {
			return
		}
		ac.rtcPeerConnections[target].makingOfferLock.Lock()
		ac.rtcPeerConnections[target].makingOffer = true
		ac.rtcPeerConnections[target].makingOfferLock.Unlock()
		defer func() {
			ac.rtcPeerConnections[target].makingOfferLock.Lock()
			ac.rtcPeerConnections[target].makingOffer = false
			ac.rtcPeerConnections[target].makingOfferLock.Unlock()
		}()

		for _, id := range ac.CurrentMembersId {
			logger.Println("----------------- sending renego to peer with id", id)
			if _, ok := ac.rtcPeerConnections[id]; !ok {
				continue
			}
			connection := ac.rtcPeerConnections[id]
			if connection.SignalingState() == webrtc.SignalingStateStable {
				localSd, err := connection.CreateOffer(nil)
				if err != nil {
					logger.Println(err)
					return err
				}
				if err = connection.SetLocalDescription(localSd); err != nil {
					logger.Println(err)
					return err
				}
				d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER), ac.ID, id, map[string]interface{}{
					"from": ac.ID,
					"to":   id,
					"sdp":  localSd.SDP,
				})
				select {
				case <-d:
				case err = <-e:
					logger.Println(err)
				}
			}
		}
		return
	})
}

func (ac *AudioChannel) broadcastDatachannelMessage(from string, eventId string, payload map[string]interface{}) (done chan struct{}, errCh chan error) {
	done, errCh = make(chan struct{}), make(chan error)
	go func() {
		bs, jsonErr := json.Marshal(&ZoneResponse{
			Type:    eventId,
			From:    ac.ID,
			Payload: payload,
		})
		if jsonErr != nil {
			errCh <- jsonErr
			return
		}
		if err := atomicallyExecute(ac.dataChannelMapFlag, func() (err error) {
			for id, dc := range ac.audiChannelsDataChannels {
				if from != id {
					if err = dc.DataChannel.SendText(string(bs)); err != nil {
						return
					}
				}
			}
			return
		}); err != nil {
			errCh <- err
		}
		done <- struct{}{}
	}()
	return
}

func (ac *AudioChannel) HandleDataChannelEvents(from string, eventId string, payload map[string]interface{}) (err error) {
	switch eventId {
	case AUDIO_CHANNEL_USER_MUTE:
		if err = atomicallyExecute(ac.remoteTracksFlag, func() (err error) {
			if _, ok := ac.remoteTracks[from]; !ok {
				err = fmt.Errorf("no corresponding remote tracks entry for id %s", from)
				return
			}
			for _, track := range ac.remoteTracks[from] {
				atomic.SwapInt32(track.rdv, 1)
			}
			return
		}); err != nil {
			return
		}
		done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_MUTE, map[string]interface{}{
			"userId": from,
		})
		select {
		case <-done:
		case err = <-errCh:
		}
	case AUDIO_CHANNEL_USER_UNMUTE:
		if err = atomicallyExecute(ac.remoteTracksFlag, func() (err error) {
			if _, ok := ac.remoteTracks[from]; !ok {
				err = fmt.Errorf("no corresponding remote tracks entry for id %s", from)
				return
			}
			for _, track := range ac.remoteTracks[from] {
				atomic.SwapInt32(track.rdv, 0)
			}
			return
		}); err != nil {
			return
		}
		done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_UNMUTE, map[string]interface{}{
			"userId": from,
		})
		select {
		case <-done:
		case err = <-errCh:
		}
	case AUDIO_CHANNEL_USER_SPEAKING:
		done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_SPEAKING, map[string]interface{}{
			"userId": from,
		})
		select {
		case <-done:
		case err = <-errCh:
		}
	case AUDIO_CHANNEL_USER_STOPPED_SPEAKING:
		done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_STOPPED_SPEAKING, map[string]interface{}{
			"userId": from,
		})
		select {
		case <-done:
		case err = <-errCh:
		}
	}
	return
}
