/*******************************************************************************
AToMPM - A Tool for Multi-Paradigm Modelling

Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
Modified by Conner Hansen (chansen@crimson.ua.edu)

This file is part of AToMPM.

AToMPM is free software: you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later 
version.

AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/

/**
 * Gets the metamodel at the current URI
 */
function __getMetamodel(uri)
{
	return uri.match(/(.*)\/.*\/.*\.instance/)[1];
}

/**
 * Compiles? the current icon metamodel into a metamodel
 * @param imm
 * @returns
 */
function __iconMetamodelToMetamodel(imm)
{
	var matches = imm.match(/(.*)\..*Icons(\.pattern){0,1}(\.metamodel)$/);
	return matches[1]+(matches[2] || '')+matches[3];
}

/**
 * Returns whether or not the input model is a button model
 * @param str
 * @returns
 */
function __isButtonModel(str)
{
	return str.match(/\.buttons.model$/);
}


/* returns true if the given uri describes a Link */
function __isConnectionType(uri)
{
	return uri.match(/Link\/[0-9]*\.instance$/) &&
		 	 ! uri.match(/(.*\.instance)--(.*\.instance)/);
}


/* returns true if the given Link (specified via uri) is a containment link */
function __isContainmentConnectionType(linkuri)
{
	var matches = linkuri.match(/(.*)\..*Icons(\.pattern){0,1}\/(.*)Link/),
		 asmm		= matches[1]+(matches[2] || '')+'.metamodel',
		 type		= matches[3];
	return __loadedToolbars[asmm].connectorTypes[type] == __CONTAINMENT_LINK;
}

/**
 * Returns whether or not this is a metamodel
 * @param str
 * @returns {Boolean}
 */
function __isAbstractSyntaxMetamodel(str)
{
	return str.match(/\.metamodel$/) && !__isIconMetamodel(str);
}

/**
 * Returns whether or not this is an icon metamodel or
 * a regular metamodel
 * @param str
 * @returns {Boolean}
 */
function __isIconMetamodel(str)
{
	return str.match(/\..*Icons\.metamodel$/) || 
			 str.match(/\..*Icons\.pattern\.metamodel$/);
}

/**
 * Returns whether or not this is a model
 * @param str
 * @returns
 */
function __isModel(str)
{
	return str.match(/\.model$/);
}

/*--------------------------- PARSING [META]MODELS ---------------------------*/
/* update the client's state given the results of a 'csworker GET current.state'
	query

	1. retrieve all missing asmm information (information which we normally get
		as a side-effect of loading an icon definition metamodel but which isn't
		stored in csworker state, and thus not returned by 'GET current.state')
	2. force the AS and CS next sequence numbers to the values from the given 
		state
	3. construct a changelog mimicking the model and metamodel loads associated
		with the given state (via state2chlog())
	4. apply it */
function __handleState(state,csn)
{
	function state2chlog(csmms,asmms,m)
	{
		var chlog = [];
		for( var csmm in csmms )
			chlog.push(	{'op':'LOADMM', 
							 'name':csmm, 
							 'mm':utils.jsons(csmms[csmm])} );
		for( var asmm in asmms )
			chlog.push(	{'op':'LOADASMM', 
							 'name':asmm, 
							 'mm':asmms[asmm]} );
		for( var id in m.nodes )
		{
			m.nodes[ m.nodes[id]['$type']+'/'+id+'.instance' ] = m.nodes[id];
			delete m.nodes[id];
		}	
		chlog.push( {'op':'RESETM', 
						 'new_model':utils.jsons(m)} );
		return chlog;	
	}
	
	var mms	 = utils.jsonp(state['mms']),
		 m		 = utils.jsonp(state['m']),
		 asmms = {},
		 nbmms = utils.keys(mms).length;
	__aswid =  state['asw'];
	
	utils.keys(mms).forEach(
		function(csmm)
		{
			var asmm = __iconMetamodelToMetamodel(csmm+'.metamodel');
			HttpUtils.httpReq(
				'GET',
				HttpUtils.url(asmm,__NO_WID),
				undefined,
				function(_statusCode,resp)
				{
					asmms[asmm.substring(0,asmm.length-'.metamodel'.length)] = resp;
					if( utils.keys(asmms).length < nbmms )
						return;
						
					__forceNextCSWSequenceNumber(csn);
					__forceNextASWSequenceNumber(state['asn']);
					__handleChangelog(
						state2chlog(mms,asmms,m),
						csn,
						undefined);
					});
		});	
}

/* create and return an edge 

	0. do nothing if provided edge ends are invalid (this happens during CS
		switches)
	1. draw the path, save it in __edges and style it according to the given 
		style (via CompileUtils.compileAndDrawEdge(..))
	2. remember the associated linktype's uri in the edge
	3. remember incoming and outgoing edges within __icons
	
	NOTE:: step 3 is redundant but allows for constant-time operations rather
  			 than O(|E|), specifically for implementing edge end following during
			 connected icon transformations */
function __createEdge(segments,style,edgeId,linkuri)
{
	var ids 	= __edgeId2ends(edgeId);
	if( !(ids[0] in __icons) || !(ids[1] in __icons) )
		return;

	var edge = CompileUtils.compileAndDrawEdge(
						 segments,
						 style,
						 ids[0],
						 ids[1],
						 linkuri);
	__icons[ids[0]]['edgesOut'].push(edgeId);
	__icons[ids[1]]['edgesIn'].push(edgeId);
    
    // sort and draw
    for (var id in __icons) {
        __icons[id]['ordernr'] = undefined;
    }
    function getOrderNr(id,visited) {
        var icon = __icons[id];
        if (visited.indexOf(icon) > 0) return
        if (icon['ordernr']) return;
        visited.push(icon);
        if (__isConnectionType(id)) {
            // I like my edges as I like my women: always on top
            icon['ordernr'] = 9999;
        } else if (icon['edgesIn'].length > 0) {
            for (var edgeId in icon['edgesIn']) {
                var associationid = __edges[icon['edgesIn'][edgeId]]['start']
                if (__isContainmentConnectionType(associationid)) {
                    getOrderNr(__edges[__icons[associationid]['edgesIn'][0]]['start'], visited);
                    icon['ordernr'] = __icons[__edges[__icons[associationid]['edgesIn'][0]]['start']]['ordernr'] + 1
                }
            }
            if (!icon['ordernr']) icon['ordernr'] = 0;
        } else {
            icon['ordernr'] = 0;
        }
    }
    for (var id in __icons) {
        getOrderNr(id, []);
    }
    
    Object.keys(__icons).concat().sort(function(a, b) {return __icons[a]['ordernr'] - __icons[b]['ordernr']}).forEach(function(el) {
        __icons[el]['icon'].toFront();
    });
    Object.keys(__edges).forEach(function(el) {
        // I like my edges as I like my women: always on top
        __edges[el]['icon'].toFront();
    });
    
	return edge;
}


/* create and return an icon 

	1. draw the icon and save it in __icons (via CompileUtils.compileAndDrawIconModel(..))
		... if the icon is a linktype, we also give it a __linkStyle attribute */
function __createIcon(node,id)
{
	var attrs = utils.mergeDicts(
						[{'__asuri'	:node['$asuri']['value'],
						  '__r' 		:node['orientation']['value'],
						  '__sx'		:node['scale']['value'][0],
			 			  '__sy'		:node['scale']['value'][1]},
						 ('link-style' in node ? 
							{'__linkStyle':utils.jsons(node['link-style']['value'])} :
						   {})] ),
	 	 icon  =  CompileUtils.compileAndDrawIconModel(
						 node['$contents']['value'],
 						 __canvas,
 						 {'id':id,
						  'attrs':attrs,
 						  'behaviours':true}),
		 bbox  = icon.getBBox(),
		 pos	 = node['position']['value'];
	icon.setAttr('__x',__getAbsoluteCoordinate(pos[0],bbox.width));
	icon.setAttr('__y',__getAbsoluteCoordinate(pos[1],bbox.height));
	icon.setAttr('vector-effect','inherit');
	__setIconTransform(id);
	return icon;
}


/* icon positions may be set to [x,a%,y,b%], meaning that the point at a% of the
  	icon's width and at b% of the icon's height should be located at x,y... for
	instance, if a and b == 50, the icon is centered on x,y... given a coordinate 
	of the form 'c,a%', this function returns an equivalent coordinate of the 
	form 'c' (returns unchanged input on coordinates of the form 'c') */
function __getAbsoluteCoordinate(c,full)
{
	if( (matches = String(c).match(/(.*),(.*)%/)) )
		return matches[1] - full*matches[2]/100.0;
	return c;
}


/* vobject geometric attribute (i.e., position, etc.) values contain the 
	attribute's initial and latest values... this is necessary to properly render
	and transform them... this function returns a parsed form of these values, 
	which look like: aa;bbbb or aa */
function __getVobjGeomAttrVal(val)
{
	if( (matches = String(val).match(/^(.*?)(;(.*)){0,1}$/)) )
		return {'initial' : parseFloat(matches[1]),
				  'latest':  matches[3]};
	else
		return {'initial' : val};
}


/* returns a list of fulltype legal connection types between specified nodes 
		1. extract info from ids
			a) csmm name
			b) asmm name
			c) astype
			d) asmm.legalconnections
		2. go through asmm1 and asmm2 adding all encoutered legal connections
			a) t1 -> t2 (when t1 and t2 are of the same mm)
			b) $* -> t2
			c) t1 -> t2
			d) $* -> $*
			if 'ctype' is specified (either CONTAINMENT_LINK or VISUAL_LINK), only
			add connections of the given ctype
		3. flatten and return resulting array of arrays 

	TBA/TBI
		add support for hyperedges (i.e., when uri1/uri2/?both? are links)...
		this might cause problems with the selection mechanism which is untested
		for anything else than edges connecting icons... will probably also need
	  	to make changes in csworker MKNODE and in worker urize-hack... copy-paste
		might also need to be updated to support more than 2 segments per Link */
function __legalConnections(uri1,uri2,ctype)
{
	/* helper function that takes an array of basic types (e.g., Below) and
		returns an array of fulltypes (e.g., /Formalisms/.../Below) */
	function t2ft(types,mm)
	{
		return types.map( 
					function(t) 
					{
						return mm+'/'+t;
					});
	}

	/* helper function that takes an array of basic types, and, if 'ctype' is
		defined, removes non-ctype types */
	function ctypes(types,connectorTypes)
	{
		return (ctype == undefined ?
					types :
					types.filter( function(t) {return connectorTypes[t]==ctype;}) );
	}

	var matches1		= uri1.match(/(.*)\/(.*)\/.*\.instance/),
		 csmm1			= matches1[1],
		 asmm1			= __iconMetamodelToMetamodel(matches1[1]+'.metamodel'),
		 asmatch1		= matches1[2].match(/(.*)Icon/),
		 astype1	 	= asmatch1 == null ? null : asmatch1[1],
		 lc1			= __loadedToolbars[asmm1].legalConnections,
		 ct1			= __loadedToolbars[asmm1].connectorTypes,
		 matches2		= uri2.match(/(.*)\/(.*)\/.*\.instance/),
		 csmm2			= matches2[2],
		 asmm2			= __iconMetamodelToMetamodel(matches2[1]+'.metamodel'),
		 asmatch2		= matches2[2].match(/(.*)Icon/),
		 astype2	 	= asmatch2 == null ? null : asmatch2[1],
		 lc2			= __loadedToolbars[asmm2].legalConnections,
		 ct2			= __loadedToolbars[asmm2].connectorTypes,
		 legalConnections = [];
		 
	if (astype1 == null || astype2 == null) {
		return [];
	}

 	if( asmm1 == asmm2 )
		// MM1=MM2: t1 -> t2
		// MM1=MM2: $* -> t2
		// MM1=MM2: __p$* -> t2
		[astype1,'$*','__p$*'].forEach(	
				function(t)
				{
					if( t in lc1  &&  astype2 in lc1[t] )
						legalConnections.push( 
							t2ft( ctypes(lc1[t][astype2],ct1), csmm1) );
				});
	else
		// MM2: $* -> t2
		// MM2: __p$* -> t2
		['$*','__p$*'].forEach(	
				function(t)
				{
					if( t in lc2  &&  astype2 in lc2[t] )
						legalConnections.push( 
							t2ft( ctypes(lc2[t][astype2],ct2), csmm2) );
				});
		
	// MM1: t1 -> $*
	// MM1: t1 -> __p$*
	['$*','__p$*'].forEach(	
			function(t)
			{
				if( astype1 in lc1  &&  t in lc1[astype1] )
					legalConnections.push( 
						t2ft( ctypes(lc1[astype1][t],ct1), csmm1) );
			});

	for( var tb in __loadedToolbars )
		if( __isIconMetamodel(tb)  										&& 
			 (asmm=__iconMetamodelToMetamodel(tb)) != asmm1			&&
			 asmm != asmm2	)													
			['$*','__p$*'].forEach(	
				function(t)
				{
					if( t in __loadedToolbars[asmm].legalConnections 	 &&
		  				 t in __loadedToolbars[asmm].legalConnections[t] )
						legalConnections.push( 
							t2ft(
								ctypes(
									__loadedToolbars[asmm].legalConnections[t][t],
									__loadedToolbars[asmm].connectorTypes),
								tb.match(/(.*)\.metamodel/)[1]) );
				});

	return utils.flatten(legalConnections);
}



/*----------------------------- CONTAINMENT... -------------------------------*/
/* returns the uris of the icons contained in the given container (specified via
  	uri)
	
	1. retrieve every icon that is explicitly contained (i.e., that has a 
		containment link from the given container or from any of its [recursive]
	  	contents)
		a. if container is a Link, return []
		b. foreach of the container's outgoing edges
			i.  if the edge's Link is not a containment Link, return []
			ii. otherwise, return all the ends of that Link's outgoing edges along
				 with the Link
		c. flatten the resulting array of arrays 
		d. recurse back to step 1a on every item returned by step 1c (i.e., on 
			all of the computed contents) and append results to those of step 1c

	2. retrieve icons that are implicity contained (i.e., icons of Links that 
		connect contained icons)... this achieved by iterating through every known
	  	Link and keeping aside those whose ends are contained

	3. return 'setified' concatenation of results from steps 1 and 2 */
function __getIconsInContainer(container)
{
	function getExplicitContents(container, explored)
	{
		if( __isConnectionType(container) )
			return [];
        
        if( explored.indexOf(container) > -1 ) {
            return []
        }
	
		var contents = 
			utils.flatten(__icons[container]['edgesOut'].map(
				function(edgeId)
				{
					var linkuri	= __edgeId2linkuri(edgeId);
					if( ! __isContainmentConnectionType(linkuri) )
						return [];
		
					return __icons[linkuri]['edgesOut'].map(
								function(_edgeId)
								{
									return __edges[_edgeId]['end'];	
								}).concat([linkuri]);
				}));
                
        explored.push(container)

        for (var ct_idx in contents) {
            var to_concat = utils.flatten(getExplicitContents(contents[ct_idx], explored));
            contents = contents.concat(to_concat);
        }
        return utils.toSet(contents);
	}

	var explicitContents = getExplicitContents(container, []),
		 implicitContents = [];

	for( var uri in __icons )
		if( __isConnectionType(uri) 					&&
			 __icons[uri]['edgesIn'].every(
 				function(edgeId)
				{
					return utils.contains(explicitContents,__edges[edgeId]['start']);
		  	   }) 										 	&&
			 __icons[uri]['edgesOut'].every(
 				function(edgeId)
 				{
 					return utils.contains(explicitContents,__edges[edgeId]['end']);
			  	}) )
			implicitContents.push(uri);

	return utils.toSet(explicitContents.concat(implicitContents));
}


/* returns true if the given container (specified via uri) has a containment 
 	Link to the given icon (also specified via uri)
 
	1. foreach of the icon's incoming edges
		a. if the edge's Link is not a containment Link, loop to 1.
		b. if the edge's Link has an incoming edge from container, break and
	  		return true
	2. return false */  	
function __isDirectlyContainedIn(icon,container)
{
	return container != undefined &&
			 ! __isConnectionType(container) &&
			 __icons[icon]['edgesIn'].some(
				function(edgeId)
				{
					var linkuri	= __edgeId2linkuri(edgeId);
					if( ! __isContainmentConnectionType(linkuri) )
						return false;
						
					return __icons[linkuri]['edgesIn'].some(
						function(_edgeId)
						{
							return __edges[_edgeId]['start'] == container;	
						});
				});
}