//SIO-FIO (socket.io file.io)
//uploads files to the server without a bunch of mess
//todo: downloads files from the server without a bunch of mess

var util = require("util"),
	EventEmitter = require("events").EventEmitter,
	path = require("path"),
	fs = require("fs");

function SioFio(){
	
	"use strict";
	EventEmitter.call(this);
	var self = this;

	//Directory in which to save uploaded files.  null = do not save files
	self.dir = null;
	//What mode (UNIX permissions) in which to save uploaded files/
	self.mode = "0666";

	self.maxFileSize = null;

	var files = [];

	var _emitComplete = function(socket, id, success){
		var fileInfo = files[id];
		socket.emit("siofio_complete", {
			id: id,
			success: success,
			response: fileInfo.response
		});
	};
	
	self.__emitComplete = function(socket, id, success){
		var file = files[id];
		socket.emit("siofio_complete", {
			id: id,
			success: success,
			response: file.response
		});
	}

	var _findFileNameWorker = function(ext, base, inc, next){
		var newBase = (inc === -1) ? base : base + "-" + inc;
		var pathName = path.join(self.dir, newBase + ext);
		fs.exists(pathName, function(exists){
			if (exists){
				_findFileNameWorker(ext, base, inc+1, next);
			}
			else {
				fs.open(pathName, "w", self.mode, function(err, fd){
					if (err){
						next(err); return;
					}
					next(null, newBase, pathName, fd);
				});
			}
		});
	};

	//Private function to save an uploaded file.
	var _findFileName = function(fileInfo, next){
		var filesafeName = fileInfo.name.replace(/[^\w\-\.]/g, "_");
		var ext = path.extname(filesafeName);
		var base = path.basename(filesafeName, ext);
		// Use a recursive function to save the file under the first available filename.
		_findFileNameWorker(ext, base, -1, function(err, newBase, pathName, fd){
			if (err){
				next(err); return;
			}
			fs.close(fd, function(err){
				if (err){
					next(err); return;
				}
				next(null, newBase, pathName);
			});
		});
	};

	var _uploadDone = function(socket){
		return function(data){
			var fileInfo = files[data.id];
			try {
				if (fileInfo.writeStream){
					fileInfo.writeStream.end(function(){
						self.emit("saved", {
							file: fileInfo,
							callback: function(opts){
								if (opts) fileInfo.response = opts;
								self.__emitComplete(socket, data.id, fileInfo.success);
							}
						});
					});
					fs.utimes(fileInfo.pathName, new Date(), fileInfo.mtime, function(err){
						if (err){
							fileInfo.success = false;
							_emitComplete(socket, data.id, fileInfo.success);
							throw err;
						}
						//change this to the callback here
						//this self.emit stuff is silly (todo: done)
						/*self.emit("saved", {
							file: fileInfo,
							callback: function(opts){
								if (opts) fileInfo.response = opts;
								self.__emitComplete(socket, data.id, fileInfo.success);
							}
						});*/
						//_emitComplete(socket, data.id, fileInfo.success);
					});
				}
				else {
					_emitComplete(socket, data.id, fileInfo.success);
				}
			}
			catch (err){
				console.log("SocketIOFileUploadServer Error (_uploadDone):");
				console.log(err);
			}

			// Emit the "complete" event to the server-side listeners
			/*self.emit("complete", {
				file: fileInfo,
				interrupt: !!data.interrupt,
				callback: function(opts){
					if (opts) fileInfo.response = opts;
					self.__emitComplete(socket, data.id, fileInfo.success);
				}
			});*/
			
			//TODO!! REMOVE fileInfo FROM ARRAY HERE? allow GC
			//why do people write code this way
			//you don't need a global array to keep track of them, use scoping
			
		};
	};

	var _uploadProgress = function(socket){
		return function(data){
			var fileInfo = files[data.id], buffer;
			try {
			
				if (data.base64) buffer = new Buffer(data.content, "base64");
				else buffer = new Buffer(data.content);

				fileInfo.bytesLoaded += buffer.length;
				if (self.maxFileSize !== null &&
					fileInfo.bytesLoaded > self.maxFileSize){
					
				 	fileInfo.success = false;
					socket.emit("siofio_error", {
						id: data.id,
						message: "Max allowed file size exceeded"
					});
					/*self.emit("error", {
						file: fileInfo,
						error: new Error("Max allowed file size exceeded"),
						memo: "self-thrown from progress event"
					});*/
				} 
				else {
					if (fileInfo.writeStream){
						fileInfo.writeStream.write(buffer);
					}
				}
				//TODO: is this one useful?
				self.emit("progress", {
					file: fileInfo,
					buffer: buffer
				});
				
			}
			catch(err){
				console.log("SocketIOFileUploadServer Error (_uploadProgress):");
				console.log(err);
			}
		};
	};

	var _uploadStart = function(socket){
		return function(data){

			var fileInfo = {
				name: data.name,
				mtime: new Date(data.mtime),
				encoding: data.encoding,
				opts: data.opts || {},
				id: data.id,
				bytesLoaded: 0,
				success: true
			};
			files[data.id] = fileInfo;

			// Dispatch event to listeners on the server side
			self.emit("start", {
				file: fileInfo
			});
			
			if (!self.dir){
				socket.emit("siofio_ready", {
					id: data.id,
					name: null
				});
			}
			else {
				_findFileName(fileInfo, function(err, newBase, pathName){
					if (err){
						_emitComplete(socket, data.id, false);
						self.emit("error", {
							file: fileInfo,
							error: err,
							memo: "computing file name"
						});
						return;
					}

					files[data.id].base = newBase;
					files[data.id].pathName = pathName;

					try {
						var writeStream = fs.createWriteStream(pathName, {
							mode: self.mode
						});
						writeStream.on("open", function(){
							socket.emit("siofio_ready", {
								id: data.id,
								name: newBase
							});
						});
						writeStream.on("error", function(err){
							_emitComplete(socket, data.id, false);
							/*self.emit("error", {
								file: fileInfo,
								error: err,
								memo: "from within write stream"
							});*/
						});
						files[data.id].writeStream = writeStream;
					}
					catch (err){
						_emitComplete(socket, data.id, false);
						/*self.emit("error", {
							file: fileInfo,
							error: err,
							memo: "creating write stream"
						});*/
						return;
					}
				});
			}
		};
	};

	this.listen = function(socket) {
		socket.on("siofio_start", _uploadStart(socket));
		socket.on("siofio_progress", _uploadProgress(socket));
		socket.on("siofio_done", _uploadDone(socket));
	};
	
}
util.inherits(SioFio, EventEmitter);

// Export the object.
module.exports = SioFio;
