var EXPORTED_SYMBOLS = ["YOONO_SERVER"];

Components.utils.import("resource://yoono/yoonoPrefs.js");

var yoono = {};
var log = {info:function() {},debug:function() {},warn:function(){},error:function (){},fatal:function(){}};

// Globals
const CI = Components.interfaces;
const CL = Components.classes;
const XMLHTTPREQUESTCOMPONENT = CL["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
const PREFSSERVICE = CL["@mozilla.org/preferences-service;1"].getService(CI.nsIPrefService);
const PREFS = PREFSSERVICE.getBranch("extensions.yoono.");
const OBS = CL['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);



// -> auto set commandQueueMgr
Components.utils.import("resource://yoono/yoonoCommandQueue.js"); 

function Connection() {
	this.wrappedJSObject=this;
	
	this.xmlHttpRequest = XMLHTTPREQUESTCOMPONENT.QueryInterface(CI.nsIXMLHttpRequest);
	this.timer = CL['@mozilla.org/timer;1'].createInstance(CI.nsITimer);
	this.request = null;
	
}

Connection.prototype.init = function(y) {
	
	yoono = y;
	log = y.log;
	
	commandQueueMgr.init(y);
	commandQueueMgr.addObserver(this);
	
}

Connection.prototype.uninstall = function(y) {
  commandQueueMgr.uninstall();
}

Connection.prototype.launch = function (command, arg) {
	if (('connect' != command) && YOONO_PREFS.get('nosynchro')) {
		log.debug("NoSync Pref is true: aborting command");
		return ;
	}
	
	// creer le script et empiler les commandes non transient
	if (command)
		this.addToNextCommandScript(command, arg);

}


Connection.prototype.addToNextCommandScript = function(command, arg) {
	if (('connect' != command) && YOONO_PREFS.get('nosynchro')) {
		//log.debug("NoSync Pref is true: command ignored");
		return ;
	}
	
	var request = this.createRequest(command, arg);
	if (!request.xml)
		return;
	
	if (request.transcient)
		commandQueueMgr.addTranscientRequest(request.xml);
	else
		commandQueueMgr.addTransactionalRequest(request.xml);
}


Connection.prototype.createRequest = function(command, arg) {
  var userCredential = yoono.main.getUserCredential();
	if (!userCredential) {
		log.debug("Command ignored because no account");
    return({xml:null});
  }

  if(userCredential.anonymous && ('connect' != command)) {
		log.debug("Non-connect command ignored because anonymous");
    return({xml:null});
  }

	var transientCommand = false;
	var mkcmd = {
		xmlObj : new XML(),
		onNode : function() {}
	};
	
	try {
		switch (command) {
		case 'connect' :
			if(userCredential.anonymous) arg = 'no-sync';
			log.backtrace("connect with arg="+arg);
			switch(arg) {
			case "first-sync" : mkcmd.xmlObj = <connect client-sync-mode="first-sync"/>; break;
			case "manual-sync" : mkcmd.xmlObj = <connect client-sync-mode="manual-sync"/>; break;
			case "auto-sync" : mkcmd.xmlObj = <connect client-sync-mode="auto-sync"/>; break;
			case "no-sync" : mkcmd.xmlObj = <connect client-sync-mode="no-sync"/>; break;
				default : mkcmd.xmlObj = <connect/>; break;
			}
			transientCommand = true;
			break;

		case 'save-all-links' :
			
			log.debug("ADD SAVE ALL");
			
			mkcmd.xmlObj = <save-all-links/>;
			mkcmd.onNode = function(node) {
				if (node.isFolder())
					return;
				
				this.xmlObj.appendChild(node.setAllAttributes(<link />));
			}
			yoono.bkm.getCurrentTree().traverseNode(mkcmd);
			break;

		case 'save-all-links-add' :
			
			log.debug('begin save-all-links-add');
			var notOnServer = arg;
			
			mkcmd.xmlObj = <save-all-links sync-id={YOONO_PREFS.get('syncid')} mode="ADD"/>;
			for each (var nodeList in notOnServer) {
				for each (var node in nodeList) {
					mkcmd.xmlObj.appendChild(node.setAllAttributes(<link />));
				}
			}

			log.debug('end save-all-links-add');
			break;

		case 'set-sync-id' :
			
			mkcmd.xmlObj = <save-all-links sync-id={YOONO_PREFS.get('syncid')} mode="ADD"/>;
			transientCommand = true;
			break;

		case 'add-link':
			
			if (!arg.isFolder())
				mkcmd.xmlObj = arg.setAllAttributes(<add-link/>);
			
			break;

		case 'add-private-folder':
			if (arg.isFolder())
				mkcmd.xmlObj = arg.setAllAttributes(<add-link private="client"/>);
			break;

		case 'remove-private-folder':
			if (arg.isFolder() )
				mkcmd.xmlObj = arg.setAllAttributes(<remove-link/>);
			break;

		case 'publish-folder':
			var node = arg[0];
			var publishedId = arg[1];
			mkcmd.xmlObj = <add-xlink ref={":" + publishedId} path={"/$pub/" +publishedId}/> ;
			var path = node.getPath().toServer;
			var tmp = <update-link/>;
			tmp.appendChild(<old-link path={path}/>);
			tmp.appendChild(<new-link path={path} id={publishedId}/>);
			mkcmd.xmlObj += tmp;
			break;

		case 'unpublish-folder':
			var node = arg[0];
			var publishedId = arg[1];
			mkcmd.xmlObj = <remove-xlink ref={":" + publishedId} path={"/$pub/" +publishedId}/> ;
			var tmp = <update-link/>;
			var path = node.getPath().toServer;
			tmp.appendChild(<old-link path={path} id={publishedId}/>);
			tmp.appendChild(<new-link path={path}/>);
			mkcmd.xmlObj += tmp;
			break;

		case 'remove-link':
			
			mkcmd.xmlObj = arg.setAllAttributes(<remove-link/>);
			
			break;

		case 'update-link':
			var oldNode = arg[0];
			var newNode = arg[1];
			if (oldNode.isAcceptable() && !newNode.isAcceptable()) {
				this.addToNextCommandScript('remove-link', oldNode);
				return;
			}
			if (!oldNode.isAcceptable() && newNode.isAcceptable()) {
				this.addToNextCommandScript('add-link', newNode);
				return;
			}
			if (!oldNode.isAcceptable() && !newNode.isAcceptable()) {
				return;
			}
			
			if (!oldNode.isFolder()) {
				mkcmd.xmlObj = <update-link/>;
				mkcmd.xmlObj.appendChild(oldNode.setAllAttributes(<old-link/>));
				mkcmd.xmlObj.appendChild(newNode.setAllAttributes(<new-link/>));
			}
			
			break;

		case 'load-all-links' :
			// The load-all-links command is always processed last, whatever its position in the request
			mkcmd.xmlObj = <load-all-links/>;
			break;

		case 'compute-recommended-links':
			var newbkmarg = 'url:' + arg.url + ',title:' + arg.title;
			mkcmd.xmlObj = <compute-recommended-links
				url={arg.url}
				client-newbkm={newbkmarg}
				size={1}
				/>;
			transientCommand = true;
			break;

		default:
			log.error ('no such command in yoonoService.createRequest : ' + command);
			break;
		}
		
	} catch(e) {
		log.exception(e);
	}
	
	return {
		xml : mkcmd.xmlObj,
		transcient : transientCommand,
		overrideSyncId : false
	};
}

// handle the response to the request containing the commands stored in runningScript and runningFile
// according to response, it might be necessary to run again the commands after they were updated
//
// (be warn, $this isn't $this! because this function is called witout his environnement)
Connection.prototype.onSendingRequest = function(req) {
	
	yoono.dialogs.setThrobberBusy(true);
	
}

Connection.prototype.onRequestError = function(req) {
	OBS.notifyObservers (this, "yoono-get-response", "error");
	
	yoono.dialogs.setThrobberFailed();
}

var safePreferences = {
  bookmarksLastModified: true,
  synchroaction: true,
  synchroask : true,
  syncid : true,
  userid : true
}
Connection.prototype.onRequestSuccess = function(req, result) {
	
	// Notify each request (primary for unit testing)
  try {
	OBS.notifyObservers (this, "yoono-get-response", result);
  } catch(e) {log.exception(e)}
  
  // Handle commands to update preferences
  yoono.main.getServerContextData(result);
	
	var newCommand = false;
	var replace = false;
	yoono.dialogs.setThrobberBusy(false);
	
	// creation d'un nouvel utilisateur ??? QUOI  ? vieux truc pourri inutile.
//	if (!YOONO_PREFS.get('userid')) {
//		YOONO_PREFS.set('userid', result.context['user-id'].toString(), PREFS.PREF_STRING);
//  }
	
	// If invalid Id, do nothing (handled by sidebar that)
	if (result['display-error'].@code == 'ERROR_INVALID_USER_ID') {
		return;
	}
	
	var requestSync = result['request-sync'].@code;
	
	if (result['display-error'].@code == 'ERROR_INTERNAL_ERROR')
		return log.error("Internal error");
	
	// Sync id was different on server
	if (requestSync == 'REQUEST_SYNC_INVALID_SYNC_ID') {
		yoono.main.addStat(['invalid_sync_id']);
		log.debug("request-sync sync-mode="+result['request-sync'].@['client-sync-mode']);
    var serverLinks = result['request-sync'].@['link-count'];
    var clientLinks = yoono.bkm.getNbBkms();
		log.debug("Nb of bkms on server : " + serverLinks);
		log.debug("Nb of bkms on client : " + clientLinks);
		// ask user
		if( result['request-sync'].@['client-sync-mode'] == "manual-sync" ) {
      // When user enables sync and no link on server, export !
			YOONO_PREFS.set('synchroaction', 'merge', PREFS.PREF_STRING);
      if(0 == serverLinks) {
        YOONO_PREFS.set('synchroaction', 'export', PREFS.PREF_STRING);
      } else {
        if (YOONO_PREFS.get('synchroask')) // don't ask for unittest (there is no way to set this pref to false, except manually!)
          yoono.dialogs.setDialog('synchroask');
      }
		} else if( result['request-sync'].@['client-sync-mode'] == "first-sync" ) {
			YOONO_PREFS.set('synchroaction', 'export', PREFS.PREF_STRING);
		} else if( result['request-sync'].@['client-sync-mode'] == "no-sync" ) {
			return;
		} else {
			YOONO_PREFS.set('synchroaction', 'import', PREFS.PREF_STRING);
      // If no link on server, sync has been disabled on another client : disable this client sync
      if(0 == serverLinks) {
			  YOONO_PREFS.set('nosynchro', true, PREFS.PREF_BOOL);
      } else {
        // Test difference between number of bkms on server and on client.
        // if difference > some threshold, ask user.
        var threshold = YOONO_PREFS.get('bkm.ask.threshold') || 10;
        if(Math.abs(serverLinks - clientLinks) > threshold && YOONO_PREFS.get('synchroask')) { // don't ask for unittest (there is no way to set this pref to false, except manually!)
          YOONO_PREFS.set('synchroaction', 'merge', PREFS.PREF_STRING);
          log.debug("Difference > threshold, asking what to do");
          yoono.dialogs.setDialog('synchroask');
        }
      }
		}

		log.debug("REQUEST_SYNC_INVALID_SYNC_ID: must perform '" + YOONO_PREFS.get('synchroaction') + "'");
		if (YOONO_PREFS.get('synchroaction') == 'merge' || YOONO_PREFS.get('synchroaction') == 'import') {
			// Re run the same commands, with the server syncid
			newCommand = yoono.server.createRequest('load-all-links');
			newCommand.overrideSyncId = result['request-sync'].@['server-sync-id'];
		}
		else if (YOONO_PREFS.get('synchroaction') == 'export') {
			// Overwrite running command with an save-all-links command
			
			newCommand = yoono.server.createRequest('save-all-links');
			
			yoono.dialogs.synchroinfos('export', yoono.bkm.bookmarksNumber() );
		}
		else if (YOONO_PREFS.get('nosynchro')) {
      // In case of user registration, wizard might be waiting for the end of the sync :
		  OBS.notifyObservers (this,"yoono-synchro-end",YOONO_PREFS.get('synchroaction'));
			YOONO_PREFS.resetSynchro();
			return;
		} else { // rajoute une securite au cas ou la valeur a ete modifiee
			YOONO_PREFS.resetSynchro();
			
			newCommand = yoono.server.createRequest('load-all-links');
		}
	} else if (requestSync == 'REQUEST_SYNC_NEW_USER') {
		// Overwrite running command with an save-all-links command
		newCommand = yoono.server.createRequest('save-all-links');
	} else {
    log.debug('Nothing to do : notify so that sidebar wizard knows it can proceed, but knows sync was not necessary');
		OBS.notifyObservers (this,"yoono-synchro-useless",YOONO_PREFS.get('synchroaction'));
  }

	// The load-all-links command is always processed last, whatever its position in the request
	var importlinks = result['import-links'];
	if (importlinks.@code == 'MSG_LINKS_LOADED') {
		
		// If some persistent commands are in the 'next' commands buffers, it means new modifications have been made by the user
		// The imported links must not be used then, in order not to undo these new modifications.
		// The load-all-links command is added to the 'next' commands buffers to be performed at the end of the request
		// that sends the new modifications
		if(commandQueueMgr.nextPersistentCommandIsEmpty()) {
			log.debug('Next command is empty, processing loadAllLinks');
			
			var resp = yoono.bkm.loadAllLinks(importlinks.elements('link'));
			
			if (resp.action == 'import') {
				yoono.dialogs.synchroinfos("import", resp.imported);
			} else if (resp.action == 'merge') {
				yoono.dialogs.synchroinfos("merge", resp.added);
			}
			// was loadAllLinks successful ?
			if( resp.clearCommands == true ) {
				// perform a set-sync-id
				newCommand = yoono.server.createRequest('set-sync-id');
			}
		} else {
			log.debug('Next command not empty, discarding loaded links and queueing load-all-links again');
			yoono.server.addToNextCommandScript('load-all-links');
		}
	}
	//log.debug(" new Command : "+(newCommand?newCommand.xml.toXMLString():"no")+" from "+req.map(function (r){return r.toXMLString();}).join(' \n')+" => "+result.toXMLString());
	return newCommand;
	
}




var YOONO_SERVER = new Connection();