#!/bin/sh
':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@"

/*
	
	For editing files remotely. For example when ssh:ed into a server:
	sudo webider /path/to/file
	
	install:
	wget https://www.webtigerteam.com/editor/download/webider
	mv webider webide
	chomd +x webide
	sudo mv webide /usr/local/bin
	
*/

var DEFAULT_PORT = 8103;

var EOF = String.fromCharCode(3);

var DEBUG = false; // spit out debug info if true

var module_os = require("os");
var module_string_decoder = require('string_decoder');
var module_net = require("net");

var homeDir = module_os.homedir();

var jsonFileLocation = "/.webide/webider.json";
var settingsFileLocation1 = "/root" + jsonFileLocation;
var settingsFileLocation2 = homeDir + jsonFileLocation;

var module_fs = require("fs");

parseArguments(process.argv);

function findSettings(settings) {
	
	debug("findSettings: settings=" + JSON.stringify(settings));
	
	module_fs.readFile(settingsFileLocation1, function(err, data) {
		
		if(err) {
			debug("Unable to open settings from " + settingsFileLocation1 + " (" + err.code + ")");
			module_fs.readFile(settingsFileLocation2, function(err, data) {
				if(err) {
					debug("Unable to open settings from " + settingsFileLocation2 + " (" + err.code + ")");
					
					ask("Host name or IP of WebIDE instance", "localhost", function(err, answer) {
						if(err) throw err;
						
						settings.host = answer;
						
						ask("What tcp Port to connect to", DEFAULT_PORT, function(err, answer) {
							if(err) throw err;
							
							settings.port = answer;
							
							ask("Save settings? [yN]", "Y", function(err, answer) {
								if(err) throw err;
								
								if(answer.match(/y|yes/i)) saveSettings(settings);
								
								dance(settings);
							});
							
						});
					});
				}
				else if(err) throw err;
				else {
					debug("Successfully loaded settings from " + settingsFileLocation2);
					parseSettings(data, settings, settingsFileLocation2);
				}
			});
		}
		else if(err) throw err;
		else {
			debug("Successfully loaded settings from " + settingsFileLocation1);
			parseSettings(data, settings, settingsFileLocation1);
		}
	});
}

function saveSettings(settings) {
	debug("Saving settings ...");
	
	var settingsCopy = {};
	for (var prop in settings) {
		settingsCopy[prop] = settings[prop];
	}
	delete settingsCopy.filePath;
	
	save(settingsFileLocation1, function(err1) {
		if(err1) save(settingsFileLocation2, function(err2) {
			if(err2) console.log("Unable to save settings!\n" + err1.message + "\n" + err2.message);
		});
	});
	
	function save(path, saveCallback) {
		var pathDelimiter = "/";
		var folder = path.slice(0, path.lastIndexOf(pathDelimiter));
		// Make sure the folder exist
		module_fs.mkdir(folder, function(err) {
			if(err && err.code == "EEXIST") err = null;
			
			if(err) return saveCallback(err);
			
			var data = JSON.stringify(settingsCopy, null, 2) + "\n";
			
			module_fs.writeFile(path, data, function(err) {
				if(!err) debug("Successfully saved settings in " + path);
				
				saveCallback(err);
			});
		});
	}
}


function parseSettings(data, settings, fromFile) {
	try {
		var json = JSON.parse(data);
	}
	catch(err) {
		console.log("Unable to parse settings from " + fromFile + " data=" + data + " error: " + err.message);
		return;
	}
	
	for(var prop in json) {
		settings[prop] = json[prop];
	}
	
	if(fromFile) settings.loadedFrom = fromFile;
	
	gotSettings(settings, fromFile);
}

function gotSettings(settings, settingsFile) {
	if(typeof settings != "object") throw new Error("Expected a settings object! settings=" + settings);

	debug("Got settings: " + JSON.stringify(settings));
	
	if(!settings.host) return ask("Host name or IP of WebIDE instance", function(err, answer) {
		if(err) throw err;
		
		settings.host = answer;

		dance(settings);
	});
	
	dance(settings);
}

function ask(question, defaultAnswer, callback) {
	
	if(typeof defaultAnswer == "function" && callback == undefined) {
		callback = defaultAnswer;
		defaultAnswer = undefined;
	}
	
	var BACKSPACE = String.fromCharCode(127);
	
	var prompt = question + ": ";
	
	var answer = "";
	if(defaultAnswer) {
		answer = defaultAnswer;
	}
	
	var stdin = process.stdin;
	stdin.resume();
	stdin.setRawMode(true);
	stdin.resume();
	stdin.setEncoding('utf8');
	
	var stdout = process.stdout;
	
	stdin.on('data', stdinData);
	
	
	
	write();
	
	
	function stdinData(ch) {
		ch = ch.toString('utf8');
		
		switch (ch) {
			case "\n":
			case "\r":
			case "\u0004":
			// finished typing
			stdout.write('\n');
			stdin.setRawMode(false);
			stdin.pause();
			stdin.removeListener("data", stdinData);
			callback(null, answer);
			callback = null;
			break;
			
			case "\u0003":
			// Ctrl-C
			stdout.write("\n");
			process.exit(1);
			break;
			
			case BACKSPACE:
			answer = answer.slice(0, answer.length - 1);
			write();
			break;
			
			default:
			// More characters
			answer += ch;
			stdout.write(ch);
			break;
		}
	}
	
	function write() {
		
		stdout.clearLine();
		stdout.cursorTo(0);
		stdout.write(prompt + answer);
		// gotcha: can't have two stdout.write after each other. Only the first will be visible
	}
	
}

function parseArguments(args) {
	
	var host, port, username;
	
	// First item is path to nodejs
	args.shift();
	// Second item is path to the script
	var scriptPath = args.shift();
	
	// Note: Special flags must come before the path!
	
	debug("args=" + JSON.stringify(args));
	
	parse();
	
	var filePath = args.join(" ").trim();
	
	debug("filePath=" + filePath + "");
	
	if(filePath.length > 0 && filePath.charAt(0) != "/") {
		// Resolve path
		var module_path = require("path");
		var workingDir = process.cwd();
		var filePath = module_path.resolve(workingDir, filePath);
	}
	
	debug("Resolved filePath=" + filePath + "");
	
	var settings = {};

	if(host) settings.host = host;
	if(port) settings.port = port;
if(filePath) settings.filePath = filePath;
	if(username) settings.username = username;

	if(!host) {
		debug("Looking up host and port from settings file(s) because it was not specified in arguments ...");
		findSettings(settings);
	}
	else gotSettings(settings, true);
	
	function parse() {
		//debug("Parsing...");
		
		// First look for debug flag
		for (var i=0; i<args.length; i++) {
			if(args[i] == "-debug") {
				DEBUG = true;
				args.splice(i, 1);
			}
		}
		
		for (var i=0; i<args.length; i++) {
			//debug("args[" + i + "]=" + args[i] + "");
			if(args[i] == "-h" || args[i] == "-host") {
				host = args[i+1];
				if(!host) throw new Error("Expected a hostname or ip after " + args[i]);
				debug("Parsed " + args[i] + " " + args[i+1]);
				args.splice(i, 2);
				return parse(args);
			}
			else if(args[i] == "-p" || args[i] == "-port") {
				port = args[i+1];
				if(!port) throw new Error("Expected a port nr after " + args[i]);
				debug("Parsed " + args[i] + " " + args[i+1]);
				args.splice(i, 2);
				return parse(args);
			}
			else if(args[i] == "-u" || args[i] == "-user") {
				username = args[i+1];
				if(!username) throw new Error("Expected a user name nr after " + args[i]);
				debug("Parsed " + args[i] + " " + args[i+1]);
				args.splice(i, 2);
				return parse(args);
			}
			
		}
	}
	}


function dance(settings) {
	
	var connection;
	
	var connected = false;
	
	var StringDecoder = module_string_decoder.StringDecoder;
	var decoder;
	var strBuffer = "";
	var fileContent;
	var isPipe = false;
	
	if(!settings.host) throw new Error("No host in settings!");
	
	if(!settings.port) {
		debug("Using default port " + DEFAULT_PORT);
		settings.port = DEFAULT_PORT;
	}
	
	if(settings.filePath) {
		debug("Opening file: " + settings.filePath + " ...");
		
		// Streams are really complicated! Open large files with scp instead.
		// Assuming only small config files
		// todo: Make sure the file is not too big!? then say "use scp instead"?
		
		module_fs.readFile(settings.filePath, function(err, data) {
			if(err) {
				console.log(err.message);
				return;
			}
			fileContent = data;
			conn();
		});
		
	}
	else {
		debug("File path not specified! process.argv=" + JSON.stringify(process.argv) + " stdin will be streamed ...");
		
		conn();
	}
	
	function conn() {
		debug("Conneting to " + settings.host + " on port " + settings.port + " ...");
		
		if(connection) throw new Error("We already have a connection!");
		
		/*
			Will NodeJS keep the connection open for us, or do we have to send junk ?
		*/
		
		connection = module_net.createConnection({host: settings.host, port: settings.port});
		connection.on("error", connectionError);
		connection.on("connect", connectionConnected);
		connection.on("close", connectionClosed);
		connection.on("data", connectionData);
		
		
	}
	
	function connectionError(err) {
		console.log(  "Connection error: " + (err && (err.message || err) )  );
		
		if(err && err.code == "ECONNREFUSED") {
			if(settings.loadedFrom) console.log("Edit " + settings.loadedFrom + " or ");
			console.log("try connecting to another host using -host and -port flags");
		}
		
		process.exit(1);
	}
	
	function connectionConnected() {
		connected = true;
		debug("Connected to editor server!");
		
		// When connecting we Must always send our username, and name of file
		var username = settings.username || module_os.userInfo().username;
		debug("Sending username=" + username);
		connection.write(username + "\n");
		
		if(settings.filePath) {
			connection.write(settings.filePath + "\n");
			debug("Sending file content...");
			connection.write(fileContent + EOF);
			
		}
		else {
			// Send stdin
			isPipe = true;
			connection.write("STDIN\n");
			
			//if(stdinData.length > 0) connection.write(stdinData);
			//else console.log("No initial stdinData to send");
			
			// hmm, will we miss early data to stdin because we are late with the pipe !?
			
			debug("Streaming stdin to editor ...");
			var stdinStream = process.stdin;
			stdinStream.pipe(connection);
			// When piping, the socket will automatically end when the piped stream end!
			
		}
	}
	
	function connectionClosed(hadError) {
		debug("connection closed. hadError=" + hadError);
		
		if(isPipe) process.stdin.unref(); // So the script can exit
		
	}
	
	function connectionData(data) {
		/*
			Some programs like "crontab -e" assumes we are done when exiting ...
			Solution: Keep the program running, and don't exit until we get a notice that the file has been closed
		*/
		
		debug("Recived " + data.length + " bytes ... isPipe=" + isPipe);
		
		if(strBuffer == "") {
			// Create a new string decoder
			decoder = new StringDecoder('utf8');
		}
		
		strBuffer += decoder.write(data);
		
		if(isPipe) {
			console.log(strBuffer);
			strBuffer = "";
		}
		else if(strBuffer.charAt(strBuffer.length-1) == EOF) {
			strBuffer = strBuffer.slice(0, -1);
			
			var totalCharacters = strBuffer.length;
			
			debug("Recieved content (" + totalCharacters + " characters) for " + settings.filePath);
			
			module_fs.writeFile(settings.filePath, strBuffer, function(err) {
				if(err) throw err;
				console.log(totalCharacters + " characters written to " + settings.filePath);
			});
			
			// Reset the buffer, so we can get the file content again.
			strBuffer = "";
			
			// Connection will be kept open until the file is closed in the editor
			
		}
	}
}

function debug() {
	if(DEBUG) console.log.apply(null, arguments);
}

