gShowingAllComments = false ;
// YUI : queue, overlay
Sync = function() {
	// this queue handles both animations and io requests
	this._q = null ;
	
	this._iPreventClick = false ; // oh really ?
}

Sync.prototype = {
	init : function (iComment) {
	this._q = new CY.AsyncQueue() ;
// pr2 this._q = new CY.Queue() ;
	},
	
	setPreventClickOn : function () {
		CY.log("setPreventClickOn !") ;
		if (gLayout.isInFrame())
			parent.f_interfaceFreeze()		
		this._iPreventClick = true ;
	},

	setPreventClickOff : function () {
		CY.log("setPreventClickOff !") ;
		if (gLayout.isInFrame())
			parent.f_interfaceUnfreeze()		
		this._iPreventClick = false ;
	},

	removeCommentRet : function(args) {
		var successfull = args['successfull'] ;
		
		var iComment = (successfull) ? args['failure']['iComment'] : args['success']['iComment'] ;
		
		if (successfull) {
			var filterData = args['returned']['filterData'] ;
			if (gLayout.isInFrame()) {
				parent.f_updateFilterData(filterData) ;
			}
			
			var y = gIComments.getTopPosition()[1] ;
			
			var comment = gDb.getComment(iComment.commentId) ;
			this._q.add(
		            function(){
		            	
		    	    	unpaintCommentScope(comment) ;
		    	    	gIComments.close(comment.id) ;
		    	    	gIComments.remove(comment.id) ;
		    	    	if (comment.reply_to_id != null)
		    	    		gIComments.refresh(comment.reply_to_id) ;
		    	    	
		    	    	gDb.del(comment) ;
		    	    	
		    	    	if (gLayout.isInFrame()) {
		    		    	if (gDb.comments.length == 0 && gDb.allComments.length != 0) {
		    		    		parent.f_enqueueMsg(gettext("no filtered comments left")) ;
		    					parent.resetFilter() ;  
		    		    	}
		    		    	else {
		    					// just counting here ...
		    					var filterRes = gDb.computeFilterResults() ;
		    					updateFilterResultsCount(filterRes['nbDiscussions'], filterRes['nbComments'], filterRes['nbReplies']) ;
		    		    	}
		    	    	}
		            }
			);
			
			this._animateTo(y) ;		
		}
		this._q.add({fn:CY.bind(this.setPreventClickOff, this)}) ;
		this.resume() ;
	},
	
	moderateCommentRet : function(args) {
		var successfull = args['successfull'] ;

		var iComment = (successfull) ? args['failure']['iComment'] : args['success']['iComment'] ;

		if (successfull) {
			var ret = args['returned'] ;
			var comment = ret['comment'] ;

			gDb.upd(comment) ;
			
			var shouldReset = gLayout.isInFrame() && !parent.f_isFrameFilterFieldsInit() ;
			if (shouldReset){
				parent.resetFilter() ;
				this._showSingleComment(comment) ;
			}
			else 
				iComment.changeModeration(comment) ;
		}

		this._q.add({fn:CY.bind(this.setPreventClickOff, this)}) ;
		this.resume() ;
	},
	
	saveCommentRet : function(args) {
		var successfull = args['successfull'] ;
		if (successfull) {
			var formId = args['success']['formId'] ;
			var ret = args['returned'] ;
		
			removeFormErrMsg(formId) ;
		
			if ('errors' in ret) { // validation error
				var errors = ret['errors'] ;
				for (var eltName in errors) {
					addFormErrMsg(formId, eltName, errors[eltName]) ;
				}
				this._animateToTop() ;
			}
			else {
				var isReply = function() {return (gNewReply != null) && (formId == gNewReply['ids']['formId']) ;} ;
				var isNewComment = function() {return (gICommentForm != null) && (formId == gICommentForm['formId']) ;} ;
				var isEdit = function() {return (gEdit != null) && (formId == gEdit['ids']['formId']) ;} ;
				
				// doing this here for the a priori moderation case
				if (isNewComment())
					this.hideICommentForm(cleanICommentForm()) ;
				else if (isEdit())
					this._hideEditForm() ;
				else if (isReply())
					this._hideNewReplyForm() ;
				
				if ("ask_for_notification" in ret) {  
					if (ret['ask_for_notification']) {
						// TODO ask for notification ...or use AUTO_CONTRIB ?
						parent.f_yesNoDialog(gettext("Do you want to be notified of all replies in all discussions you participated in?"), gettext("Reply notification"), 
							function() { // special case : no waiting for the return, no error check, nothing !
									var cfg = {
									method: "POST", 
									data: urlEncode({'fun':'ownNotify', 'key':sv_key, 'version_key':sv_version_key, 'email':ret['email'], 'active':false}) 
								} ; 
								CY.io(sv_client_url, cfg);
							}, this, null,
							function() { // special case : no waiting for the return, no error check, nothing !
									var cfg = {
									method: "POST", 
									data: urlEncode({'fun':'ownNotify', 'key':sv_key, 'version_key':sv_version_key, 'email':ret['email'], 'active':true}) 
								} ; 
								CY.io(sv_client_url, cfg);
							}, this, null) ;
					}
				}
					
				
				if ("comment" in ret) { // won't be when add with a priori moderation
					var comment = ret['comment'] ;
	
					gDb.upd(comment) ;
	
					var shouldReset = gLayout.isInFrame() && !parent.f_isFrameFilterFieldsInit() ;
					if (shouldReset)
						parent.resetFilter() ;
					else { // ASSUMING filter is in init state ! (because when not // TODO $$$$$$$$$$$ this isn't true anymore .... when passing filter arguments in url !!
							// in frame for now data can't be filtered)
						if (comment.reply_to_id == null) { // not a reply
							unpaintCommentScope(comment) ; // for the edit case
							paintCommentScope(comment) ;
						}
					}
	
					// UPDATE FILTER DATA // TODO move ????
					var filterData = ret['filterData'] ;
					if (gLayout.isInFrame()) {
						parent.f_updateFilterData(filterData) ;
						updateResetFilterResultsCount() ;				
					}

					if (isReply()) { // add reply case
						if (!shouldReset) {
							this._insertReply(comment) ;
						}
					}
					else { // edit (reply or comment) or add (comment) case
				    	this._showSingleComment(comment) ;
					}
				}
				else 
					this._animateToTop() ;
			}

		}
		else { // TODO ? ALL ret-FUNCTIONS ?
			this._q.add({id:"expl", fn:function () {CY.log('in example .........') ;}}) ;
			this._q.promote("expl") ;
		}

		this._q.add({fn:CY.bind(this.setPreventClickOff, this)}) ;
		this.resume() ;
	},
	
	example : function () {
		CY.log('in example .........') ;
	},

	moderateComment	: function(iComment, state) {
		var comment = gDb.getComment(iComment['commentId']) ;
        this._q.add(
	            {fn:CY.bind(this.setPreventClickOn, this)},
	            {autoContinue:false, fn:CY.bind(doExchange, null, "editComment", {'comment_key':comment.key, 'state':state}, null, this.moderateCommentRet, this, {'iComment':iComment}, gettext("could not save comment"))}
	            ).run();
	},
	
	_saveComment : function(serverFun, formId) {
        this._q.add(
	            {fn:CY.bind(this.setPreventClickOn, this)},
	            {autoContinue:false, fn:CY.bind(doExchange, null, serverFun, {}, formId, this.saveCommentRet, this, {'formId':formId}, gettext("could not save comment"))}
	            ).run();
	},
	
	editComment : function() {
		this._saveComment("editComment", gEdit['ids']['formId']) ;
	},
	
	saveComment : function(formId) {
		this._saveComment("addComment", formId) ;
	},
	
	removeComment : function(iComment) {
		checkForOpenedDialog(iComment, function() {
			if (gLayout.isInFrame()) {
				parent.f_yesNoDialog(gettext("Are you sure you want to delete this comment?"), gettext("Warning"), function() { this.animateToTop() ;}, this, null, function() {
					var comment = gDb.getComment(iComment.commentId) ;
			        this._q.add(
				            {fn:CY.bind(this.setPreventClickOn, this)},
				            {autoContinue:false, fn:CY.bind(doExchange, null, "removeComment", {'comment_key':comment.key}, null, this.removeCommentRet, this, {'iComment':iComment}, gettext("could not remove comment"))}
				            ).run();
				}, this, null) ;
		        
			}
//			else {
//				alert("TODO : can't yet delete comments when not embed in text_view_frame because can't 'dialog' confirmation") ;
//			}
		
		}, this, null) ;
	},
	
	resume : function(commentDbIds, mouseXY) {
		this._q.run() ;
	},
	
	resetAutoContinue : function(callbackId) {
		this._q.getCallback(callbackId).autoContinue = true;
	},
	
	hideICommentForm : function(funObj) {
//		this._q.add({fn:function() {persistICommentFormValues();}}) ;
		this._q.add({autoContinue:false, fn:CY.bind(gICommentForm['animationHide'].run, gICommentForm['animationHide'])}) ;
//		this._q.add({fn:function() {cleanICommentForm();}}) ;
		if (funObj)
			this._q.add(funObj) ;
	},
	
	/* ANIMATION DRIVEN INTERFACE CHANGES */
	
	showCommentForm : function(iComment) {
		checkForOpenedDialog(null, function() {
	        this._q.add({fn:CY.bind(this.setPreventClickOn, this)});
			this._q.add({fn:function() {
								if (iComment == null) {
									var selection = getSelectionInfo() ;
									updateICommentFormSelection(selection) ;
								}
								showICommentForm(iComment);
							}}) ;
			this._q.add({autoContinue:false, fn:CY.bind(gICommentForm['animationShow'].run, gICommentForm['animationShow'])},
					{fn:CY.bind(this.setPreventClickOff, this)}
					).run();
		}, this, null) ;
	},
	
	showEditForm : function(iComment) {
		checkForOpenedDialog(null, function() {
	        this._q.add({fn:CY.bind(this.setPreventClickOn, this)});

	        this._q.add({fn:function() {
	        				showEditForm(iComment) ;
	        			}});
			this._animateToTop() ;
			
	        this._q.add({fn:CY.bind(this.setPreventClickOff, this)});
	        this._q.run() ;
		}, this, null) ;
	},
	
	showReplyForm : function(iComment) {
		checkForOpenedDialog(null, function() {
	        this._q.add({fn:CY.bind(this.setPreventClickOn, this)});

	        this._q.add({fn:function() {
	        					instanciateNewReplyForm(iComment) ;
	        					}});
			this._animateToTop() ;
			
	        this._q.add({fn:CY.bind(this.setPreventClickOff, this)});
	        this._q.run() ;
		}, this, null) ;
	},
	
	cancelICommentForm : function() {
        this._q.add({fn:CY.bind(this.setPreventClickOn, this)});
        
//		this._q.add({fn:function() {cleanICommentForm();}}) ;
    	this.hideICommentForm() ;
    	
        this._q.add({fn:CY.bind(this.setPreventClickOff, this)});
        this._q.run() ;
	},
	cancelEdit : function() {
        this._q.add({fn:CY.bind(this.setPreventClickOn, this)});
        
    	this._q.add({fn:function() {cancelEditForm();}}) ;
    	this._animateToTop() ;
    	
        this._q.add({fn:CY.bind(this.setPreventClickOff, this)});
        this._q.run() ;
	},
	cancelReply : function() {
        this._q.add({fn:CY.bind(this.setPreventClickOn, this)});
        
    	this._q.add({fn:function() {cancelNewReplyForm();}}) ;
    	this._animateToTop() ;
    	
        this._q.add({fn:CY.bind(this.setPreventClickOff, this)});
        this._q.run() ;
	},
	changeScopeFormClick : function() {
        this._q.add({fn:CY.bind(this.setPreventClickOn, this)});
        
    	this._q.add({fn:function() {changeScopeFormClick();}}) ;
    	this._animateToTop() ;
    	
        this._q.add({fn:CY.bind(this.setPreventClickOff, this)});
        this._q.run() ;
	},
	// this is invoked during queue execution
	_hideNewReplyForm : function() {
		this._q.add({fn:function() {
					cleanNewReplyForm() ; 
					cancelNewReplyForm() ;}}) ;
	},
	// this is invoked during queue execution
	_hideEditForm : function() {
		this._q.add({fn:function() {
					cancelEditForm() ;}}) ;
	},
	// this is invoked during queue execution
	_insertReply : function(comment) {
		this._q.add({fn:function() {
					var parentComment = gDb.getComment(comment.reply_to_id) ;
					var parentThread = gDb.getThreads([parentComment]) ;
					var previousComment = parentThread[parentThread.length - 2] ; // - 2 because now that comment has been added comment is parentThread[parentThread.length - 1]

					var iComment = gIComments.insertAfter(previousComment, comment) ;
					// iComment CAN'T BE NULL ! (TODO check that there is a
					// check server side that parent exists when adding a reply
					// !)
					
					var parentPosition = gIComments.getPosition(comment.reply_to_id) ;
					iComment.setPosition(parentPosition) ;
					
					// check if activation is necessary (will always be ?)
					var comment_path = gDb.getPath(comment) ;
					var topComment = comment_path[comment_path.length - 1] ;
					if (gIComments.isTopActive(topComment.id))
						iComment.activate() ;

					iComment.show() ;}}) ;
    	this._animateToTop() ;
	},
	_showSingleComment : function(comment) {
		if (comment != null) {
			var path = gDb.getPath(comment) ;
			var topAncestorComment = path[path.length - 1] ;
			var topY = 0 ;
			if (comment['start_wrapper'] != -1) 
				topY = CY.get(".c-id-"+topAncestorComment.id).getY() ;
			else 
				topY = CY.get('document').get('scrollTop') ;
			
			this._showComments([topAncestorComment.id], topY, false) ;
			// optim when browsing comments with no reply			
			if (topAncestorComment.replies.length > 0)
				this._animateTo(topY) ;
		}
	},
	_showFocusSingleComment : function(topComment, focusComment, reply) {
		if (topComment != null) {
			var topY = 0 ;
			if (topComment['start_wrapper'] != -1) 
				topY = CY.get(".c-id-"+topComment.id).getY() ;
			else 
				topY = CY.get('document').get('scrollTop') ;
			
			this._showComments([topComment.id], topY, false) ;
			// optim when browsing comments with no reply			
			if (topComment.replies.length > 0 || reply)
				this._animateToAndFocus(topY, focusComment.id, reply) ;
		}
	},

	showSingleComment : function(comment) {
		this._q.add({fn:CY.bind(this.setPreventClickOn, this)}) ;
		this._showSingleComment(comment) ;
		this._q.add({fn:CY.bind(this.setPreventClickOff, this)}) ;
		this._q.run();
	},
	
	showFocusSingleComment : function(topComment, focusComment, reply) {
		this._q.add({fn:CY.bind(this.setPreventClickOn, this)}) ;
		this._showFocusSingleComment(topComment, focusComment, reply) ;
		this._q.add({fn:CY.bind(this.setPreventClickOff, this)}) ;
		this._q.run();
	},
	
	browse : function(order, whereto) { 
		var comment = gIComments.browse(order, whereto) ;
		if (comment != null) 
			this.showSingleComment(comment) ;
	},
	
	_showComments : function(commentDbIds, topY, atDocumentTop) {
		this._q.add(
			{fn:function() {
				gShowingAllComments = atDocumentTop ;			
				gIComments.hide() ; 
				var cs = CY.Array.map(commentDbIds, function(id) { return gDb.getComment(id) ; }) ;
				var comments = gDb.getThreads(cs) ;
				gIComments.fetch(comments) ;
	
				if (commentDbIds.length > 0) {
					if (atDocumentTop) {
						CY.get('document').set('scrollTop', 0) ; 
					}
					else {
						gIComments.activate(commentDbIds[0]) ;
						var scopeStart = CY.get(".c-id-"+commentDbIds[0]) ;
						if (scopeStart && !scopeStart.inViewportRegion()) // scopeStart could be null when comment has no scope
							scopeStart.scrollIntoView(true) ;
					}
				}
				
				gIComments.setPosition([gConf['iCommentLeftPadding'], topY]) ;
	
				gIComments.show() ;
			}}) ;
	},
	
	_animateTo : function(topY) {
		this._q.add({fn:function() {
						gIComments.setAnimationToPositions(topY) ;
						}},
					{id:"animationRun", autoContinue:false, fn:CY.bind(gIComments.runAnimations, gIComments)}
	            ) ;
	},
	
	_animateToAndFocus : function(topY, focusCommentId, reply) {
		this._q.add({fn:function() {
						gIComments.setAnimationToPositionsAndFocus(topY, focusCommentId, reply) ;
						}},
					{id:"animationRun", autoContinue:false, fn:CY.bind(gIComments.runAnimations, gIComments)}
	            ) ;
	},
	
	_animateToTop : function() {
		var topPos = gIComments.getTopPosition() ;
		if (topPos != null)
			this._animateTo(topPos[1]) ;
	},
	
	animateToTop : function() {
		this._q.add({fn:CY.bind(this.setPreventClickOn, this)}) ;
		this._animateToTop() ;
		this._q.add({fn:CY.bind(this.setPreventClickOff, this)}) ;
		this._q.run();
	},
	
	showAllComments : function() {
		checkForOpenedDialog(null, function() {
			gShowingAllComments = true ;
			var allTopComments = CY.Array.map(gDb.comments, function(c){return c.id;}) ;
			this.showComments(allTopComments, [0,0], true) ;
		}, this, null) ;
	},

	showScopeRemovedComments : function() {
		checkForOpenedDialog(null, function() {
			gShowingAllComments = true ;
			var scopeRemovedComments = CY.Array.filter(gDb.comments, function(comment) { return (comment.start_wrapper == -1) ; }) ;
			var scopeRemovedCommentIds = CY.Array.map(scopeRemovedComments, function(c){return c.id;}) ;
			this.showComments(scopeRemovedCommentIds, [0,0], true) ;
			
		}, this, null) ;
	},

	showComments : function(commentDbIds, mouseXY, atDocumentTop) {
		checkForOpenedDialog(null, function() {
			this._q.add({fn:CY.bind(this.setPreventClickOn, this)}) ;
			this._showComments(commentDbIds, mouseXY[1], atDocumentTop) ;
			this._animateTo(mouseXY[1]) ;
			this._q.add({fn:CY.bind(this.setPreventClickOff, this)}) ;
			this._q.run();
		}, this, null) ;
	},

	openComment : function(iComment) { 
		this._q.add({fn:CY.bind(this.setPreventClickOn, this)}) ;

		var y = gIComments.getTopPosition()[1] ;
		this._q.add({fn:function() {
			gIComments.open(iComment.commentId) ;
    		gIComments.refresh(iComment.commentId) ;
		}}) ;
		this._animateTo(y) ;
		
		this._q.add({fn:CY.bind(this.setPreventClickOff, this)}) ;
		this._q.run();
	},
	
	closeComment : function(iComment) { 
		checkForOpenedDialog(iComment, function() {
			this._q.add({fn:CY.bind(this.setPreventClickOn, this)}) ;
	
			var y = gIComments.getTopPosition()[1] ;
			this._q.add({fn:function() {
				var comment = gDb.getComment(iComment.commentId) ;
				gIComments.close(iComment.commentId) ;
		    	if (comment.reply_to_id != null)
		    		gIComments.refresh(comment.reply_to_id) ;
			}}) ;
			this._animateTo(y) ;
	
			this._q.add({fn:CY.bind(this.setPreventClickOff, this)}) ;
			this._q.run() ;
		}, this, null) ;
	},
	
	activate : function(iComment) {
		gIComments.activate(iComment.commentId) ;
	}
}
			
readyForAction = function () {
	return !gSync._iPreventClick ;
};
