var bricks = window.bricks || {}

bricks.VideoBox = class extends bricks.JsWidget {
	create(){
		this.dom_element = this._create('video');
		this.set_attribute('autoplay', true);
		this.set_attribute('muted', true);
	}
	get_stream(){
		return this.stream;
	}
	set_stream(stream){
		this.stream = stream
		this.dom_element.srcObject = this.stream;
	}
}

bricks.Signaling = class {
	/*
	{
		signaling_url:
		info:
		connect_opts:
		onclose:
		onlogin:
	}
	*/
	constructor(opts){
		this.signaling_url = opts.signaling_url;
		this.info = opts.info;
		this.connect_opts = opts.connect_opts;
		this.peers = [];
		this.sessions = {};
		this.handlers = {};
		this.sessionhandlers = {};
		this.init_websocket();
		this.hb_task = null;
		this.heartbeat_period = opts.heartbeat_period;
		this.onclose = opts.onclose;
		this.onlogin = opts.onlogin;
		this.onopen = opts.onopen;
		if (!this.heartbeat_period){
			this.heartbeat_period = 0;
		}
	}

	init_websocket(){
		var sessdata = bricks.app.get_session();
		this.socket = new WebSocket(this.signaling_url, sessdata);
		this.socket.onmessage = this.signaling_recvdata.bind(this);
		this.socket.onopen = this.login.bind(this);
		this.socket.onclose = this.reconnect.bind(this);
		this.socket.onerror = this.reconnect.bind(this);
	}
	reconnect(){
		console.log('eror happened');
		if (this.hb_task){
			this.hb_task.cancel();
			this.hb_task = null;
		}
		if (this.onclose){
			this.onclose();
		}
		return;
	}
	del_handler(sessionid){
		var handlers = {};
		delete self.handlers[sessionid];
	}
	async signaling_recvdata(event){
        var datapkg = JSON.parse(event.data);
        var data = datapkg.data;
		console.log('ws recv data=', data);
		if (data.session) {
			var sessionid = data.session.sessionid;
			var sessiontype = data.session.sessiontype;
			var handler = this.handlers[sessionid]
			if (!handler){
				var k = this.sessionhandlers[sessiontype];
				if (!k){
					throw 'recvdata_handler() exception(' + sessiontype + ')';
				}
				var h = new k(this, data.session, this.connect_opts);
				handler = h.recvdata_handler.bind(h);
				this.add_handler(sessionid, handler);
			}
			await handler(data);
		} else {
			await this.recvdata_handler(data);
		}
	}
	add_handler(key, handler){
		this.handlers[key] = handler;
	}
	add_sessionhandler(sessiontype, handler){
		this.sessionhandlers[sessiontype] = handler;
	}
	async recvdata_handler(data){
		console.log('recv data=', data);
		if (data.type == 'online'){
			data.online.forEach(p => {
				var d = this.peers[p.id];
				if (!d) d = {};
				d = bricks.extend(d, p);
				this.peers[p.id] = d;
			});
			if (this.onlogin){
				this.onlogin(data.online);
			}
			return;
		}
		console.log('recv data=', data, 'NOT HANDLED');
	}
	new_session(sessiontype, peer){
		var k = this.sessionhandlers[sessiontype];
		if (!k){
			throw 'new_session() exception(' + sessiontype + ')';
		}
		var sessionid = bricks.uuid();
		var session = {
			sessiontype:sessiontype,
			sessionid:sessionid
		}
		var d = {
			type:'new_session',
			session: session
		};
		this.send_data(d);
		var opts = bricks.extend({}, this.connect_options);
		opts.peer_info = peer;
		var h = new k(this, session, opts);
		this.add_handler(sessionid, h.recvdata_handler.bind(h) );
		return h
	}
	login(){
		console.log('login send', this.heartbeat_period)
		var d = {
			type:'login',
		}
		this.send_data(d);
		if (this.heartbeat_period > 0){
			console.log('call login again in', this.heartbeat_period, ' seconds');
			this.hb_task = schedule_once(this.login.bind(this), this.heartbeat_period);
		}
	}

	logout(){
		var d= {
			type:'logout',
		}
		this.send_data(d);
	}
	send_data(d){
		d.msgfrom = this.info;
		var s = JSON.stringify(d);
		console.log('send_data()', s);
		this.socket.send(s);
	}
	socket_send(s){
		this.socket.send(s);
	}
}

bricks.RTCP2PConnect = class {
	/*
	opts:{
		ice_servers:
		peer_info:
		auto_callaccept: true or false
		media_options: { video:trur or false, audio:true or false }
		data_connect: true or false
	}
	*/

	constructor(signaling, session, opts){
		this.id = bricks.uuid();
		this.signaling = signaling;
		this.session = session;
		this.requester = false;
		this.opts = opts;
		this.peers = {};
		this.signal_handlers = {};
		this.local_stream = null;
		this.localVideo = null;
		this.add_handler('sessioncreated', this.h_sessioncreated.bind(this));
		this.add_handler('callrequest', this.h_callrequest.bind(this));
		this.add_handler('callaccepted', this.h_callaccepted.bind(this));
		this.add_handler('offer', this.h_offer.bind(this));
		this.add_handler('answer', this.h_answer.bind(this));
		this.add_handler('icecandidate', this.h_icecandidate.bind(this));
		this.add_handler('sessionquit', this.h_sessionquit.bind(this));
	}

	add_handler(type, f){
		this.signal_handlers[type] = f;
	}
	get_handler(typ){
		return this.signal_handlers[typ];
	}

	async p2pconnect(peer){
		await this.getLocalStream();
		var p = this.peers[peer.id];
		if (!p){
			await this.createPeerConnection(peer);
		} else {
			aconsole.log(peer, 'connect exists', this);
		}
		console.log('p2pconnect() called, this=', this, 'peer=', peer);
	}
	async h_sessioncreated(data){
		await this.p2pconnect(this.opts.peer_info, 'requester');
		if (this.opts.peer_info){
			var d = {
				type:'callrequest',
				msgto:this.opts.peer_info
			}
			this.signaling_send(d);
			this.requester = true;
		}
	}
	async h_callrequest(data){
		if (this.opts.auto_callaccept || true){
			await this.p2pconnect(data.msgfrom, 'responser');
			var d = {
				type:'callaccepted',
				msgto:data.msgfrom
			};
			this.signaling_send(d);
			return;
		}
	}
	async h_callaccepted(data){
		this.createDataChannel(data.msgfrom);
		await this.send_offer(data.msgfrom, true);
	}
	async h_offer(data){
		console.log('h_offer(), this=', this, 'peer=', data.msgfrom);
		var pc = this.peers[data.msgfrom.id].pc;
		var offer = new RTCSessionDescription(data.offer);
		await pc.setRemoteDescription(offer);
		var answer = await pc.createAnswer();
		await pc.setLocalDescription(answer);
		this.signaling_send({
			type:'answer',
			answer:pc.localDescription,
			msgto:data.msgfrom
		});
		if (this.peers[data.msgfrom.id].role != 'requester'){
			await this.send_offer(data.msgfrom);
		}
		
	}
	async h_answer(data){
		var desc = new RTCSessionDescription(data.answer);
		var pc = this.peers[data.msgfrom.id].pc;
		await pc.setRemoteDescription(desc);
	}
	async h_icecandidate(data){
		var candidate = new RTCIceCandidate(data.candidate);
		var pc = this.peers[data.msgfrom.id].pc;
		await pc.addIceCandidate(candidate);
	}
	async h_sessionquit(data){
		var pc = this.peers[data.msgfrom.id].pc;
		pc.close();
	}
	async send_offer(peer, initial){
		console.log('send_offer(), peers=', this.peers, 'peer=', peer);
		var pc = this.peers[peer.id].pc;
		if (initial){
			this.peers[peer.id].role = 'requester';
		} else {
			this.peers[peer.id].role = 'responser';
		}
        var offer = await pc.createOffer();
        await pc.setLocalDescription(offer);
        var d = {
            type:'offer',
            offer:pc.localDescription,
            msgto:peer
        };
		this.signaling_send(d);
	}
	
	send_candidate(peer, event){
		console.log('send_candidate called, peer=', peer, 'event=', event); 
        if (event.candidate) {
            var candidate = event.candidate;
            this.signaling_send({ 
                type: 'icecandidate', 
                msgto:peer,
                candidate: candidate
            }); 
        }
	}

	signaling_send(d){
		d.session = this.session;
		this.signaling.send_data(d);
	}

	async recvdata_handler(data){
		console.log('recvdata=', data, this.signal_handlers);
		var f = this.get_handler(data.type);
		if (f) {
			await f(data);
			return;
		}
		console.log('recvdata=', data, 'NOT HANDLED');
	}

	async ice_statechange(peer, event){
		var pc = this.peers[peer.id].pc;
		console.log(`oniceconnectionstatechange, pc.iceConnectionState is ${pc.iceConnectionState}.`);
	}

	async connection_statechange(peer, event){
		var pc = this.peers[peer.id].pc;
		console.log(`${peer.id} state changed. new state=${pc.connectionState}`);
		console.log('state=', pc.connectionState, typeof(pc.connectionState));
		if (pc.connectionState == 'disconnected'){
			this.peer_close(peer);
			if (this.opts.on_pc_disconnected){
				this.opts.on_pc_disconnected(peer);
			}
            return;
        }
        if (pc.connectionState == 'connected'){
			console.log('state is connected, data_connect=',
						this.opts.data_connect);
			if(this.opts.on_pc_connected){
				this.opts.on_pc_connected(peer);
			}
        }
	}

	async dc_accepted(peer, event){
		console.log('accept datachannel ....');
		this.peers[peer.id].dc = event.channel;
		await this.dc_created(peer, this.peers[peer.id].dc);
	}
	async dc_created(peer, dc){
		console.log('dc_created.....', dc);
		dc.onmessage = this.datachannel_message.bind(peer);
		dc.onopen = this.datachannel_open(peer);
		dc.onclose = this.datachannel_close(peer);
	}

	async datachannel_message(peer, event){
		console.log('datachannel_message():', this, arguments);
		var dc = this.peers[peer.id].dc;
		if (this.opts.on_dc_messaage){
			await this.opts.on_dc_message(dc, event.data);
		}
	}
	async datachannel_open(peer){
		console.log('datachannel_open():', this, arguments);
		var dc = this.peers[peer.id].dc
		if (this.opts.on_dc_open){
			await this.opts.on_dc_open(dc);
		}
	}
	async datachannel_close(peer){
		console.log('datachannel_close():', this, arguments);
		var dc = this.peers[peer.id].dc
		if (this.opts.on_dc_close){
			await this.opts.on_dc_close(dc);
		}
	}
	async createDataChannel(peer){
		var pc = this.peers[peer.id].pc;
		this.peers[peer.id].dc = pc.createDataChannel('chat', {ordered:true});
		var dc = this.peers[peer.id].dc;
		await this.dc_created(peer, this.peers[peer.id].dc);
		console.log('dc created', this.peers[peer.id].dc);
	}
	async createPeerConnection(peer){
		const configuration = {
			iceServers:this.opts.ice_servers
		}
		console.log('RTCPC configuration=', configuration);
		var pc = new RTCPeerConnection(configuration);
		if (this.local_stream){
			this.local_stream.getTracks()
			.forEach(track => {
				pc.addTrack(track, this.local_stream);
			});
		}
		this.peers[peer.id] = bricks.extend({}, peer);
		this.peers[peer.id].pc = pc;
		this.peers[peer.id].role = '';
		var remoteVideo = new bricks.VideoBox();
		this.peers[peer.id].video = remoteVideo;
        pc.onicecandidate = this.send_candidate.bind(this, peer);
        pc.oniceconnectionstatechange = this.ice_statechange.bind(this, peer);
        pc.onconnectionstatechange = this.connection_statechange.bind(this, peer);
		pc.ondatachanel = this.dc_accepted.bind(this, peer);
		pc.ontrack = event => {
			remoteVideo.set_stream(event.streams[0]);
		}
	}
	async changeLocalVideoStream(peer, new_stream){
		var pc = this.peers[peer.id].pc;
	    const senders = pc.getSenders();
		const oldVideoTrackSender = senders.find(sender => sender.track && sender.track.kind === 'video');
		if (oldVideoTrackSender) {
			pc.removeTrack(oldVideoTrackSender);
		}
		new_stream.getTracks().forEach(track => {
			if (track.kind === 'video') {
				pc.addTrack(track, newStream);
			}
		});
		await this.send_offer(peer, true);
	}
	async getLocalStream() {
		if (this.opts.media_options){
			try {
				var mediaOptions = this.opts.media_options;
				this.local_stream = await navigator.mediaDevices.getUserMedia(mediaOptions);
				this.localVideo = new bricks.VideoBox();
				this.localVideo.set_stream(this.local_stream);
			} catch (error) {
				console.error('获取本地媒体流失败：', error);
			}
			try {
				this.local_screen = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false });
			} catch (error) {
				console.error('获取本地Screen失败：', error);
			}
		}
	}
	peer_close(peer){
		var pc = this.peers[peer.id].pc;
		var video = this.peers[peer.id].video;
		pc.getReceivers().forEach(receiver => {
			if (receiver.track){
				receiver.track.stop();
			}
		});
		pc.getSenders().forEach(receiver => {
			if (receiver.track){
				receiver.track.stop();
			}
		});
		video.get_stream().getTracks().forEach(track => track.stop());
		var dc = this.peers[peer.id].dc;
		if (dc){
			dc.close();
		}
		pc.close();
		delete this.peers[peer.id];
		var keys = Object.keys(this.peers);
		if (keys.length == 0){
			this.localVideo.get_stream()
			.getTracks().forEach(track => track.stop());
			this.local_stream.getTracks().forEach(track => track.stop());
			this.local_screan.getTracks().forEach(track => track.stop());
			this.signaling.del_handler(this.session.sessionid);
		}
	}
};

