/*
VERSION:	1.3
1.3		Change: 
1.2		Change:	Failure returns undefined instead of an error string,  because I typically test for failure by looking for undefined or falsey values.
1.1		Allow onProgress as 3rd parameter.  Each returned promise has a unique ID "uid" to help with debugging.

DESCRIPTION:
	Use this for both loading & saving files.
	This can handle multiple files being saved or opened at the same time.
	Saving automatically makes temporary backups of files and automatically recovers from errors.
	Use file.open() to load files instead of Flash's XML class.  This is because saving needs to know about files that are being opened.
	file.save() & file.open() each return a promise for that file operation.

USAGE:
	#include "functions/VOW/zincMultiSave.as"
	var file = zincMultiSave();
	// file.open()
	// file.save()
	// file.opening		array of promises, each representing a file being opened
	// file.saving		array of promises, each representing a file being saved
	
	
	
	var saveProcess = file.save( myFilePath, myStr );
	var saveProcess.then( saveDone, saveFailed );
	
	saveProcess.onProgress = function( percent, chunks ){
		trace( percent + "%" );
		// chunks.current
		// chunks.total
	}// onProgress()
	
	
	
	var open_process = file.open( myFilePath, checkData );
	open_process.then( loadSuccess, loadFail );
	
	function checkData( fileContents_str ){
		return true;	// if there's nothing wrong with the data
	}// checkData()
	
	function loadSuccess( fileContents_str ){
		// ...
	}// loadSuccess()
	
	
*/
var zincMultiSave = function(){
	// zincSave.as  must be in the same folder as  zincMultiSave.as
	#include "zincSave.as"
	
	var file = {};
	file.opening = {};
	file.saving = {};
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/*
		If verifyFunc is provided, it'll be passed the loaded data as a string.
		verifyFunc is expected to return true or false, depending on whether it accepts the data as valid.
	*/
	file.open = function( path, verifyFunc ){
		var file = this;
		var vow = VOW.make();
		var content_str = null;
		
		
		// FAIL conditions
		// if no path, fail
		if( path == undefined  ||  path == "" ){
			var err = {
				message: "No file-path was specified, so I don't know which file to load.",
				type: "fail"
			}// err {}
			mdm.Exception.DebugWindow.trace( "  "+ err.message );
			vow.doBreak( undefined );
			vow.promise.uid = Math.random();
			return vow.promise;
		}// if:  no path specified
		
		// if file doesn't exist, fail
		var fileExists = mdm.FileSystem.fileExists( path );
		if( fileExists === false ){
			var err = {
				message: getFileName(path) + " doesn't exist, so it cannot be loaded.",
				type: "fail",
				path: path
			}// err {}
			mdm.Exception.DebugWindow.trace( "  "+ err.message );
			vow.doBreak( undefined );
			vow.promise.uid = Math.random();
			return vow.promise;
		}// if:  file doesn't exist
		
		// if this file is saving, refuse to open it
		if( file.saving[path] ){
			var err = {
				message: "Cannot open " + getFileName(path) + " because it is still saving.",
				type: "fail",
				path: path
			}
			mdm.Exception.DebugWindow.trace( "  "+ err.message );
			vow.doBreak( undefined );
			vow.promise.uid = Math.random();
			return vow.promise;
		}// if:  file is saving
		
		// if already opening, refer to the pre-existing open process instead of starting a new one
		if( file.opening[path] )		return file.opening[path];
		
		
		
		// OPEN
		// leave a record of this open-process
		file.opening[path] = vow.promise;
		
		XML.prototype.ignoreWhite = true;
		var file_xml = new XML();
		file_xml.onData = function( content_str ){
			
			if(content_str == undefined){
				var err = {
					message: getFileName(path) + " could not be loaded for some reason.",
					type: "fail",
					path: path
				}// err {}
				mdm.Exception.DebugWindow.trace("  "+err.message);
				vow.doBreak( undefined );				
			}// if:  loading failed
			
			else
			
			{// if:  loading was successful
				// forget that this file is being loaded
				delete file.opening[path];
				
				if(verifyFunc)
				{// if:  the data needs to be checked
					var isValid = verifyFunc( content_str );
					if(isValid){
						// announce the loaded data
						vow.keep( content_str );
					}else{
						var err = {
							message: "There's something wrong with the data in " + getFileName(path) + ", so it cannot be used.",
							type: "fail",
							path: path
						}// err {}
						mdm.Exception.DebugWindow.trace("  "+err.message);
						vow.doBreak( undefined );
					}// if:  invalid data
				}// if:  the data needs to be checked
				else
				{// if:  the data doesn't need to be checked  (no validation function was specified)
					// announce the loaded data
					vow.keep( content_str );
				}// if:  the data doesn't need to be checked  (no validation function was specified)
			}// if:  loading was successful
			
		}// onData()  (file loaded)
		file_xml.load( path );
	
		
		
		function getFileName(filePath){
			var startAt = filePath.lastIndexOf("\\");
			return filePath.substr( startAt+1 );
		}// getFileName()
		
		
		vow.promise.uid = Math.random();
		return vow.promise;
	}// open()
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/*
		fail		= means something malfunctioned in an unexpected way
		abort		= means save() stopped itself on purpose
	*/
	file.save = function( path, data_str, onProgress ){
		// #include "functions/VOW/zincSave.as"
		var file = this;
		var vow = VOW.make();
		var waitForThese;
		
		mdm.Exception.DebugWindow.trace("zincMultiSave save()");
		mdm.Exception.DebugWindow.trace("  path: " + path);
		
		// ABORT conditions
		// if no path, fail
		if( path == undefined  ||  path == "" ){
			var err = {
				message: "No file-path was specified, so I don't know where to save this data.",
				type: "abort",
				data: data_str
			}// err {}
			mdm.Exception.DebugWindow.trace( "  "+ err.message );
			vow.doBreak( undefined );
			vow.promise.uid = Math.random();
			return vow.promise;
		}// if:  no path specified
		
		// if no data, then fail
		if( data_str === false ){
			var err = {
				message: "No data to save, so no files will be created or modified.",
				type: "abort",
				path: path
			}// err {}
			mdm.Exception.DebugWindow.trace( "  "+ err.message );
			vow.doBreak( undefined );
			vow.promise.uid = Math.random();
			return vow.promise;
		}// if:  data doesn't exist
		
		// if this file is being opened,  refuse to save the incomplete data
		if( file.opening[path] ){
			var err = {
				message: "This file is still being opened, so it cannot be saved yet.",
				type: "abort",
				path: path
			}// err {}
			mdm.Exception.DebugWindow.trace( "  "+ err.message );
			vow.doBreak( undefined );
			vow.promise.uid = Math.random();
			return vow.promise;
		}// if:  file is currently being opened
		
		// prevent additional saves during (brief) save-abort processes
		if( file.saving[path].hasBeenAborted === true){
			var err = {
				message: "This file is already about to be saved soon, so saving again is not necessary right now.",
				type: "abort",
				path: path
			}// err {}
			mdm.Exception.DebugWindow.trace( "  "+ err.message );
			vow.doBreak( undefined );
			vow.promise.uid = Math.random();
			return vow.promise;
		}// if:  the previous save is already being aborted by a pending save
		
		
		// WAIT conditions
		waitForThese = [];
		
		// if saving, abort the previous save, wait for the abort to finish, then save this file
		if( file.saving[path] ){
			mdm.Exception.DebugWindow.trace("  This file is already being saved.  Attempting to abort the previous save,  and then waiting for that abort to finish.");
			var abortProcess = file.saving[path].abort();		// additional calls to abort() are ignored and return nothing
			mdm.Exception.DebugWindow.trace("  abortProcess obj: " + abortProcess);
			file.saving[path].hasBeenAborted = true;		// prevent additional saves during this (brief) abort process
			if( abortProcess ){
				waitForThese.push( abortProcess );
			}
		}// if:  already saving
		
		mdm.Exception.DebugWindow.trace("  waiting for "+waitForThese.length+" abort operations, before saving." );
		VOW.every( waitForThese )
		.then( saveFile );
		
		
		// MAIN
		function saveFile(){
			mdm.Exception.DebugWindow.trace("zincMultiSave save() is calling saveFile()");
			var saveProcess = zincSave( path, data_str );
			saveProcess.then( saveSuccess, saveFailed );
			// bridge onProgress() to external interface
			saveProcess.onProgress = function(){
				onProgress.apply( saveProcess, arguments.slice() );
				
				vow.promise.onProgress.apply( saveProcess, arguments.slice() );
			}
			
			// manually handle errors
			mdm.Exception.enableHandler();
			_root.onMDMScriptException = zincError;
			
			// leave a record of this save-process,  which also replaces & removes the "hasBeenAborted" flag of any previous save-process
			mdm.Exception.DebugWindow.trace("  file.saving[path] now refers to an active save promise");
			file.saving[path] = saveProcess;
			file.saving[path].path = path;
		}// saveFile()
		
		
		function saveSuccess( result ){
			delete file.saving[path];
			
			// let Zinc announce errors and panic
			for(var isStillSaving in file.saving){	break;	}
			if( isStillSaving === undefined ){
				mdm.Exception.DebugWindow.trace("  saveSuccess() set Zinc to panic mode");
				mdm.Exception.disableHandler();
			}
			
			vow.keep( result );
		}// saveSuccess()
		
		
		function zincError(){
			mdm.Exception.DebugWindow.trace("zincError()");
			// Tell zinc you've taken care of this error
			mdm.Exception.resetHandler();
			
			// tell saveFailed() that this abort won't count as a failure of the overall save
			file.saving[path].isRetrying = true;
			
			// abort & try again
			mdm.Exception.DebugWindow.trace("  zincError is aborting "+getFileName(path) );
			file.saving[path].abort()
			.then( saveFile );
			
		}// zincError()
		
		
		function saveFailed( err ){
			// when zincError causes an abort, do not fail this save-promise, because it's going to re-try
			if( file.saving[path].isRetrying )		return;
			
			mdm.Exception.DebugWindow.trace( "saveFailed()  "+ err.message );
			
			// let Zinc announce errors and panic
			for(var isStillSaving in file.saving){	break;	}
			if( isStillSaving === undefined ){
				mdm.Exception.DebugWindow.trace("  saveFailed set Zinc to panic mode");
				mdm.Exception.disableHandler();
			}
			
			delete file.saving[path];
			
			vow.doBreak( undefined );
		}// saveFailed()
		
		
		function getFileName(filePath){
			var startAt = filePath.lastIndexOf("\\");
			return filePath.substr( startAt+1 );
		}// getFileName()
		
		
		vow.promise.uid = Math.random();
		return vow.promise;
	}// save()
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	
	
	return file;
}// zincMultiSave()