frappe.socketio = {
	open_tasks: {},
	open_docs: [],
	emit_queue: [],
	init: function(port = 3000) {
		if (!window.io) {
			return;
		}

		if (frappe.boot.disable_async) {
			return;
		}

		if (frappe.socketio.socket) {
			return;
		}

		//Enable secure option when using HTTPS
		if (window.location.protocol == "https:") {
			frappe.socketio.socket = io.connect(frappe.socketio.get_host(port), {secure: true});
		}
		else if (window.location.protocol == "http:") {
			frappe.socketio.socket = io.connect(frappe.socketio.get_host(port));
		}
		else if (window.location.protocol == "file:") {
			frappe.socketio.socket = io.connect(window.localStorage.server);
		}

		if (!frappe.socketio.socket) {
			console.log("Unable to connect to " + frappe.socketio.get_host(port));
			return;
		}

		frappe.socketio.socket.on('msgprint', function(message) {
			frappe.msgprint(message);
		});

		frappe.socketio.socket.on('eval_js', function(message) {
			eval(message);
		});

		frappe.socketio.socket.on('progress', function(data) {
			if(data.progress) {
				data.percent = flt(data.progress[0]) / data.progress[1] * 100;
			}
			if(data.percent) {
				if(data.percent==100) {
					frappe.hide_progress();
				} else {
					frappe.show_progress(data.title || __("Progress"), data.percent, 100, data.description);
				}
			}
		});

		frappe.socketio.setup_listeners();
		frappe.socketio.setup_reconnect();
		frappe.socketio.uploader = new frappe.socketio.SocketIOUploader();

		$(document).on('form-load form-rename', function(e, frm) {
			if (frm.is_new()) {
				return;
			}

			for (var i=0, l=frappe.socketio.open_docs.length; i<l; i++) {
				var d = frappe.socketio.open_docs[i];
				if (frm.doctype==d.doctype && frm.docname==d.name) {
					// already subscribed
					return false;
				}
			}

			frappe.socketio.doc_subscribe(frm.doctype, frm.docname);
		});

		$(document).on("form-refresh", function(e, frm) {
			if (frm.is_new()) {
				return;
			}

			frappe.socketio.doc_open(frm.doctype, frm.docname);
		});

		$(document).on('form-unload', function(e, frm) {
			if (frm.is_new()) {
				return;
			}

			// frappe.socketio.doc_unsubscribe(frm.doctype, frm.docname);
			frappe.socketio.doc_close(frm.doctype, frm.docname);
		});

		window.onbeforeunload = function() {
			if (!cur_frm || cur_frm.is_new()) {
				return;
			}

			// if tab/window is closed, notify other users
			if (cur_frm.doc) {
				frappe.socketio.doc_close(cur_frm.doctype, cur_frm.docname);
			}
		}
	},
	get_host: function(port = 3000) {
		var host = window.location.origin;
		if(window.dev_server) {
			var parts = host.split(":");
			port = frappe.boot.socketio_port || port.toString() || '3000';
			if(parts.length > 2) {
				host = parts[0] + ":" + parts[1];
			}
			host = host + ":" + port;
		}
		return host;
	},
	subscribe: function(task_id, opts) {
		// TODO DEPRECATE

		frappe.socketio.socket.emit('task_subscribe', task_id);
		frappe.socketio.socket.emit('progress_subscribe', task_id);

		frappe.socketio.open_tasks[task_id] = opts;
	},
	task_subscribe: function(task_id) {
		frappe.socketio.socket.emit('task_subscribe', task_id);
	},
	task_unsubscribe: function(task_id) {
		frappe.socketio.socket.emit('task_unsubscribe', task_id);
	},
	doc_subscribe: function(doctype, docname) {
		if (frappe.flags.doc_subscribe) {
			console.log('throttled');
			return;
		}

		frappe.flags.doc_subscribe = true;

		// throttle to 1 per sec
		setTimeout(function() { frappe.flags.doc_subscribe = false }, 1000);

		frappe.socketio.socket.emit('doc_subscribe', doctype, docname);
		frappe.socketio.open_docs.push({doctype: doctype, docname: docname});
	},
	doc_unsubscribe: function(doctype, docname) {
		frappe.socketio.socket.emit('doc_unsubscribe', doctype, docname);
		frappe.socketio.open_docs = $.filter(frappe.socketio.open_docs, function(d) {
			if(d.doctype===doctype && d.name===docname) {
				return null;
			} else {
				return d;
			}
		})
	},
	doc_open: function(doctype, docname) {
		// notify that the user has opened this doc, if not already notified
		if(!frappe.socketio.last_doc
			|| (frappe.socketio.last_doc[0]!=doctype && frappe.socketio.last_doc[1]!=docname)) {
			frappe.socketio.socket.emit('doc_open', doctype, docname);
		}
		frappe.socketio.last_doc = [doctype, docname];
	},
	doc_close: function(doctype, docname) {
		// notify that the user has closed this doc
		frappe.socketio.socket.emit('doc_close', doctype, docname);
	},

	setup_listeners: function() {
		frappe.socketio.socket.on('task_status_change', function(data) {
			frappe.socketio.process_response(data, data.status.toLowerCase());
		});
		frappe.socketio.socket.on('task_progress', function(data) {
			frappe.socketio.process_response(data, "progress");
		});
	},
	setup_reconnect: function() {
		// subscribe again to open_tasks
		frappe.socketio.socket.on("connect", function() {
			// wait for 5 seconds before subscribing again
			// because it takes more time to start python server than nodejs server
			// and we use validation requests to python server for subscribing
			setTimeout(function() {
				$.each(frappe.socketio.open_tasks, function(task_id, opts) {
					frappe.socketio.subscribe(task_id, opts);
				});

				// re-connect open docs
				$.each(frappe.socketio.open_docs, function(d) {
					if(locals[d.doctype] && locals[d.doctype][d.name]) {
						frappe.socketio.doc_subscribe(d.doctype, d.name);
					}
				});

				if (cur_frm && cur_frm.doc) {
					frappe.socketio.doc_open(cur_frm.doc.doctype, cur_frm.doc.name);
				}
			}, 5000);
		});
	},
	process_response: function(data, method) {
		if(!data) {
			return;
		}

		// success
		var opts = frappe.socketio.open_tasks[data.task_id];
		if(opts[method]) {
			opts[method](data);
		}

		// "callback" is std frappe term
		if(method==="success") {
			if(opts.callback) opts.callback(data);
		}

		// always
		frappe.request.cleanup(opts, data);
		if(opts.always) {
			opts.always(data);
		}

		// error
		if(data.status_code && data.status_code > 400 && opts.error) {
			opts.error(data);
		}
	}
}

frappe.provide("frappe.realtime");
frappe.realtime.on = function(event, callback) {
	frappe.socketio.socket && frappe.socketio.socket.on(event, callback);
};

frappe.realtime.off = function(event, callback) {
	frappe.socketio.socket && frappe.socketio.socket.off(event, callback);
}

frappe.realtime.publish = function(event, message) {
	if(frappe.socketio.socket) {
		frappe.socketio.socket.emit(event, message);
	}
}

frappe.socketio.SocketIOUploader = class SocketIOUploader {
	constructor() {
		frappe.socketio.socket.on('upload-request-slice', (data) => {
			var place = data.currentSlice * this.chunk_size,
				slice = this.file.slice(place,
					place + Math.min(this.chunk_size, this.file.size - place));

			if (this.on_progress) {
				// update progress
				this.on_progress(place / this.file.size * 100);
			}

			this.reader.readAsArrayBuffer(slice);
			this.started = true;
			this.keep_alive();
		});

		frappe.socketio.socket.on('upload-end', (data) => {
			this.reader = null;
			this.file = null;
			if (data.file_url.substr(0, 7)==='/public') {
				data.file_url = data.file_url.substr(7);
			}
			this.callback(data);
		});

		frappe.socketio.socket.on('upload-error', (data) => {
			this.disconnect(false);
			frappe.msgprint({
				title: __('Upload Failed'),
				message: data.error,
				indicator: 'red'
			});
		});

		frappe.socketio.socket.on('disconnect', () => {
			this.disconnect();
		});
	}

	start({file=null, is_private=0, filename='', callback=null, on_progress=null,
		chunk_size=24576, fallback=null} = {}) {

		if (this.reader) {
			frappe.throw(__('File Upload in Progress. Please try again in a few moments.'));
		}

		function fallback_required() {
			return !frappe.boot.sysdefaults.use_socketio_to_upload_file || !frappe.socketio.socket.connected;
		}

		if (fallback_required()) {
			return fallback ? fallback() : frappe.throw(__('Socketio is not connected. Cannot upload'));
		}

		this.reader = new FileReader();
		this.file = file;
		this.chunk_size = chunk_size;
		this.callback = callback;
		this.on_progress = on_progress;
		this.fallback = fallback;
		this.started = false;

		this.reader.onload = () => {
			frappe.socketio.socket.emit('upload-accept-slice', {
				is_private: is_private,
				name: filename,
				type: this.file.type,
				size: this.file.size,
				data: this.reader.result
			});
			this.keep_alive();
		};

		var slice = file.slice(0, this.chunk_size);
		this.reader.readAsArrayBuffer(slice);
	}

	keep_alive() {
		if (this.next_check) {
			clearTimeout (this.next_check);
		}
		this.next_check = setTimeout (() => {
			if (!this.started) {
				// upload never started, so try fallback
				if (this.fallback) {
					this.fallback();
				} else {
					this.disconnect();
				}
			}
			this.disconnect();
		}, 3000);
	}

	disconnect(with_message = true) {
		if (this.reader) {
			this.reader = null;
			this.file = null;
			frappe.hide_progress();
			if (with_message) {
				frappe.msgprint({
					title: __('File Upload'),
					message: __('File Upload Disconnected. Please try again.'),
					indicator: 'red'
				});
			}
		}
	}
}
