/*
 *   This file is based on TiddlyWiki Project (www.tiddlywiki.com)
 */

var Tiddly = {};
Tiddly.File = {};
Tiddly.File.IO = {

userdataExt   : '.userdata',
startSaveArea : '<div id="' + 'storeArea">', // Split up into two so that indexOf() of this source doesn't find it
endSaveArea   : '</d' + 'iv>',

saveAsJSON : function(data)
{
	var originalPath = document.location.toString();
	var localPath = this.getLocalPath(originalPath);
	var jsonfile  = localPath.substr(0,localPath.lastIndexOf("."))+ this.userdataExt
	try {
		save = this.saveFile(jsonfile, this.convertUnicodeToUTF8(Object.toJSON(data)));
	}catch (e){
		this.showException(e);
	}
	if(!save){
		alert('Save error: ' + jsonfile);
	}
},

loadAsJSON : function()
{
	var originalPath = document.location.toString();
	var localPath = this.getLocalPath(originalPath);
	var jsonfile  = localPath.substr(0,localPath.lastIndexOf("."))+ this.userdataExt

	return this.convertUTF8ToUnicode(this.loadFile(jsonfile))
},


// Save this tiddlywiki with the pending changes
saveChanges: function()
{
	// Get the URL of the document
	var originalPath = document.location.toString();
	// Check we were loaded from a file URL
	if(originalPath.substr(0,5) != "file:"){
		return;
	}
	var localPath = this.getLocalPath(originalPath);
	// Load the original file
	var original = this.loadFile(localPath);
	if(original == null){
		alert("can't save");
		return;
	}
	// Locate the storeArea div's
	var posOpeningDiv = original.indexOf(this.startSaveArea);
	var limitClosingDiv = original.indexOf("<!--POST-BODY-START--"+">");
	var posClosingDiv = original.lastIndexOf(this.endSaveArea,limitClosingDiv == -1 ? original.length : limitClosingDiv);
	if((posOpeningDiv == -1) || (posClosingDiv == -1)){
		alert(posOpeningDiv +':'+posClosingDiv);
		alert('invalid file format: ' + localPath);
		return;
	}
	var save;
	try {
		// Save new file
		var revised = original.substr(0,posOpeningDiv + this.startSaveArea.length)  +
					this.convertUnicodeToUTF8(this.getAsHtml())  +
					original.substr(posClosingDiv);
		save = this.saveFile(localPath,revised);
	}catch (e) {
		this.showException(e);
	}
	if(!save){
		alert(config.messages.mainFailed);
	}
},

getAsHtml : function() {
	html = document.getElementById('storeArea').innerHTML;
	return html;
},


getLocalPath : function(originalPath)
{
	// Remove any location or query part of the URL
	var argPos = originalPath.indexOf("?");
	if(argPos != -1)
		originalPath = originalPath.substr(0,argPos);
	var hashPos = originalPath.indexOf("#");
	if(hashPos != -1)
		originalPath = originalPath.substr(0,hashPos);
	// Convert file://localhost/ to file:///
	if(originalPath.indexOf("file://localhost/") == 0)
		originalPath = "file://" + originalPath.substr(16);
	// Convert to a native file format assuming
	// "file:///x:/path/path/path..." - pc local file --> "x:\path\path\path..."
	// "file://///server/share/path/path/path..." - FireFox pc network file --> "\\server\share\path\path\path..."
	// "file:///path/path/path..." - mac/unix local file --> "/path/path/path..."
	// "file://server/share/path/path/path..." - pc network file --> "\\server\share\path\path\path..."
	var localPath;
	if(originalPath.charAt(9) == ":") // pc local file
		localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file://///") == 0) // FireFox pc network file
		localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file:///") == 0) // mac/unix local file
		localPath = unescape(originalPath.substr(7));
	else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
		localPath = unescape(originalPath.substr(5));
	else // pc network file
		localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
	return localPath;
},

getBackupPath: function(localPath)
{
	var backSlash = true;
	var dirPathPos = localPath.lastIndexOf("\\");
	if(dirPathPos == -1)
		{
		dirPathPos = localPath.lastIndexOf("/");
		backSlash = false;
		}
	var backupFolder = config.options.txtBackupFolder;
	if(!backupFolder || backupFolder == "")
		backupFolder = ".";
	var backupPath = localPath.substr(0,dirPathPos) + (backSlash ? "\\" : "/") + backupFolder + localPath.substr(dirPathPos);
	backupPath = backupPath.substr(0,backupPath.lastIndexOf(".")) + "." + (new Date()).convertToYYYYMMDDHHMMSSMMM() + ".html";
	return backupPath;
},



// UTF-8 encoding rules:
// 0x0000 - 0x007F:	0xxxxxxx
// 0x0080 - 0x07FF:	110xxxxx 10xxxxxx
// 0x0800 - 0xFFFF:	1110xxxx 10xxxxxx 10xxxxxx

convertUTF8ToUnicode : function(u)
{
	if(window.netscape == undefined)
		return this.manualConvertUTF8ToUnicode(u);
	else
		return this.mozConvertUTF8ToUnicode(u);
},

manualConvertUTF8ToUnicode : function(utf)
{
	var uni = utf;
	var src = 0;
	var dst = 0;
	var b1, b2, b3;
	var c;
	while(src < utf.length)
		{
		b1 = utf.charCodeAt(src++);
		if(b1 < 0x80)
			dst++;
		else if(b1 < 0xE0)
			{
			b2 = utf.charCodeAt(src++);
			c = String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
			uni = uni.substring(0,dst++).concat(c,utf.substr(src));
			}
		else
			{
			b2 = utf.charCodeAt(src++);
			b3 = utf.charCodeAt(src++);
			c = String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
			uni = uni.substring(0,dst++).concat(c,utf.substr(src));
			}
	}
	return(uni);
},

mozConvertUTF8ToUnicode : function(u)
{
	try
		{
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
		converter.charset = "UTF-8";
		}
	catch(e)
		{
		return this.manualConvertUTF8ToUnicode(u);
		} // fallback
	var s = converter.ConvertToUnicode(u);
	var fin = converter.Finish();
	return (fin.length > 0) ? s+fin : s;
},

convertUnicodeToUTF8 : function(s)
{
	if(window.netscape == undefined)
		return this.manualConvertUnicodeToUTF8(s);
	else
		return this.mozConvertUnicodeToUTF8(s);
},

manualConvertUnicodeToUTF8 : function(s)
{
	var re = /[^\u0000-\u007F]/g ;
	return s.replace(re, function($0) {return("&#" + $0.charCodeAt(0).toString() + ";");})
},

mozConvertUnicodeToUTF8 : function(s)
{
	try
		{
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
		converter.charset = "UTF-8";
		}
	catch(e)
		{
		return this.manualConvertUnicodeToUTF8(s);
		} // fallback
	var u = converter.ConvertFromUnicode(s);
	var fin = converter.Finish();
	if(fin.length > 0)
		return u + fin;
	else
		return u;
},

saveFile : function(fileUrl, content)
{
	var r = null;
	if((r == null) || (r == false))
		r = this.mozillaSaveFile(fileUrl, content);
	if((r == null) || (r == false))
		r = this.ieSaveFile(fileUrl, content);
	if((r == null) || (r == false))
		r = this.javaSaveFile(fileUrl, content);
	return(r);
},

loadFile : function(fileUrl)
{
	var r = null;
	if((r == null) || (r == false))
		r = this.mozillaLoadFile(fileUrl);
	if((r == null) || (r == false))
		r = this.ieLoadFile(fileUrl);
	if((r == null) || (r == false))
		r = this.javaLoadFile(fileUrl);
	return(r);
},

// Returns null if it can't do it, false if there's an error, true if it saved OK
ieSaveFile : function (filePath, content)
{
	try
		{
		var fso = new ActiveXObject("Scripting.FileSystemObject");
		}
	catch(e)
		{
		//alert("Exception while attempting to save\n\n" + e.toString());
		return(null);
		}
	var file = fso.OpenTextFile(filePath,2,-1,0);
	file.Write(content);
	file.Close();
	return(true);
},

// Returns null if it can't do it, false if there's an error, or a string of the content if successful
ieLoadFile : function(filePath)
{
	try
		{
		var fso = new ActiveXObject("Scripting.FileSystemObject");
		var file = fso.OpenTextFile(filePath,1);
		var content = file.ReadAll();
		file.Close();
		}
	catch(e)
		{
		//alert("Exception while attempting to load\n\n" + e.toString());
		return(null);
		}
	return(content);
},

// Returns null if it can't do it, false if there's an error, true if it saved OK
mozillaSaveFile : function(filePath, content)
{
	if(window.Components)
		try
			{
			netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
			var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
			file.initWithPath(filePath);
			if (!file.exists())
				file.create(0, 0664);
			var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
			out.init(file, 0x20 | 0x02, 00004,null);
			out.write(content, content.length);
			out.flush();
			out.close();
			return(true);
			}
		catch(e)
			{
			//alert("Exception while attempting to save\n\n" + e);
			return(false);
			}
	return(null);
},

// Returns null if it can't do it, false if there's an error, or a string of the content if successful
mozillaLoadFile : function(filePath)
{
	if(window.Components)
		try
			{
			netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
			var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
			file.initWithPath(filePath);
			if (!file.exists())
				return(null);
			var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
			inputStream.init(file, 0x01, 00004, null);
			var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
			sInputStream.init(inputStream);
			return(sInputStream.read(sInputStream.available()));
			}
		catch(e)
			{
			//alert("Exception while attempting to load\n\n" + e);
			return(false);
			}
	return(null);
},

javaUrlToFilename : function(url)
{
	var f = "//localhost";
	if(url.indexOf(f) == 0)
		return url.substring(f.length);
	var i = url.indexOf(":");
	if(i > 0)
		return url.substring(i-1);
	return url;
},

javaSaveFile : function(filePath, content)
{
	try{
		if(document.applets["TiddlySaver"])
			return document.applets["TiddlySaver"].saveFile(this.javaUrlToFilename(filePath),"UTF-8",content);
	}catch(e){
		return null;
	}
	try{
		var s = new java.io.PrintStream(new java.io.FileOutputStream(this.javaUrlToFilename(filePath)));
		s.print(content);
		s.close();
	}catch(e){
		return null;
	}
	return true;

},

javaLoadFile : function(filePath)
{
	try {
		if(document.applets["TiddlySaver"])
			return String(document.applets["TiddlySaver"].loadFile(this.javaUrlToFilename(filePath),"UTF-8"));
	}catch(e){
		return null;
	}
	var content = [];
	try {
		var r = new java.io.BufferedReader(new java.io.FileReader(this.javaUrlToFilename(filePath)));
		var line;
		while ((line = r.readLine()) != null)
			content.push(new String(line));
		r.close();
	}catch(e){
		return null;
	}
	return content.join("\n");
},


// ---------------------------------------------------------------------------------
// Remote HTTP requests
// ---------------------------------------------------------------------------------

// Load a file over http
//   url - the source url
//   callback - function to call when there's a response
//   params - parameter object that gets passed to the callback for storing it's state
// Return value is the underlying XMLHttpRequest object, or 'null' if there was an error
// Callback function is called like this:
//   callback(status,params,responseText,xhr)
//     status - true if OK, false if error
//     params - the parameter object provided to loadRemoteFile()
//     responseText - the text of the file
//     xhr - the underlying XMLHttpRequest object
loadRemoteFile : function(url,callback,params)
{
	// Get an xhr object
	var x;
	try
		{
		x = new XMLHttpRequest(); // Modern
		}
	catch(e)
		{
		try
			{
			x = new ActiveXObject("Msxml2.XMLHTTP"); // IE 6
			}
		catch (e)
			{
			return null;
			}
		}
	// Install callback
	x.onreadystatechange = function()
		{
		if (x.readyState == 4)
			{
			if ((x.status == 0 || x.status == 200) && callback)
				{
				callback(true,params,x.responseText,url,x);
			}
			else
				callback(false,params,null,url,x);
			}
		}
	// Send request
	if(window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
		window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
	try
		{
		url = url + (url.indexOf("?") < 0 ? "?" : "&") + "nocache=" + Math.random();
		x.open("GET",url,true);
		if (x.overrideMimeType)
			x.overrideMimeType("text/html");
		x.send(null);
		}
	catch (e)
		{
		alert("Error in send " + e);
		return null;
		}
	return x;
},

// Returns a string containing the description of an exception, optionally prepended by a message
exceptionText : function(e, message)
{
	var s = e.description ? e.description : e.toString();
	return message ? "%0:\n%1".format([message, s]) : s;
},

// Displays an alert of an exception description with optional message
showException : function(e, message)
{
	alert(this.exceptionText(e, message));
}

}


