/* ==============================================
	topicMap represents a topicmap.  It contains 
	all the topic and associations, and also a
	list of all scopes used in basenames, occurrences,
	and associations.

=================================================*/
function topicMap(id){
	var Top=new topic('tt-urtype')
	var name=new baseName('Ur-Type')
	Top.addBaseName(name)
	this.id=id||''
	this.associations=new Object() // Associative array keyed by association id
	this.topics=new Object() // Associative array keyed by topic id
	this.scopes=new scopeCatalog()

	// index for topics keyed by role-player id
	this.assocIndexByTopics={}

	// Salt with the universal type
	this.addTopic(Top)
}

// ====== Interface =========
topicMap.prototype.addTopic=_add_topic
topicMap.prototype.addAssoc=_add_assoc
topicMap.prototype.getTopics=_get_topics
topicMap.prototype.getTopicsFiltered=_get_topics_filtered
topicMap.prototype.addScope=_add_scope
topicMap.prototype.getTopicsFilteredAny=_get_topics_filtered_any
topicMap.prototype.getTopicById=_get_topic_by_id
topicMap.prototype.getTopicsByType=_get_topics_by_type
topicMap.prototype.getTopicTypesIndex = _get_topic_types_index // Dictionary whose keys are ids of topics that have instances
topicMap.prototype.getAssocIndex=_get_assoc_index

//topicMap.prototype.getTopicTypes=_get_topic_types
//topicMap.prototype.getTopicByScope=_get_topic_by_scope
topicMap.prototype.getAllScopes=_get_all_scopes
topicMap.prototype.getNameScopes=_get_name_scopes
topicMap.prototype.getAssocScopes=_get_assoc_scopes
topicMap.prototype.getAssociations=_get_associations
topicMap.prototype.getAssociationsFilteredOr=_get_associations_filtered_or
topicMap.prototype.getAssociationsFilteredAnd=_get_associations_filtered_and
topicMap.prototype.getAssocByType=get_assoc_by_type
topicMap.prototype.getAssocByID=get_assoc_by_id
//topicMap.prototype.getAssocByScope=get_assoc_by_scope
topicMap.prototype.erase=_erase
topicMap.prototype.deleteTopicById=_delete_topic_by_id

function _add_topic(Topic){
	var t=this.topics
	if (!Topic){return}
	if (!Topic.id){// Create random id if id is missing
		Topic.id=Math.random()
		Topic.id='GEN-'+Topic.id.toString().substring(2)
	}


	t[Topic.id]=Topic
}

/* =====================================================
	Add association to topic map and add its role-players
	to the map's index of associations by topic.
   ===================================================== */
function _add_assoc(assoc){
	var a=this.associations
	var m,mem,members
	var p,player,players
	var assoclist
	if (assoc){
		if (!assoc.id){// Create random id if id missing
			assoc.id=Math.random()
			assoc.id='GEN-'+assoc.id.toString().substring(2)
		}
		a[assoc.id]=assoc

		// Find players and add to index
		members=assoc.getMembers()
		for (m in members){
			mem=members[m]
			players=mem.getPlayers()
			for (p in players){
				player=players[p]
				assoclist=this.assocIndexByTopics[player.id]
				if (!assoclist){
					assoclist=new Array()
					assoclist.push(assoc.id)
					this.assocIndexByTopics[player.id]=assoclist
				}
				else {
					assoclist.push(assoc.id)
				}
			}
		}
	}
}

function _get_topics(){
	return this.topics
}

function _get_topics_filtered(filters){
	/* Get all topics, then for each, check 
	 to see if it has a baseName with this scope.
	 If there is no scope, just return the whole
	 list of topics without checking.
	*/	
	if (!filters || !filters.length || filters[0]=='st-unconstrained'){return this.getTopics()}
	
	var result = []
	var topics = this.getTopics()

	for (var i in topics){
		if (topics[i].getNamesFiltered(filters).length){
			result.push(topics[i])
		}
	}

	return result
}

/*
	Create an index keyed by topic id, telling whether a topic has any instances or not.
	The value returned by the key is true.  The index only contains ids for topics that
	have instances.
*/
function _get_topic_types_index(){
	var instanceIndex = {}
	var topics = this.getTopics()
	var topic_type
	var topic
	for (var t in topics){
		topic = this.getTopicById(t)
		topic_type = topic.instanceOf
		if (topic_type){
			instanceIndex[topic_type] = true
		}
	}
	return instanceIndex

}


/* =====================================================
	Add association to topic map and add its role-players
	to the map's index of associations by topic.
   ===================================================== */
function _add_assoc(assoc){
	var a=this.associations
	var m,mem,members
	var p,player,players
	var assoclist
	if (assoc){
		if (!assoc.id){// Create random id if id missing
			assoc.id=Math.random()
			assoc.id='GEN-'+assoc.id.toString().substring(2)
		}
		a[assoc.id]=assoc

		// Find players and add to index
		members=assoc.getMembers()
		for (m in members){
			mem=members[m]
			players=mem.getPlayers()
			for (p in players){
				player=players[p]
				assoclist=this.assocIndexByTopics[player.id]
				if (!assoclist){
					assoclist=new Array()
					assoclist[assoclist.length]=assoc.id
					this.assocIndexByTopics[player.id]=assoclist
				}
				else {
					assoclist.push(assoc.id)
				}
			}
		}
	}
}

function _get_assoc_index(){
	return this.assocIndexByTopics
}


function _add_scope(scope){
	var s=this.scopes
	if (scope){
		s[s.length]=scope
	}
}

function _get_topics_filtered_any(filters){this.counter = this
	/* Get all topics, then for each, check 
	 to see if it has a baseName with this scope.
	 If there is no scope, just return the whole
	 list of topics without checking.
	*/
	var i,j,result=new Array()
	var theTopic
	
	if (!filters||!filters.length){return this.getTopics()}
	if (filters[0]=='st-unconstrained'){
		return this.getTopics()
	}
	
	for (i in this.getTopics()){
		theTopic=this.topics[i]
		if (theTopic.getNamesFilteredAny(filters).length){
			result[result.length]=theTopic
		}
	}
	return result		
}

function _get_topic_by_id(id){
	var i,t
	if (!id){return false}
	
	//if(id == "t-Ashburnham"){
	//	alert("In _get_topic_by_id ... " + this.topics[id])
	//}
	
	if (this.topics[id]){
		return this.topics[id]
	}
	return false
}

function _get_topics_by_type(parentType){
	var i,t,result=new Array()
	if (!parentType){return result}
	for (i in this.topics){
		t=this.topics[i]
		if (t&&t.instanceOf==parentType){
			result.push(t)
		}
	}
	return result
}


/* ==================================================
	Get all scopes used in basenames in the entire 
	map.  The result is an array keyed by the id
	of the scopes's topic, whose value is the 
	first basenamestring of that topic.
   ==================================================*/
function _get_name_scopes(){
	var t,id,topicname
	var names,n
	var s,scopes,scopetopic
	var results=new Object()
	for (t in this.topics){
		// baseName scopes
		if (!this.topics[t]){return}
		names=this.topics[t].nameList
		for (n in names){
			scopes=names[n].getScopes()
			for (s in scopes){
				scopetopic=null
				topicname=''
				id=scopes[s].topic
				scopetopic=this.getTopicById(id)
				if (scopetopic){
					topicname=scopetopic.getNamesFiltered()[0].name
				}
				else {
					topicname=scope.topic
				}
				// Only add new scopes
				if (id&&!results[id]){
					results[id]=topicname
				}
			}
		}
	}
	// add unconstrained scope in case it isn't already
	// there
	results['st-unconstrained']='All Scopes'
	return results
}

/* ==================================================
	Get all scopes used in associations in the entire 
	map.  The result is an array keyed by the id
	of the scopes's topic, whose value is the 
	first basenamestring of that topic.
   ==================================================*/
function _get_assoc_scopes(){
	var t,id,topicname
	var names,n
	var a,assoc
	var s,scopes,scopetopic,Scope
	var results=new Object()
	for (a in this.associations){
		// association scopes
		if (!this.associations[a]){continue}
		assoc=this.associations[a]
		scopes=assoc.getScopes()
		for (s in scopes){
			Scope=scopes[s]
			if (!Scope) {continue}

			scopetopic=null
			topicname=''
			id=Scope.topic
			scopetopic=this.getTopicById(id)
			if (scopetopic){
				topicname=scopetopic.getNamesFiltered()[0].name
			}
			else {
				topicname=scope.topic
			}
			// Only add new scopes
			if (id&&!results[id]){
				results[id]=topicname
			}
		}
	}
	// add unconstrained scope in case it isn't already
	// there
	results['st-unconstrained']='All Scopes'
	return results
}

function _get_all_scopes(){
	// Returns all scopes in use by names, occurrences
	// and associations
	var a,n,o,s,t,id,names,scopes,assoc
	var results=new Array()
	for (t in this.topics){
		// baseName scopes
		names=this.topics[t].nameList
		for (n in names){
			scopes=names[n].getScopes()
			for (s in scopes){
				id=scopes[s].topic
				// Only add new scopes
				if (id&&!inArray(results,id)){
					results.push(id)
				}
			}
		}
		// Occurrence scopes
		occurs=this.topics[t].occurrences
		for (o in occurs){
			scopes=occurs[o].getScopes()
			for (s in scopes){
				id=scopes[s].topic
				if (id&&!inArray(results,id)){
					results[results.length]=id
				}
			}
		}
	}
	for (a in this.getAssociations()){
		assoc=this.associations[a]
		scopes=assoc.getScopes()
		for (s in scopes){
			id=scopes[s].topic
			if (id&&!inArray(results,id)){
				results[results.length]=id
			}
		}
	}
	return results
}


function _get_associations(){
	return this.associations
}

function _get_associations_filtered_or(filters){
	var i,j,result=new Array()
	var theAssoc
	
	if (!filters||!filters.length){return this.getAssociations()}
	if (filters[0]=='st-unconstrained'){
		return this.getAssociations()
	}
	
	for (i in this.getAssociations()){
		theAssoc=this.getAssociations()[i]
		if (theAssoc.hasScopeOr(filters)){
			result[result.length]=theAssoc
		}
	}
	return result		
}

function _get_associations_filtered_and(filters){
	var i,j,result=new Array()
	var theAssoc
	
	if (!filters||!filters.length){return this.getAssociations()}
	if (filters[0]=='st-unconstrained'){
		return this.getAssociations()
	}
	
	for (i in this.getAssociations()){
		theAssoc=this.getAssociations()[i]
		if (theAssoc.hasScopeAnd(filters)){
			result[result.length]=theAssoc
		}
	}
	return result		
}

function get_assoc_by_type(parentType){
	var i,a,result=new Array()
	if (!parentType){return result}
	for (i in this.associations){
		a=this.associations[i]
		if (!a){continue}
		
		if (a.instanceOf==parentType){
			result.push(a)
		}
	}
	return result
}

function get_assoc_by_id(id){
	var i,a,A
	if (!id){return null}
	return this.associations[id]
}


/* ==========================================
	Remove all components of the topic map,
	so a reference-counting garbage collector
	can scavange them.

	Remove each association
		Erase the association
		remove the association
	For each topic:
		remove each basename
		remove each occurrence
		remove the topic
   ===========================================*/
function _erase(){
	var a,t,aa=this.getAssociations()
	var  tt=this.getTopics()
	// Associations
	while (aa&&aa.length){
		a=aa[aa.length]
		a.erase()
		a=null
		aa.length-=1
	}
	this.associations=null
		
	// Topics
	while (tt&&tt.length){
		t=tt[tt.length]
		t.erase()
		t=null
		tt.length-=1
	}
	this.topics=null

	this.assocIndexByTopics=null
	this.scopes=null
}

/* ===========================================
	Delete a topic from the map.
	
	For each association in which it is a player:
		delete that player
		In the index of associations by topic, null
			this topic's entry.
	For each association in which it is the type
		of a member:
		make the member type an empty string
	For each occurrence of which it is the type:
		make the occurrence type an empty string.
	For each scope of which it is the type:
		delete that scope.
	Erase the topic.
	Remove the topic from the index of topics.
   =========================================== */
function _delete_topic_by_id(id){
	if (!id){return}	
	var a,assoc,assocs
	var m,mem,members
	var p,player,players
	var theTopic,t,topics,Top
	var o,occurs,Occur
	var s,scopes,Scope
	
	theTopic=this.getTopicById(id)
	assocs=this.getAssociations()
	
	if (assocs){// process associations
		for (a in assocs){
			assoc=assocs[a]
			if (!assoc){continue}
			if (assoc.instanceOf&&assoc.instanceOf==id){
				assoc.instanceOf=''
			}
			scopes=assoc.getScopes()
			if (scopes){
				for (s in scopes){
					Scope=scopes[s]
					if (Scope.topic==id){
						Scope.topic=''	
					}
				}
			}
			members=assoc.getMembers()
			if (!members){continue}
			for (m in members){
				mem=members[m]
				if (!mem){continue}
				players=mem.getPlayers()
				for (p in players){
					player=players[p]
					if (!player){continue}
					if (player.playerTopic==id){
						player.playerTopic==''	
					}
				}
				if (mem.roleSpec==id){
					mem.roleSpec=''	
				}
			}
		}
	}
	
	topics=this.getTopicsFiltered()
	for (t in topics){
		Top=topics[t]
		if (!Top){continue}
		
		occurs=Top.getOccursFiltered()
		for (o in occurs){
			Occur=occurs[o]
			if (Occur.instanceOf==id){
				Occur.instanceOf=''	
			}
		}
	}
	
	theTopic.erase()
	this.topics[id]=undefined
}
