#!/usr/bin/env node

/*
	This script can be used in place of "editor".
	
	webide [filepath]

	or pipe to it:
	echo hello | webide [filepath]

	
	Installation (optional, if npm didn't do it):
	---------------------------------------------
	* Make sure this file is executable: chmod +x webide
	* Add this folder to PATH
	* Or copy this file to /usr/bin/: sudo cp webide /usr/bin/
	* Or add the webide location to .profile or .bashrc :  export PATH=$PATH:/path/to/bin/webide
	

	Make webide the default editor:
	-------------------------------
	export EDITOR='webide'

	or export EDITOR='/path/to/webide/bin/webide')

	permanent: Add the export command (above) to .bashrc

*/

var module_path = require("path");
var module_net = require("net");
var module_fs = require("fs");
var module_log = require("../shared/log.js");
var UTIL = require("../client/UTIL.js");

var log = module_log.log;
module_log.overrideConsole();

var DEBUG = module_log.DEBUG;

var DEFAULT = require("../server/default_settings.js");
var STDIN_PORT = DEFAULT.stdin_channel_port;

var WEBIDE_LOCATION = module_path.resolve(__dirname, "../"); // Path to the webide folder (where package.json is located)
console.log("WEBIDE_LOCATION=" + WEBIDE_LOCATION);

var EDITOR_STARTED = false;

var EDITOR_START_PROCESS; // If we end up running start.js this will be the start.js process

//console.log("process.argv=" + process.argv);

function main() {

	var args = process.argv;
	args.shift(); // Remove the path to node
	args.shift(); // Remove the path to this script
	args = args.join(" ").trim();
	
	if(args.length > 0) {
		
		
		if(args == "-v" || args == "--version") {
			module_fs.readFile(module_path.resolve(__dirname, "../package.json"), function(err, data) {
				if(err) throw err;
				
				try {
					var json = JSON.parse(data);
				}
				catch(err) {
					throw new Error("Unable to parse package.json! " + err.message);
				}
				
				log("Semver version: " + json.version);
				
				module_fs.readFile(module_path.resolve(__dirname, "../version.inc"), function(err, data) {
					if(err) return;
						
					log("Mercurial commit id: " + data);
				});
				
				
			});
			return;
		}
		
		// Asume args is a file path. Resolve the path
		var workingDir = process.cwd();
		var filePath = module_path.resolve(workingDir, args);
	}

	var argsSent = false;
	
	var stdinData = '';
	
	var stdinChannel;
	
	console.log("Initiating stdin stream ...");
	var stdinStream = process.stdin;
	
	var STDIN_STREAM_READABLE = false;
	
	stdinStream.on('readable', stdinStreamReadable);
	stdinStream.on('end', stdinStreamEnd);
	
	stdinStream.setEncoding('utf8');
	stdinStream.resume();
	
	// Some versions of nodejs wont call stream readably until it gets something, but we are in a hurry!
	console.log("Press Enter to continue ...");
	
	function stdinStreamEnd() {
		console.log("stdin stream ended!");
		//if(stdinChannel) stdinChannel.end();
	}
	
	function stdinStreamReadable() {
		if(STDIN_STREAM_READABLE) return;
		
		STDIN_STREAM_READABLE = true;
		
		console.log("stdin stream readable!");
		/*
			readable will be called on program start
			and after pressing enter while the program is running
			
		*/
		var chunk = this.read();
		// Note: Reading data will remove it from the internal buffer and it will not be piped
		
		if (chunk === null) {
			var stdinStreamHasData = false;
			console.log("No data in stdin stream.");
			
			
		} else {
			var stdinStreamHasData = true;
			console.log("stdin stream has data!");
			
			stdinData += chunk;
			
		}
		
		/*
			Connect to the editor server's stdin channel
		*/
		console.log("stdinChannel=" + !!stdinChannel +
		" stdinChannel.destroyed=" + (stdinChannel && stdinChannel.destroyed) +
		" argsSent=" + argsSent +
		" stdinStreamHasData=" + stdinStreamHasData +
		" stdinChannel.connecting = " + (stdinChannel && stdinChannel.connecting) );
		
		if( (!stdinChannel || stdinChannel.destroyed) &&
		(!argsSent || stdinStreamHasData) &&
		!(stdinChannel && stdinChannel.connecting) ) connectToStdinChannel(stdinStreamHasData);
		
		
	}
	
	function connectToStdinChannel(pipeStdin, secondTry) {
		console.log("(re)connecting to editor server stdin channel. secondTry=" + secondTry + " ...");
		stdinChannel = module_net.createConnection({port: STDIN_PORT});
		stdinChannel.on("error", stdinChannelError);
		stdinChannel.on("connect", stdinChannelConnected);
		stdinChannel.on("close", stdinChannelClosed);
		stdinChannel.on("data", stdinChannelData);

		function stdinChannelError(err) {
			if(err.code == "ECONNREFUSED") {
				if(!secondTry) {
					console.log("Attempting to start the editor ...");
					startEditor(function editorStarted(err) {
						if(err) console.log("Failed to start editor: " + err.message);
						else {
							console.log("Editor started successfully");
							connectToStdinChannel(pipeStdin, true);
						}					
					});
				}
				else console.log("Already tried starting the editor!");
			}
			else {
console.error(err);
				
				console.log("Exiting because there was an error with the stdin channel (server probably closed)");
				process.exit(1);
				
			}
		}
		
		function stdinChannelConnected() {
			console.log("Connected to editor server stdin channel. pipeStdin=" + pipeStdin);
			
			// When connecting we Must always send the arguments even if they are empty
			var argsString = filePath || args;
			console.log("Sending argsString=" + argsString);
			stdinChannel.write(argsString + "\n");
			argsSent = true;
			
			if(stdinData.length > 0) stdinChannel.write(stdinData);
			else console.log("No stdinData to send");
			
			if(pipeStdin) {
				log("Piping stdin to editor ...");
				stdinStream.pipe(stdinChannel);
				// When piping, the socket will automatically end when the piped stream end!
			}
			else if(filePath == undefined) {
				console.log("Closing stdin channel because pipeStdin=" + pipeStdin + " and filePath=" + filePath);
				stdinChannel.end();
				// The script refuse to exist, force exit as long as editor wasn't started, and we are not waiting for a stdout event
				if(!EDITOR_STARTED && pipeStdin) process.exit(0);
			}
		}
		
		function stdinChannelClosed(hadError) {
			console.log("stdin channel closed. hadError=" + hadError);
		}
		
		function stdinChannelData(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
			*/
			if(typeof data == "object") data = data.toString();
			if(filePath && data == filePath) {
				console.log("Done editing " + filePath);
				
				if(EDITOR_START_PROCESS) {
					// Try to decouple so that the editor doesn't close when we close
					// 
					//EDITOR_START_PROCESS.disconnect();
					EDITOR_START_PROCESS.unref();
				}
				
				process.exit(0);
			}
			else {
				console.log("stdin channel stdout: " + data);
			}
		}
		
	}
	
}

function startEditor(callback) {
	
	// start.js will start both the server and the client

	var editorDebugLog = [];
	
	var pathDelimiter = UTIL.getPathDelimiter(WEBIDE_LOCATION);
	
	var editor = require('child_process').spawn("node", [
		'"' + WEBIDE_LOCATION + pathDelimiter + 'start.js"',
		"-local",
		"--loglevel=" + DEBUG
	] , {
		env: process.env,
		cwd: WEBIDE_LOCATION,
		shell: true // Prevents start.js from closing the server on Windows when we exit
	});
	
	EDITOR_START_PROCESS = editor;
	
	// Disconnect from the communications channel to allow start.js to exit
	//editor.disconnect();
	
	EDITOR_STARTED = true;

	editor.on("error", function editorError(err) {
		console.log("start.js error: " + err.message);
		if(callback) {
			callback( err );
			callback = null;
		}
	});
	
	editor.on("close", function editorClosed(code) {
		console.log("start.js closed. code=" + code);
		if(callback) {
			callback( new Error("start.js closed! code=" + code) );
			callback = null;
		}

		for(var i=0; i<editorDebugLog.length; i++) {
			log("start.js stderr:" + editorDebugLog[i], DEBUG);
		}
		console.log("Exiting because start.js closed.")
		process.exit(0); // Always exit (this script) when the editor exit
	});
	
	editor.stdout.on("data", function editorStdout(data) {
		//console.log("start.js stdout: " + data);
		editorLog(data);
	});
	
	editor.stderr.on("data", function editorStderr(data) {
		//console.log("start.js stderr: " + data);
		editorLog(data);
	});

	
	function editorLog(data) {

		if(typeof data == "object") data = data.toString();

		var reStdinChannel = /stdin channel listening on port (\d+)/;
		var match = data.match(reStdinChannel);
		if(match) {
			if(match[1] != STDIN_PORT) STDIN_PORT = match[1];
			
			console.log("Editor listening on stdin channel!");

			callback(null);
			callback = null;
			
			// Don't exit until the editor exit's
			//editor.ref();
		}

		//Waiting for editor to open stdin channel ...

		log(data, DEBUG);

		// Save log for debugging purpouse
		editorDebugLog.push(data);
		if(editorDebugLog.length > 30) editorDebugLog.shift();

	}
}


main();
