var __nextASWSequenceNumber = '/asworker#1',
	 __nextCSWSequenceNumber = '/csworker#1',	
	 __pendingASWChangelogs = [],
	 __pendingCSWChangelogs = [];


/* delete pending changelogs older than the specified sequence number */
function __clearObsoleteChangelogs(pendingChangelogs,sn)
{
	pendingChangelogs = 
		pendingChangelogs.filter(
			function(c)
			{
				return utils.sn2int(pc['sequence#']) > utils.sn2int(sn);
			});
}


/* set the value of __nextASWSequenceNumber to the given sequence number */
function __forceNextASWSequenceNumber(sn)
{
	__nextASWSequenceNumber = sn;
	__clearObsoleteChangelogs(__pendingASWChangelogs,sn)
}


/* set the value of __nextCSWSequenceNumber to the given sequence number */
function __forceNextCSWSequenceNumber(sn)
{
	__nextCSWSequenceNumber = sn;
	__clearObsoleteChangelogs(__pendingCSWChangelogs,sn)
}

//Todo: Shred this into smaller functions
/* apply a changelog (or postpone its application)
	
	0. check the changelog's sequence number to know if we should handle it 
		now or later... note that we will be receiving changelogs with both 
		asworker and csworker sequence numbers
	1. iterate through and handle the changelog's steps 
	2. apply next pending changelog, if any and if applicable */
function __handleChangelog(changelog,seqNum,hitchhiker)
{
	console.debug(' ++ ('+seqNum+') ',changelog);

	var isCSWChangelog 	 = seqNum.match(/csworker/)
		 nextSeqNum 	 	 = 
			 (isCSWChangelog ? __nextCSWSequenceNumber : __nextASWSequenceNumber),
		 pendingChangelogs = 
			 (isCSWChangelog ? __pendingCSWChangelogs : __pendingASWChangelogs);

	if( utils.sn2int(seqNum) > utils.sn2int(nextSeqNum) )
	{
		pendingChangelogs.push(
				{'changelog':changelog,
				 'sequence#':seqNum,
				 'hitchhiker':hitchhiker});
		pendingChangelogs.sort(
			function(a,b)
			{
				return utils.sn2int(a['sequence#']) - utils.sn2int(b['sequence#']);
			});
		return;
	}
	else if( utils.sn2int(seqNum) < utils.sn2int(nextSeqNum) )
	{
		WindowManagement.openDialog(
				__FATAL_ERROR, 
				'invalid changelog sequence# :: '+utils.sn2int(seqNum));
		return;
	}

	WindowManagement.setWindowTitle(true);
	changelog.forEach(
		function(step)
		{
			if( (step['op'] == 'RMNODE' ||
  				  step['op'] == 'RMEDGE' ||
  				  step['op'] == 'CHATTR') &&
			 	 __watching(step['id']) )
			{
				console.warn(
					'someone has altered the node you are currently editing...'+
					'to keep your changes, click OK... to discard them and see'+
					' what has changed, click CANCEL');
				__changed(step['id'],true);
			}
				
			if( step['op'] == 'MKEDGE' )
				;

			/* react to the removal of an edge */
			else if( step['op'] == 'RMEDGE' )	
			{
				__removeEdge(
						step['id1']+'--'+step['id2'],
						[step['id1'],step['id2']]);

				if( __selection != undefined )
					__select( utils.filter(__selection['items'],[edgeId]) );
			}

			/* react to the creation of a node */
			else if( step['op'] == 'MKNODE' )
			{
				var node  = utils.jsonp(step['node']),
					 icon = __createIcon(node,step['id']);

				if( '$segments' in node )
				{	
					var linkStyle = node['link-style']['value'],
						 segments  = node['$segments']['value'];
					icon.setAttr('__segments',utils.jsons(segments));
					for( var edgeId in segments )
						__createEdge(segments[edgeId], linkStyle, edgeId, step['id']);
				}
			}

			/* react to the removal of a node */	
			else if( step['op'] == 'RMNODE' )	
			{
				__icons[step['id']]['icon'].remove();
				__icons[step['id']]['edgesOut'].forEach(__removeEdge);
				__icons[step['id']]['edgesIn'].forEach(__removeEdge);
				delete __icons[step['id']];

				if( __selection != undefined )
					__select( utils.filter(__selection['items'],[step['id']]) );
			}

			/* react to attribute update
					CASE 1	: Icon/Link layout attribute (position, etc.)
						update the corresponding attribute of the corresponding icon 
						in __icons + actually effect the layout transformation (via
						__setIconTransform())

					CASE 2	: Link attribute ($segments / link-style)
						redraw existing edges and create any new ones

					CASE 3	: VisualObject attribute update
						update the corresponding attribute of the corresponding icon's
						corresponding vobject */
			else if( step['op'] == 'CHATTR' )
			{
				/* CASE 1 */
				if(utils.contains(['position','orientation','scale'],step['attr']))
				{
					var newVal = step['new_val'],
						 icon	  = __icons[step['id']]['icon'];
						 
					/* bugfix where newVal would get a string instead of the array of values 
					should search for reason why it has string in the first place - Vasco - 02-02-2017*/
					if( typeof newVal == 'string'){
						newVal = newVal.replace('[','');
						newVal = newVal.replace(']','');
						newVal = newVal.split(',');
					}
					/* endbugfix*/
					
					if( step['attr'] == 'position' )
					{
						var bbox = icon.getBBox();
						icon.setAttr('__x', __getAbsoluteCoordinate(newVal[0],bbox.width) );
						icon.setAttr('__y', __getAbsoluteCoordinate(newVal[1],bbox.height) );
					}
					else if( step['attr'] == 'orientation' )
						icon.setAttr('__r',newVal);

					else if( step['attr'] == 'scale' )
					{
						icon.setAttr('__sx',newVal[0]);
						icon.setAttr('__sy',newVal[1]);
					}
					__setIconTransform(step['id']);
				}

				/* CASE 2 */
				else if(utils.contains(['$segments','link-style','arrowTail','arrowHead'],step['attr']))
				{
					var icon		   = __icons[step['id']]['icon'],
						 segments,
						 linkStyle;

					if( step['attr'] == '$segments' )
					{
						segments  = step['new_val'];
						linkStyle = utils.jsonp(icon.getAttr('__linkStyle'));
						icon.setAttr('__segments',utils.jsons(segments));
					}
					else if (step['attr'] == 'link-style')
					{
						segments  = utils.jsonp(icon.getAttr('__segments'));
						linkStyle = step['new_val'];
						icon.setAttr('__linkStyle',utils.jsons(linkStyle));
					}
					else
					{
						segments  = utils.jsonp(icon.getAttr('__segments'));
						linkStyle = utils.jsonp(icon.getAttr('__linkStyle'));
					}
					for( var edgeId in segments )
						if( edgeId in __edges )
							__redrawEdge(edgeId,segments[edgeId],linkStyle);
						else
							/* react to the creation of an edge */
							__createEdge( 
									segments[edgeId], linkStyle, edgeId, step['id']);
				}

				/* CASE 3
					
					NOTE:: certain VisualObject parameters need some special care...
						a) position, orientation, scale: these can only be changed by
					  		the csworker layout constraint solver and link decorator 
							positioner... after each change, the transformation applied
						  	to the vobject is overwritten
						b) r/rx/ry: changing these causes relevant shapes to grow 
							around their center thereby changing their top-left corner
							... we translate the said shapes to compensate
						c) Polygons/Stars: see __editPolygon/Star(..)
						d) style: pass {...,attr:val,...} hash directly to attr(..)
						e) src: make sure URL is valid / make it valid 
						f) Text VisualObject: see __valignText(..) */
				else
				{
					var matches = step['attr'].match(/.*\/(.*)\/(.*)/),
						 vid		= matches[1],
						 vobj		= __icons[step['id']]['vobjects'][vid],
						 attr		= matches[2];

					if(utils.contains(['position','scale','orientation'],attr))
					{		
						var newVal = (utils.isArray(step['new_val']) ? 
									[__getVobjGeomAttrVal(step['new_val'][0]).latest,
									 __getVobjGeomAttrVal(step['new_val'][1]).latest] :
									parseFloat(
										__getVobjGeomAttrVal(step['new_val']).latest) );
						if( attr == 'position' )
						{
							vobj.node.setAttribute('__x',
									utils.buildVobjGeomAttrVal(
										vobj.node.getAttribute('__x'), newVal[0]) );
							vobj.node.setAttribute('__y',
									utils.buildVobjGeomAttrVal(
										vobj.node.getAttribute('__y'), newVal[1]) );
						}
						else if( attr == 'scale' )
						{
							var sx = vobj.node.getAttribute('__sx'),
								 sy = vobj.node.getAttribute('__sy');
							vobj.node.setAttribute('__sx',
									utils.buildVobjGeomAttrVal(
										sx, newVal[0]*__getVobjGeomAttrVal(sx).initial) );
							vobj.node.setAttribute('__sy',
									utils.buildVobjGeomAttrVal(
										sy, newVal[1]*__getVobjGeomAttrVal(sy).initial) );
						}
						else
						{
							var r = vobj.node.getAttribute('__r');
							vobj.node.setAttribute('__r',
									utils.buildVobjGeomAttrVal(
										r, newVal+__getVobjGeomAttrVal(r).initial) );
						}

						__setVobjectTransform(vobj);
					}

					if(utils.contains(['r','rx','ry'],attr))
					{
						var oldVal = vobj.attr(attr) || vobj.node.getAttribute('__'+attr);
							 offset = step['new_val'] - oldVal;
						vobj.translate(
								utils.contains(['r','rx'],attr) ? offset : 0,
								utils.contains(['r','ry'],attr) ? offset : 0);
					}

					if( step['id'].match(/\/PolygonIcon\//) &&
						 utils.contains(['r','sides'],attr) )
						__editPolygon(vobj,attr,step['new_val']);

					else if( step['id'].match(/\/StarIcon\//) &&
						 		utils.contains(['r','rays'],attr) )
						__editStar(vobj,attr,step['new_val']);

					try 			{var newVal = utils.jsonp(step['new_val']);}
					catch(err)	{var newVal = step['new_val'];}
					if( attr == 'style' )
						vobj.attr( newVal );
					else if( attr == 'src' )
						vobj.attr( 'src', __relativizeURL(newVal) );
					else
						vobj.attr( __ATTR_MAP[attr] || attr, newVal );

					if( vobj.type == 'text' )
						__valignText(vobj);
				}

				if( __isSelected(step['id']) )
					utils.doAfterUnlessRepeated(
						function(selection) {
							__select(selection);
						}, [__selection['items']], 5
					)
			}

			/* react to loading of an IconDefinition (CS metamodel)
				1. setup and show appropriate toolbar
				2. replace default toolbar button icons by icons described in
					the loaded metamodel */
			else if( step['op'] == 'LOADMM' )
			{
				var data = eval('('+step['mm']+')');
	 			GUIUtils.setupAndShowToolbar(
					step['name']+'.metamodel',
					data,
					__METAMODEL_TOOLBAR);
	
				for( var t in data.types )
				{
					if( t.match(/(.*)Link$/) )
						continue;
					else if( ! GUIUtils.$$(step['name']+'.metamodel/'+t) )
					{
						if( !t.match(/(.*)Icon$/) )
							console.error('Icon typenames must be "<AbstractType>Icon", the following does not conform :: '+t);
						else
							console.error('found icon definition for unknwon abstract type :: '+t);
						continue;
					}

					var im = 
						data.types[t].filter( 
							function(attr) 
							{
								return attr['name'] == '$contents';
							})[0]['default'];
					CompileUtils.compileAndDrawIconModel(
							im,
							Raphael(GUIUtils.$$(step['name']+'.metamodel/'+t)),
							{'size':__ICON_SIZE, 
							 'wrap':true});
					GUIUtils.$$(step['name']+'.metamodel/'+t).removeChild(
							GUIUtils.$$(step['name']+'.metamodel/'+t).lastChild );
				}
			}

			else if( step['op'] == 'LOADASMM' )
				__loadedToolbars[step['name']+'.metamodel'] = 
					eval('('+step['mm']+')');

			else if( step['op'] == 'DUMPMM' )
			{
				var csmmPath = step['name']+'.metamodel';
				GUIUtils.removeToolbar(csmmPath);
			}

			else if( step['op'] == 'RESETM' )	
			{
				var m = utils.jsonp(step['new_model']);
				GUIUtils.resetCanvas();

				for( var id in m.nodes )
				{
					var node  = m.nodes[id],
						 icon  = __createIcon(node,id);

					if( '$segments' in node )
					{
						var linkStyle = node['link-style']['value'],
							 segments  = node['$segments']['value'];
						icon.setAttr('__segments',utils.jsons(segments));
						for( var edgeId in segments )
							__createEdge( segments[edgeId], linkStyle, edgeId, id);
					}
				}
				WindowManagement.setWindowTitle(false);
			}

			else if( step['op'] == 'SYSOUT' )
			{
				if( step['text'].match(/^ERROR :: /) )
					console.error(step['text']);
				else if( step['text'].match(/^WARNING :: /) )
					console.warn(step['text']);
				else if( step['text'].match(/^CLIENT_BDAPI :: /) )
				{
					var op = utils.jsonp(step['text'].match(/^CLIENT_BDAPI :: (.*)/)[1]);
					this[op['func']](op['args']);
				}
				else
					console.log('MESSAGE :: '+step['text']);
			}
			///////
			else if (step['op'] == 'CHAT') 
			{
				Collaboration.updateChat(step['text']);
			}
		});


	if( isCSWChangelog )
		nextSeqNum = __nextCSWSequenceNumber = 
			utils.incrementSequenceNumber(nextSeqNum);
	else
		nextSeqNum = __nextASWSequenceNumber = 
			utils.incrementSequenceNumber(nextSeqNum);

	if( pendingChangelogs.length > 0 &&
		 nextSeqNum == pendingChangelogs[0]['sequence#'] )
	{
		var pc = pendingChangelogs.shift();
		__handleChangelog(
				pc['changelog'],
				pc['sequence#'],
				pc['hitchhiker']);
	}
}