Source: edit.js

/**
 * Purpose: Noctua editing operations ove a bbop-graph base.
 *
 * The base pieces are just subclasses of their analogs in bbop-graph.
 *
 * Now, a discussion if the structure an terminology of the evidence
 * model.
 *
 * Definitions:
 *
 * - "model": the graph model as a whole
 * - "seed": an evidence instance that is referenced ; a seed may only belong to a single clique
 * - "sub_clique": the evidence subgraph pattern built off of a seed
 * - "clique" the complete evidence subgraph, obtained by walking all edges from any node in it (should be same no matter what node)
 * - "shared_struct": (currently) nodes of the clique that may be shared between different sub_cliques; for now, just pmid nodes
 *
 * Rules:
 *
 * A clique may only be removed from the graph, with its
 * constitutent sub_cliques contained as referenced subgraphs,
 * when:
 * - all constituent sub_cliques have the "correct" structure
 * - all clique nodes are in at least one sub_clique
 * - sub_cliques only share structure in shared_struct nodes
 *
 * @see module:bbop-graph
 * @module bbop-graph-noctua
 */

var us = require('underscore');
var each = us.each;
var keys = us.keys;
var bbop = require('bbop-core');
var bbop_model = require('bbop-graph');
var class_expression = require('class-expression');

///
/// Debug helpers.
///

// Change these as necessary.
var debug_map = {
    // 'gomodel:5798651000000002/5798651000000003': 'set of upper jaw teeth',
    // 'gomodel:5798651000000002/5798651000000004': 'sharp',
    // 'gomodel:5798651000000002/5798651000000005': 'mouth',
    // 'gomodel:5798651000000002/5798651000000006': 'pointed',
    // 'gomodel:5798651000000002/5798651000000008': 'tongue',
    // 'gomodel:5798651000000002/5798651000000009': 'lip',
    // 'gomodel:5798651000000002/5798651000000010': 'tusk',
    // 'gomodel:5798651000000002/5798651000000033': 'taste bud'
};
var dd = function(id){
    var ret = id;
    if( debug_map[id] ){
	ret = debug_map[id];
    }
    return ret;
};

///
/// Annotations.
///

/**
 * Edit annotations.
 * Everything can take annotations.
 *
 * This structure of the raw key-value set has been updated in the
 * wire protocol. It now looks like:
 *
 * : {"key": "contributor", "value": "GOC:kltm" }
 *
 * or:
 *
 * : {"key": "contributor", "value": "GOC:kltm", "value-type":"foo"}
 *
 * @constructor
 * @param {Object} [kv_set] - optional a set of keys and values; a simple object
 * @returns {this} new instance
 */
function annotation(kv_set){
    this._id = bbop.uuid();

    this._properties = {};

    if( kv_set && bbop.what_is(kv_set) === 'object' ){

	// Attempt to convert
	if( kv_set['key'] && kv_set['value'] ){
	    // var key = kv_set['key'];
	    // var val = kv_set['value'];
	    // var adj_set = {};
	    // adj_set[key] = val;
	    // Silently pass in value-type if there.
	    this._properties = bbop.clone(kv_set);
	}else{
	    // TODO: Replace this at some point with the logger.
	    console.log('bad annotation k/v set: ', kv_set);
	}
    }
}

/**
 * The unique id of this annotation.
 *
 * @returns {String} string
 */
annotation.prototype.id = function(){
    return this._id;
};

/**
 * Add/modify a property by key and value (and maybe value_type).
 *
 * @param {String} key - string
 * @param {String} [value] - string
 * @param {String} [value_type] - string
 * @returns {String|null} returns property is key
 */
annotation.prototype.annotation = function(key, value, value_type){

    var anchor = this;
    var ret = null;

    // Set if the key and value are there.
    if( key ){
	if( typeof(value) !== 'undefined' ){
	    anchor._properties['key'] = key;
	    anchor._properties['value'] = value;

	    // Add or get rid of value type depending.
	    if( typeof(value_type) === 'undefined' ){
		delete anchor._properties['value-type'];
	    }else{
		anchor._properties['value-type'] = value_type;
	    }
	}
    }
    ret = anchor._properties;

    return ret;
};

/**
 * Get/set annotation's key.
 *
 * @param {String} [key] - string
 * @returns {String|null} returns string of annotation
 */
annotation.prototype.key = function(key){
    var anchor = this;
    if( key ){ anchor._properties['key'] = key; }
    return anchor._properties['key'];
};

/**
 * Get/set annotation's value.
 *
 * @param {String} [value] - string
 * @returns {String|null} returns string of annotation
 */
annotation.prototype.value = function(value){
    var anchor = this;
    if( value ){ anchor._properties['value'] = value; }
    return anchor._properties['value'];
};

/**
 * Get/set annotation's value-type.
 *
 * @param {String} [value_type] - string
 * @returns {String|null} returns string of annotation
 */
annotation.prototype.value_type = function(value_type){
    var anchor = this;
    if( value_type ){ anchor._properties['value-type'] = value_type; }
    return anchor._properties['value-type'];
};

/**
 * Delete a property by key.
 *
 * @param {String} key - string
 * @returns {Boolean} true if not empty
 */
annotation.prototype.delete = function(){

    var anchor = this;
    var ret = false;

    if( ! us.isEmpty(anchor._properties) ){
	anchor._properties = {}; // nuke
	ret = true;
    }

    return ret;
};

/**
 * Clone an annotation.
 *
 * @returns {annotation} a fresh annotation for no shared structure
 */
annotation.prototype.clone = function(){
    var anchor = this;

    // Copy most of the data structure.
    var a = {};
    if( anchor.key() ){ a['key'] = anchor.key(); }
    if( anchor.value() ){ a['value'] = anchor.value(); }
    if( anchor.value_type() ){ a['value-type'] = anchor.value_type(); }

    var new_ann = new annotation(a);

    // Copy ID as well.
    new_ann._id = anchor._id;

    return new_ann;
};

///
/// Generalized violations. Currently just around ShEx.
///

/**
 * Model violations.
 *
 * These are currently ShEx oriented, but will be expanded into OWL,
 * GO rules, etc. in the future.
 *
 * The current structure of the raw object looks like:
 *
 * : {"key": "contributor", "value": "GOC:kltm" }
 *
 * @constructor
 * @param {Object} [vobj] - optional TBD object structure for violations
 * @returns {this} new instance
 */
function violation(vobj){

    this._id = bbop.uuid();
    this._node = null;
    this._explanations = [];

    if( us.isString(vobj['node']) ){
	this._node = bbop.clone(vobj['node']);
    }else{
	console.log('bad violation node: ', this._id);
    }

    if( us.isArray(vobj['explanations']) ){
	this._explanations = bbop.clone(vobj['explanations']);
    }else{
	console.log('bad violation explanations: ', this._id);
    }

    return this;
}

/**
 * The unique id of this violation.
 *
 * @returns {String} string
 */
violation.prototype.id = function(){
    return this._id;
};

/**
 * The unique id of this violation.
 *
 * @returns {String} string
 */
violation.prototype.node_id = function(){
    return this._node;
};

/**
 * The list of explanation objects for this violation.
 *
 * @returns {String} string
 */
violation.prototype.explanations = function(){
    return this._explanations;
};

/**
 * A clone of this violation object.
 *
 * @returns {violation} a fresh violation...
 */
violation.prototype.clone = function(){
    var anchor = this;

    var v = {};
    v['node'] = anchor.node_id();
    v['explanations'] = anchor.explanations();

    // Our constructor will use all new material, so no problem.
    var new_vio = new violation(v);
    return new_vio;
};

///
/// Generic internal annotation operations; dynamically attached to
/// graph, node, and edge.
///

/**
 * Get/set annotation list.
 *
 * @name annotations
 * @function
 * @param {Array} [in_anns] - list of annotations to clobber current list
 * @returns {Array} list of all annotations
 */
function _annotations(in_anns){
    if( us.isArray(in_anns) ){
	this._annotations = in_anns;
    }
    return this._annotations;
}

/**
 * Add annotation.
 *
 * @name add_annotation
 * @function
 * @param {annotation} in_ann - annotation to add
 * @returns {Array} list of all annotations
 */
function _add_annotation(in_ann){
    if( ! us.isArray(in_ann) ){
	this._annotations.push(in_ann);
    }
    return this._annotations;
}

/**
 * Get a sublist of annotation using the filter function. The filter
 * function take a single annotation as an argument, and adds to the
 * return list if it evaluates to true.
 *
 * @name get_annotations_by_filter
 * @function
 * @param {Function} filter - function described above
 * @returns {Array} list of passing annotations
 */
function _get_annotations_by_filter(filter){

    var anchor = this;
    var ret = [];
    each(anchor._annotations, function(ann){
	var res = filter(ann);
	if( res && res === true ){
	    ret.push(ann);
	}
    });
    return ret;
}

/**
 * Get sublist of annotations with a certain key.
 *
 * @name get_annotations_by_key
 * @function
 * @param {String} key - key to look for.
 * @returns {Array} list of list of annotations with that key
 */
function _get_annotations_by_key(key){
    var anchor = this;

    var ret = [];
    each(anchor._annotations, function(ann){
	if( ann.key() === key ){
	    ret.push(ann);
	}
    });

    return ret;
}

/**
 * Get sublist of annotations with a certain ID.
 *
 * @name get_annotations_by_id
 * @function
 * @param {String} aid - annotation ID to look for
 * @returns {Array} list of list of annotations with that ID
 */
function _get_annotation_by_id(aid){

    var anchor = this;
    var ret = null;
    each(anchor._annotations, function(ann){
	if( ann.id() === aid ){
	    ret = ann;
	}
    });
    return ret;
}

///
/// Generic internal evidence (reference individuals) operations;
/// dynamically attached to node and edge.
///

/**
 * Get/set referenced subgraph list.
 *
 * Copies in new data.
 *
 * @name referenced_subgraph
 * @function
 * @param {Array} [subgraphs] - list of {graph} to clobber current list
 * @returns {Array} list of all referenced subgraphs
 */
function _referenced_subgraphs(subgraphs){

    if( us.isArray(subgraphs) ){

	// Not copies, so add by replacement.
	this._referenced_subgraphs = [];
	// // Convert type.
	each(subgraphs, function(g){
	    //g.type('referenced');
	    this._referenced_subgraphs.push(g.clone());
	});

    }
    return this._referenced_subgraphs;
}

/**
 * Add referenced subgraph.
 *
 * @name add_referenced_subgraph
 * @function
 * @param {graph} subgraph - subgraph to add
 * @returns {Array} list of all subgraphs
 */
function _add_referenced_subgraph(subgraph){
    if( ! us.isArray(subgraph) ){
	//subgraph.type('referenced');
	this._referenced_subgraphs.push(subgraph);
    }
    return this._referenced_subgraphs;
}

/**
 * Get a sublist of referenced subgraphs using the filter
 * function. The filter function take a single subgraph as an
 * argument, and adds it to the return list if it evaluates to true.
 *
 * @name get_referenced_subgraphs_by_filter
 * @function
 * @param {Function} filter - function described above
 * @returns {Array} list of passing subgraphs
 */
function _get_referenced_subgraphs_by_filter(filter){
    var anchor = this;

    var ret = [];
    each(anchor._referenced_subgraphs, function(g){
	var res = filter(g);
	if( res && res === true ){
	    ret.push(g);
	}
    });

    return ret;
}

/**
* Get a referenced_subgraph with a certain ID.
 *
 * @name get_referenced_subgraph_by_id
 * @function
 * @param {String} iid - referenced_individual ID to look for
 * @returns {Object|null} referenced_subgraph with that ID
 */
function _get_referenced_subgraph_by_id(iid){
    var anchor = this;

    var ret = null;
    each(anchor._referenced_subgraphs, function(g){
	if( g.id() === iid ){
	    ret = g;
	}
    });

    return ret;
}

/**
 * Returns a list with the following structure:
 *
 * : [ { id: <ID>,
 * :     class_expressions: [{class_expression}, ...],
 * :     anntations: [{annotation}, ...] },
 * :   ...
 * : ]
 *
 * Each top-level element in the list represents the core information
 * of a single referenced graph for a node or edge in this model.
 *
 * Keep in mind that this may be most useful in the GO Noctua use case
 * as reference subgraphs with singleton elements, where the class(es)
 * are evidence and the annotations keep things such as source
 * (e.g. PMID), etc.
 *
 * @name get_referenced_subgraph_profiles
 * @function
 * @param {Function} [extractor] extraction functions to use instead of default
 * @returns {Array} list of referenced_individual information
 */
function _get_referenced_subgraph_profiles(extractor){
    var anchor = this;

    //
    function extractor_default(g){
	var ret = null;

	// If singleton.
	if( g.all_nodes().length === 1 ){
	    var ind = g.all_nodes()[0];

	    // Base.
	    var prof = {
		id: null,
		class_expressions: [],
		annotations: []
	    };

	    // Referenced instance ID.
	    prof['id'] = ind.id();

	    // Collect class expressions and annotations.
	    each(ind.types(), function(ce){
		prof['class_expressions'].push(ce);
	    });
	    each(ind.annotations(), function(ann){
		prof['annotations'].push(ann);
	    });

	    //
	    ret = prof;
	}

	return ret;
    }

    // If we are using the simple standard.
    if( typeof(extractor) !== 'function' ){
	extractor = extractor_default;
    }

    // Run the extractor over the referenced subraph in the calling
    // node.
    var ret = [];
    each(anchor.referenced_subgraphs(), function(g){
	var extracted = extractor(g);
	if( extracted ){
	    ret.push(extracted);
	}
    });

    return ret;
}

/**
 * Returns a list with the following structure:
 *
 * : [ { id: <ID>,
 * :     cls: <ID>,
 * :     source: <STRING>,
 * :     date: <STRING>,
 * :     etc
 * :   },
 * :   ...
 * : ]
 *
 * Each top-level element in the list represents the core information
 * in a simple (GO-style) element. This is essentially a distilled
 * version of get_referenced_individual_profiles for cases where that
 * is modelling simple piece of evidence (single non-nested class
 * expression and a set know list of annotations).
 *
 * @name get_basic_evidence
 * @function
 * @param {Array} annotation_ids - list of strings that identify the annotation keys that will be captured--
 * @returns {Array} list of referenced_individual simple evidence information.
 */
function _get_basic_evidence(annotation_ids){
    var anchor = this;

    var ret = [];

    // Get hash of the annotation keys present.
    var test = us.object(us.map(annotation_ids,
				function(e){ return [e, true]; }));

    each(anchor.get_referenced_subgraph_profiles(), function(cmplx_prof){
	//console.log(cmplx_prof);

	// Only add conformant referenced individuals.
	if( cmplx_prof.id && ! us.isEmpty(cmplx_prof.class_expressions) ){

	    // Base.
	    //console.log(cmplx_prof.class_expressions);
	    var basic_prof = {
		id: cmplx_prof.id,
		cls: cmplx_prof.class_expressions[0].to_string()
	    };

	    // Match and clobber.
	    each(cmplx_prof.annotations, function(ann){
		//console.log(ann);
		if( test[ann.key()] ){
		    basic_prof[ann.key()] = ann.value();
		}
	    });

	    //console.log(basic_prof);
	    ret.push(basic_prof);
	}

    });

    return ret;
}

///
/// Next, get some subclasses working for the core triumvirate: graph,
/// node, edge. Start with graph.
///

var bbop_graph = bbop_model.graph;

/**
 * Sublcass of bbop-graph for use with Noctua ideas and concepts.
 *
 * Unlike the superclass, can take an id as an argument, or will
 * generate on on its own.
 *
 * @constructor
 * @see module:bbop-graph
 * @alias graph
 * @param {String} [new_id] - new id; otherwise new unique generated
 * @returns {this}
 */
function noctua_graph(new_id){
    bbop_graph.call(this);
    this._is_a = 'bbop-graph-noctua.graph';

    // Deal with id or generate a new one.
    if( typeof(new_id) !== 'undefined' ){
	this.id(new_id);
    }

    // The old edit core.
    this.core = {
	'edges': {}, // map of id to edit_edge - edges not completely anonymous
	'node_order': [], // initial table order on redraws
	'node2elt': {}, // map of id to physical object id
	'elt2node': {},  // map of physical object id to id
	// Remeber that edge ids and elts ids are the same, so no map
	// is needed.
	'edge2connector': {}, // map of edge id to virtual connector id
	'connector2edge': {}  // map of virtual connector id to edge id
    };

    this._annotations = [];
    //this._referenced_subgraphs = []; // not for graph yet, or maybe ever

    // Some things that come up in live noctua environments. These are
    // graph properties that may or may not be there. If unknown,
    // null; if positively true (bad), true; may be false otherwise.
    this._inconsistent_p = null;
    this._modified_p = null;

    // New section as we work out how violations operate.
    this._violations = [];
    this._valid_p = true;
    this._valid_owl_p = true;
    this._valid_shex_p = true;
}
bbop.extend(noctua_graph, bbop_graph);

/**
 * Create an edge for use in internal operations.
 *
 * @param {string} subject - node id string or node
 * @param {string} object - node id string or node
 * @param {string} [predicate] - a user-friendly description of the node
 * @returns {edge} bbop model edge
 */
noctua_graph.prototype.create_edge = function(subject, object, predicate){
    return new noctua_edge(subject, object, predicate);
};

/**
 * Create a node for use in internal operations.
 *
 * @param {string} id - a unique id for the node
 * @param {string} [label] - a user-friendly description of the node
 * @param {Array} [types] - list of types to pre-load
 * @param {Array} [inferred_types] - list of inferred types to pre-load
 * @returns {node} new bbop model node
 */
noctua_graph.prototype.create_node = function(id, label, types, inferred_types, inferred_types_with_all){
    return new noctua_node(id, label, types, inferred_types, inferred_types_with_all);
};

/**
 * Create a clone of the graph.
 *
 * Naturally, ID is copied.
 *
 * @returns {graph} bbop model graph
 */
noctua_graph.prototype.clone = function(){
    var anchor = this;

    var new_graph = anchor.create_graph();

    // Collect the nodes and edges.
    each(anchor.all_nodes(), function(node){
	new_graph.add_node(node.clone());
    });
    each(anchor.all_edges(), function(edge){
	new_graph.add_edge(edge.clone());
    });

    // Collect other information.
    new_graph.default_predicate = anchor.default_predicate;
    new_graph._id = anchor._id;

    // Copy new things: annotations.
    each(anchor._annotations, function(annotation){
	new_graph._annotations.push(annotation.clone());
    });

    // Copy other properties over.
    new_graph._inconsistent_p = anchor._inconsistent_p;
    new_graph._modified_p = anchor._modified_p;

    // Copy violation information over.
    each(anchor._violations, function(v){
	new_graph._violations.push(v.clone());
    });
    new_graph._valid_p = anchor._valid_p;
    new_graph._valid_owl_p = anchor._valid_owl_p;
    new_graph._valid_shex_p = anchor._valid_shex_p;

    return new_graph;
};

/**
 * Create a graph for use in internal operations.
 *
 * @returns {graph} bbop model graph
 */
noctua_graph.prototype.create_graph = function(){
    return new noctua_graph();
};

/**
 * Add an ID to the graph.
 *
 * Use .id() instead.
 *
 * @deprecated
 * @see module:bbop-graph#id
 * @param {String} id - string
 * @returns {String} string
 */
noctua_graph.prototype.add_id = function(id){
    return this.id(id);
};

/**
 * Get the ID from the graph.
 *
 * Use .id() instead.
 *
 * @deprecated
 * @see module:bbop-graph#id
 * @returns {String} string
 */
noctua_graph.prototype.get_id = function(){
    return this.id();
};

/**
 * Returns true if the model had the "inconsistent-p" property when
 * built.
 *
 * @returns {Boolean|null} inconsistent or not; null if unknown
 */
noctua_graph.prototype.inconsistent_p = function(){
    return this._inconsistent_p;
};

/**
 * Returns true if the model had the "modified-p" property when
 * built.
 *
 * @returns {Boolean|null} inconsistent or not; null if unknown
 */
noctua_graph.prototype.modified_p = function(){
    return this._modified_p;
};

/**
 * Returns boolean on whether the returned model is /completely/
 * valid; true if there is no logic to probe (this is a temporary
 * measure until reasoner is "always on").
 *
 * @returns {Boolean} p - bool
 */
noctua_graph.prototype.valid_p = function(){
    return this._valid_p;
};

/**
 * Returns boolean on whether the returned model is valid according to
 * the owl reasoner; true if there is no logic to probe (this is a temporary
 * measure until reasoner is "always on").
 *
 * @returns {Boolean} p - bool
 */
noctua_graph.prototype.valid_owl_p = function(){
    return this._valid_owl_p;
};

/**
 * Returns boolean on whether the returned model is valid according to
 * ShEx shapes in Minerva; true if there is no logic to probe (this is
 * a temporary measure until reasoner is "always on").
 *
 * @returns {Boolean} p - bool
 */
noctua_graph.prototype.valid_shex_p = function(){
    return this._valid_shex_p;
};

/**
 * Returns an array of objects that describe the ShEx violations
 * found in a model.
 *
 * @returns {Array} violations or an empty list if none
 */
noctua_graph.prototype.violations = function(){
    return this._violations;
};

/**
 * Return an edge by is ID.
 *
 * @param {String} node_id - the ID of the {edge}
 * @returns {Array|Null} - Returns a list of 'violation' found in the graph for a given id.
 */
noctua_graph.prototype.get_violations_by_id = function(node_id){
    var ret = [];

    // TODO: needs to be optimized once we figure out how we handle
    // shex + owl + whatever as general objects in here.
    each(this.violations(), function(v){
	if( v.node_id() === node_id ){
	    ret.push(v.clone());
	}
    });

    return ret;
};

/**
 * Get the ID from the graph.
 *
 * @param {node} enode - noctua node
 * @returns {Boolean} true on new node
 */
noctua_graph.prototype.add_node = function(enode){
    // Super call: add it to the general graph.
    bbop_graph.prototype.add_node.call(this, enode);

    var ret = false;

    // Add/update node.
    var enid = enode.id();
    //this.core['nodes'][enid] = enode; // add to nodes

    // Only create a new elt ID and order if one isn't already in
    // there (or reuse things to keep GUI working smoothly).
    var elt_id = this.core['node2elt'][enid];
    if( ! elt_id ){ // first time
	this.core['node_order'].unshift(enid); // add to default order
	elt_id = bbop.uuid(); // generate the elt id we'll use from now on
	this.core['node2elt'][enid] = elt_id; // map it
	this.core['elt2node'][elt_id] = enid; // map it
	ret = true;
    }

    return ret;
};

/**
 * Add a node into the graph modeled from the the JSON-LD lite model.
 * Creates or adds types and annotations as necessary.
 *
 * @param {Object} indv - hash rep of graph individual from Minerva response?
 * @returns {node|null}
 */
noctua_graph.prototype.add_node_from_individual = function(indv){
    var anchor = this;

    var new_node = null;

    // Add individual to edit core if properly structured.
    var iid = indv['id'];
    if( iid ){
	//var nn = new bbop.model.node(indv['id']);
	//var meta = {};
	//ll('indv');

	// See if there is type info that we want to add.
	// Create the node.
	var itypes = indv['type'] || [];
	var inf_itypes = indv['inferred-type'] || [];
	var inf_itypes_with_all = indv['inferred-type-with-all'] || [];
	new_node = anchor.create_node(iid, null, itypes,
				      inf_itypes, inf_itypes_with_all);

	// See if there is type info that we want to add.
	var ianns = indv['annotations'] || [];
	if( us.isArray(ianns) ){
	    // Add the annotations individually.
	    each(ianns, function(ann_kv_set){
		var na = new annotation(ann_kv_set);
		new_node.add_annotation(na);
	    });
	}

	anchor.add_node(new_node);
    }

    return new_node;
};

/**
 * Return the "table" order of the nodes.
 *
 * @returns {Array} node order by id?
 */
noctua_graph.prototype.edit_node_order = function(){
    return this.core['node_order'] || [];
};

/**
 * Return a node's element id.
 *
 * @returns {String|null} node element id
 */
noctua_graph.prototype.get_node_elt_id = function(enid){
    return this.core['node2elt'][enid] || null;
};

/**
 * Return a copy of a {node} by its element id.
 *
 * @returns {node|null} node
 */
noctua_graph.prototype.get_node_by_elt_id = function(elt_id){
    var ret = null;
    var enid = this.core['elt2node'][elt_id] || null;
    if( enid ){
	ret = this.get_node(enid) || null;
    }
    return ret;
};

/**
 * Return a copy of a {node} by its corresponding Minerva JSON rep
 * individual.
 *
 * @returns {node|null} node
 */
noctua_graph.prototype.get_node_by_individual = function(indv){
    var anchor = this;

    var ret = null;

    // Get node from graph if individual rep is properly structured.
    var iid = indv['id'];
    if( iid ){
	ret = this.get_node(iid) || null;
    }

    return ret;
};

/**
 * Return a hash of node ids to nodes.
 * Real, not a copy.
 *
 * @see module:bbop-graph#all_nodes
 * @returns {Object} node ids to nodes
 */
noctua_graph.prototype.get_nodes = function(){
    return this._nodes || {};
};

/**
 * Remove a node from the graph.
 *
 * @param {String} node_id - the id for a node
 * @param {Boolean} [clean_p] - remove all edges connects to node (default false)
 * @returns {Boolean} true if node found and destroyed
 */
noctua_graph.prototype.remove_node = function(node_id, clean_p){
    var anchor = this;

    var ret = false;
    var enode = anchor.get_node(node_id);
    if( enode ){
	ret = true;

	///
	/// First, remove all subclass decorations.
	///

	// Also remove the node from the order list.
	// TODO: Is this a dumb scan?
	var ni = this.core['node_order'].indexOf(node_id);
	if( ni !== -1 ){
	    this.core['node_order'].splice(ni, 1);
	}

	// Clean the maps.
	var elt_id = this.core['node2elt'][node_id];
	delete this.core['node2elt'][node_id];
	delete this.core['elt2node'][elt_id];

	///
	/// We want to maintain superclass compatibility.
	///

	// Finally, remove the node itself.
	bbop_graph.prototype.remove_node.call(this, node_id, clean_p);
    }

    return ret;
};

/**
 * Add an edge to the graph. Remember that edges are no anonymous
 * edges here.
 *
 * @param {edge} eedge - a bbop-graph-noctua#edge
 */
noctua_graph.prototype.add_edge = function(eedge){

    // Super.
    bbop_graph.prototype.add_edge.call(this, eedge);

    // Sub.
   var eeid = eedge.id();
   if( ! eeid ){ throw new Error('edge not of bbop-graph-noctua'); }
   this.core['edges'][eeid] = eedge;
};

/**
 * Add an edge to the graph using a "fact" as the seed.
 * Creates and adds annotations as necessary.
 *
 * @param {} fact - JSON structure representing a fact
 * @returns {edge} newly created edge
 */
noctua_graph.prototype.add_edge_from_fact = function(fact){
    var anchor = this;

    var new_edge = null;

    // Add individual to edit core if properly structured.
    var sid = fact['subject'];
    var oid = fact['object'];
    var pid = fact['property'];
    var plbl = fact['property-label'];
    var anns = fact['annotations'] || [];
    if( sid && oid && pid ){

	new_edge = anchor.create_edge(sid, oid, pid);
	if( ! us.isArray(anns) ){
	    throw new Error('annotations is wrong');
	}else{
	    // Add the annotations individually.
	    each(anns, function(ann_kv_set){
		var na = new annotation(ann_kv_set);
		new_edge.add_annotation(na);
	    });
	}
	// Add edge if possible.
	if( plbl ){
	    new_edge.label(plbl);
	}

	// Add and ready to return edge.
	anchor.add_edge(new_edge);
    }

    return new_edge;
};

/**
 * Return an edge by is ID.
 *
 * @param {String} edge_id - the ID of the {edge}
 * @returns {edge|null} - the {edge}
 */
noctua_graph.prototype.get_edge_by_id = function(edge_id){
    var ret = null;
    var ep = this.core['edges'][edge_id];
    if( ep ){ ret = ep; }
    return ret;
};

/**
 * Return an edge ID by it's associated connector ID if extant.
 *
 * @param {String} cid - the ID of the connector.
 * @returns {String} - the ID of the associated edge
 */
noctua_graph.prototype.get_edge_id_by_connector_id = function(cid){
    return this.core['connector2edge'][cid] || null;
};

/**
 * Return a connector by it's associated edge ID if extant.
 *
 * @param {String} eid - the ID of the edge
 * @returns {String} - the connector ID
 */
noctua_graph.prototype.get_connector_id_by_edge_id = function(eid){
    return this.core['edge2connector'][eid] || null;
};

/**
 * Remove an edge to the graph.
 * The edge as referenced.
 *
 * @param {String} subject_id - subject by ID
 * @param {String} object_id - object by ID
 * @param {String} [predicate_id] - predicate ID or default
 * @returns {Boolean} true if such an edge was found and deleted, false otherwise
 */
// noctua_graph.prototype.remove_edge = function(subject_id, object_id, predicate_id){
//     var ret = false;

//     var eedge = this.get_edge(subject_id, object_id, predicate_id);
//     if( eedge ){
// 	ret = this.remove_edge_by_id(eedge.id());
//     }

//     return ret;
// };

/**
 * Remove an edge to the graph.
 * The edge as IDed.
 *
 * @param {String} edge_id - edge by ID
 * @returns {Boolean} true if such an edge was found and deleted, false otherwise
 */
noctua_graph.prototype.remove_edge_by_id = function(eeid){
    var ret = false;

    if( this.core['edges'][eeid] ){

	// Summon up the edge to properly remove it from the model.
	var eedge = this.core['edges'][eeid];

	// Remove the node itself from super.
	ret = bbop_graph.prototype.remove_edge.call(this,
						    eedge.subject_id(),
						    eedge.object_id(),
						    eedge.predicate_id());

	// Main bit out.
	delete this.core['edges'][eeid];

	// And clean the maps.
	var cid = this.core['edge2connector'][eeid];
	delete this.core['edge2connector'][eeid];
	delete this.core['connector2edge'][cid];
    }

    return ret;
};

/**
 * Internally connect an edge to a connector ID
 *
 * TODO/BUG: Should use generic ID mapping rather than depending on
 * jsPlumb thingamajunk.
 *
 * @deprecated
 * @param {edge} eedge - edge
 * @param {connector} connector - jsPlumb connector
 */
noctua_graph.prototype.create_edge_mapping = function(eedge, connector){
    var eid = eedge.id();
    var cid = connector.id;
    this.core['edge2connector'][eid] = cid;
    this.core['connector2edge'][cid] = eid;
};

/**
 * Debugging text output function.
 *
 * Not sure what this is for anymore honestly...
 *
 * @deprecated
 * @returns {String} a graph rep as a string
 */
noctua_graph.prototype.dump = function(){

    //
    var dcache = [];

    each(this.get_nodes(), function(node, node_id){
	var ncache = ['node'];
	ncache.push(node.id());
	dcache.push(ncache.join("\t"));
    });

    each(this.core['edges'], function(edge, edge_id){
	var ecache = ['edge'];
	ecache.push(edge.subject_id());
	ecache.push(edge.predicate_id());
	ecache.push(edge.object_id());
	dcache.push(ecache.join("\t"));
    });

    return dcache.join("\n");
};

/**
 * Merge another graph (addition) into the current graph. Includes the
 * copying of annotations for the graph. This is an /additive/
 * operation (e.g. annotations and other non-unique entities
 * accumulate). Graph ID is /not/ copied.
 *
 * modified-p and inconsistent-p properties are copied from the the
 * incoming graph (assuming that the update has more recent
 * information).
 *
 * @param {graph} in_graph - the graph to merge in
 * @returns {Boolean} if graph was loaded
 */
noctua_graph.prototype.merge_in = function(in_graph){
    var anchor = this;

    var ret = bbop_graph.prototype.merge_in.call(anchor, in_graph);

    // Function to check if two annotations are the same.
    function _is_same_ann(a1, a2){
    	var ret = false;
    	if( a1.key() === a2.key() &&
    	    a1.value() === a2.value() &&
    	    a1.value_type() === a2.value_type() ){
    	    ret = true;
    	}
    	return ret;
    }

    // Merge in graph annotations.
    var in_graph_anns = in_graph.annotations();
    each(in_graph_anns, function(ann){
    	// If there are no annotations that have the same KVT triple,
    	// add a clone.
    	if( anchor.get_annotations_by_filter( function(a){ return _is_same_ann(ann, a); } ).length === 0 ){
    	    anchor.add_annotation(ann.clone());
    	}
    });

    // Accept the signal that the merge in graph (update) has the
    // correct modification and inconsistent information.
    anchor._inconsistent_p = in_graph._inconsistent_p;
    anchor._modified_p = in_graph._modified_p;

    // Accept the signal that the merge in graph (update) has the
    // correct validation/violation information.
    // TODO: Ask @goodb what the semantics are for different
    // signals when the reasoner are on--I'm assuming we clobber
    // here.
    anchor._violations = [];
    each(in_graph._violations, function(violation){
	anchor._violations.push(violation.clone());
    });
    anchor._valid_p = in_graph._valid_p;
    anchor._valid_owl_p = in_graph._valid_owl_p;
    anchor._valid_shex_p = in_graph._valid_shex_p;

    return ret;
};

/**
 * Merge another graph into the current graph, with special overwrite
 * rules. In essence, this could be used when trying to simulate a
 * rebuild even though you got merge data.
 *
 * Annotations in any top-level item (graph, node, edge), or lack
 * thereof, from the incoming graph is preferred.
 *
 * All extant edges and nodes in the incoming graph are clobbered.
 *
 * The incoming graph is considered to be "complete", so any edges
 * where both the source and sink are in the incoming graph are
 * considered to be the only edges between those node.
 *
 * Graph ID is /not/ copied.
 *
 * Beware that you're in the right folded mode.
 *
 * @param {graph} in_graph - the graph to merge in
 * @returns {Boolean} if graph was loaded
 */
noctua_graph.prototype.merge_special = function(in_graph){
    var anchor = this;

    // Since we can actually legally have an edge delete in the
    // merge, let's go ahead and cycle through the "complete"
    // graph and toss edges from individuals involved in the
    // merge.
    var involved_node = {};
    each(in_graph.all_nodes(), function(node){
	involved_node[node.id()] = true;
    });
    // Okay, now get rid of all edges that are defined by the
    // involved nodes.
    each(anchor.all_edges(), function(edge){
	if(involved_node[edge.subject_id()] && involved_node[edge.object_id()]){
	    anchor.remove_edge_by_id(edge.id());
	}
    });

    // Blitz our old annotations as all new will be incoming (and
    // the merge just takes the superset). Will be fine with fix:
    // https://github.com/geneontology/minerva/issues/5
    anchor.annotations([]);

    var ret = anchor.merge_in(in_graph);
    return ret;
};

/**
 * DEPRECATED
 *
 * This uses a subgraph to update the contents of the current
 * graph. The update graph is considered to be an updated complete
 * self-contained subsection of the graph, clobbering nodes, edges,
 * and the graph annotations. In the case of edges, all edges for the
 * incoming nodes are deleted, and the ones described in the incoming
 * graph are added (again, update).
 *
 * For example: you can think of it like this: if we have a graph:
 *  A, B, C, and A.1, where A, B, and C are nodes and A.1 is an annotation for A.
 * And we have an argument subgraph:
 *  A, B, and edge (A,B), and A.2, B.1.
 * The final graph would be:
 *  A, B, C and edge (A,B), and A.2, B.1.
 *
 * Essentially, any entity in the new graph clobbers the "old"
 * version; nodes not mentioned are left alone, the subgraph edges are
 * assumed to be complete with reference to the contained nodes. This
 * can express removal of things like annotations and sometimes edges,
 * but not of nodes and edges not contained in within the subgraph.
 *
 * See the unit tests for examples.
 *
 * Be careful of what happens when using with the various loaders as
 * the contents of top-level entities can be very different--you
 * probably want to apply the right loader first.
 *
 * @deprecated
 * @param {graph} in_graph - the graph to update with
 * @returns {Boolean} if graph was loaded
 */
noctua_graph.prototype.update_with = function(update_graph){
    var anchor = this;

    // Prefer the new graph annotations by nuking the old.
    anchor._annotations = [];
    var update_graph_anns = update_graph.annotations();
    each(update_graph_anns, function(ann){
    	anchor.add_annotation(ann.clone());
    });

    // Next, look at individuals/nodes for addition or updating.
    var updatable_nodes = {};
    each(update_graph.all_nodes(), function(ind){
	// Update node by clobbering. This is preferred since deleting
	// it would mean that all the connections would have to be
	// reconstructed as well.
	var update_node = anchor.get_node(ind.id());
	if( update_node ){
	    //console.log('update node: ' + ind.id());
	}else{
	    //console.log('add new node' + ind.id());
	}
	// Mark as a modified node.
	updatable_nodes[ind.id()] = true;
	// Add new node to edit core.
	anchor.add_node(ind.clone());
    });

    // Now look at edges (by individual) for purging and
    // reinstating--no going to try and update edges, just clobber.
    each(update_graph.all_nodes(), function(source_node){
    	//console.log('looking at node: ' + source_node.id());

    	// Look up what edges it has in /core/, as they will be the
    	// ones to update.
    	var snid = source_node.id();
    	var src_edges = anchor.get_edges_by_subject(snid);

    	// Delete all edges for said node in model. We cannot
    	// (apparently?) go from connection ID to connection easily,
    	// so removing from UI is a separate step.
    	each(src_edges, function(src_edge){
    	    // Remove from model.
    	    var removed_p = anchor.remove_edge_by_id(src_edge.id());
    	    //console.log('remove edge (' + removed_p + '): ' + src_edge.id());
    	});
    });

    // All edges should have IDs, so get them out of the graph if they
    // are incoming.
    each(update_graph.all_edges(), function(edge){
	var in_id = edge.id();
	anchor.remove_edge_by_id(in_id);
	anchor.add_edge(edge.clone());
    });

    return true;
};

/**
 * Load minerva data response.
 *
 * TODO: inferred individuals
 *
 * @param {Object} the "data" portion of a Minerva graph-related response.
 * @returns {Boolean} if data was loaded
 */
noctua_graph.prototype.load_data_basic = function(data){
    var anchor = this;

    var ret = false;

    if( data ){

	// Add the graph metadata.
	var graph_id = data['id'] || null;
	var graph_anns = data['annotations'] || [];
	if( graph_id ){ anchor.id(graph_id); }
	if( ! us.isEmpty(graph_anns) ){
	    each(graph_anns, function(ann_kv_set){
		var na = new annotation(ann_kv_set);
		anchor.add_annotation(na);
	    });
	}

	// Add the additional metadata.
	if( typeof(data['inconsistent-p']) !== 'undefined' ){
	    anchor._inconsistent_p = data['inconsistent-p'];
	}
	if( typeof(data['modified-p']) !== 'undefined' ){
	    anchor._modified_p = data['modified-p'];
	}

	// Validation/violation information. Currently, this will only
	// be entered if the reasoner is on.
	if( typeof(data['validation-results']) !== 'undefined' &&
	    us.isObject(data['validation-results']) ){

	    var vres = data['validation-results'];

	    // Top level.
	    if( us.isBoolean(vres['is-conformant']) ){
		if( vres['is-conformant'] === true ){
		    anchor._valid_p = true;
		}else if( vres['is-conformant'] === false ){
		    anchor._valid_p = false;
		}
	    }

	    // OWL.
	    if( vres['owl-validation'] && us.isObject(vres['owl-validation']) ){
		if( vres['owl-validation']['is-conformant'] === true ){
		    anchor._valid_owl_p = true;
		}else if( vres['owl-validation']['is-conformant'] === false ){
		    anchor._valid_owl_p = false;
		}
	    }

	    // ShEx.
	    if( vres['shex-validation'] && us.isObject(vres['shex-validation'])){
		if( vres['shex-validation']['is-conformant'] === true ){
		    anchor._valid_shex_p = true;
		}else if( vres['shex-validation']['is-conformant'] === false ){
		    anchor._valid_shex_p = false;
		}
	    }

	    // Collect into violation objects; currently just ShEx.
	    if( vres['shex-validation'] &&
		us.isObject(vres['shex-validation']) &&
		us.isArray(vres['shex-validation']['violations']) ){

		var violations = vres['shex-validation']['violations'] || [];
		each(violations, function(raw_v){
		    var new_v = new violation(raw_v);
		    anchor._violations.push(new_v);
		});
	    }
	}

	// Easy facts.
	var facts = data['facts'];
	each(facts, function(fact){
	    anchor.add_edge_from_fact(fact);
	});

	// Build the structure of the graph in the most obvious way.
	var inds = data['individuals'];
	each(inds, function(ind){
	    anchor.add_node_from_individual(ind);
	});

	ret = true;
    }

    return ret;
};

/**
 * Extract all of the evidence seeds from the graph--nodes and edges.
 *
 * An evidence seed is a: 1) real node in the graph that 2) is
 * referenced by the value of a node or edge special evidence
 * annotation.
 *
 * @returns {Object} a map of seeds (by id) to their referencing enity {node} or {edge}
 */
noctua_graph.prototype.extract_evidence_seeds = function(){
    var anchor = this;

    // Take and, and see if it is an evidence reference.
    function is_iri_ev_p(ann){
	var ret = false;
	if( ann.key() === 'evidence' && ann.value_type() === 'IRI' ){
	    ret = true;
	}
	return ret;
    }

    // For any node, look at all of the annotations, and fold in
    // ones that 1) pass the test and 2) reference a singleton
    // node.
    var seeds = {}; // collect all possibilities here
    function pull_seeds(entity, test_p){
	each(entity.annotations(), function(ann){

	    //console.log(ann.key(), ann.value_type(), ann.value());

	    // Is it an evidence annotation.
	    if( ! test_p(ann) ){
		// Skip.
		//console.log('skip folding with failed test');
	    }else{
		//console.log('start folding with passed test');

		// If so, and the individual in question exists, it is
		// the jumping off point for the evidence folding
		// subgraph.
		var ref_node_id = ann.value();
		var ref_node = anchor.get_node(ref_node_id);
		if( ref_node ){
		    seeds[ref_node_id] = entity;
		}
	    }
	});
    }

    // Cycle through everything and collect them.
    each(anchor.all_nodes(), function(node){
	pull_seeds(node, is_iri_ev_p);
    });
    each(anchor.all_edges(), function(edges){
	pull_seeds(edges, is_iri_ev_p);
    });

    return seeds;
};

/**
 * Extract the entire super clique subgraph for an entity.
 *
 * The ID for the graph will be the ID of the seed node.
 *
 * BUG/WARNING: The clique actually needs to use the walker rather
 * than the anc/desc functions it uses now.
 *
 * @param {String} node_id the ID of the see node in an evidence clique
 * @returns {graph} a list of found seeds as {node} ids
 */
noctua_graph.prototype.get_evidence_clique = function(node_id){
    var anchor = this;

    // Create the clique by grabbing all nodes and creating a walkable
    // neighborhood.
    var up = anchor.get_ancestor_subgraph(node_id);
    //console.log("UP: ", up);
    var down = anchor.get_descendent_subgraph(node_id);
    //console.log("DOWN: ", down);
    up.merge_in(down);

    up.id(node_id);

    var ret = up;
    return ret;
};

/**
 * Extract an evidence subclique starting at a seed node.
 *
 * A subclique is a subgraph within a clique that represents a piece
 * of evidence, and may overlap with other pieces of evidence.
 *
 * The ID for the graph will be the ID of the seed node.
 *
 * Returns a clone.
 *
 * TODO: More to do as we expand what the evidence subgraphs look
 * like.
 *
 * @param {String} node_id the ID of the seed node in an evidence clique - it is *assumed* that this is a legit seed node id
 * @returns {graph|null} a list of found seeds as {node} ids
 */
noctua_graph.prototype.get_evidence_subclique = function(node_id){
    var anchor = this;

    var ret = null;

    // Must have a seed to start.
    var seed_node = anchor.get_node(node_id);
    if( seed_node ){

	// Start a new graph here. If this is the traditional simple
	// GO model, we also stop here.
	var ret_graph = anchor.create_graph();
	ret_graph.id(node_id);
	ret_graph.add_node(seed_node.clone());

	// For more complicated PMID evidence, we need to walk a
	// little deeper.
	// Add the kids...
	var kids = anchor.get_child_nodes(seed_node.id(), 'IAO:0000136');
	if( ! us.isEmpty(kids) ){
	    each(kids, function(kid){
		var klone = kid.clone();
		ret_graph.add_node(klone);
	    });
	    // ...and create new edges.
	    var keds = anchor.get_child_edges(seed_node.id(), 'IAO:0000136');
	    each(keds, function(ked){
		var klone = ked.clone();
		ret_graph.add_edge(klone);
	    });

	    // TODO: Dig down deeper from here for publication.
	}

	// TODO: Dig down deeper from here for non-publication.

	// This is what we'll return.
	ret = ret_graph;
    }

    return ret;
};

/**
 * Fold the evidence individuals into the edges and nodes that
 * reference them under the referenced_subgraph functions.
 *
 * Currently, a single pass is run to fold evidence subgraphs
 * (sometimes containing a single node) into other nodes/edges as
 * referenced subgraphs. However, additional passes can very easily be
 * added to fold away references to references as long as a matching
 * function is provided.
 *
 * @returns {Boolean} if data was loaded
 */
noctua_graph.prototype.fold_evidence = function(){
    var anchor = this;

    var ret = false;

    // We are going to fold by clique.
    var seeds = anchor.extract_evidence_seeds();
    //console.log('seeds', us.keys(seeds));

    // Get the cliques (super-neighborhood evidence) and get a map of
    // what seeds are in each clique. Instead of comparing cliques to
    // eliminate dupes (it's possible to have shared structure), we
    // just keep checking the seeds, marking the ones that we've seen
    // so we don't check again.
    //
    // This section produces a clique map and a clique to sed map.
    //
    var cliques = {}; // clique_id->clique
    var clique_seed_map = {}; // clique_id->{seeds->true, in->true, clique->true}
    var skippable_seeds = {}; // once we see a seed, we can skip it afterwards
    each(seeds, function(referncing_entity, seed_id){
	//console.log('seed_id', seed_id);
	if( ! skippable_seeds[seed_id] ){ // skip uneeded ones

	    // Get clique.
	    var clique = anchor.get_evidence_clique(seed_id);
	    var clique_id = clique.id();
	    //console.log(' clique_id', clique_id);
	    //console.log(' clique', clique);

	    // Ready clique map.
	    clique_seed_map[clique_id] = {};

	    //
	    each(seeds, function(check_referencing_entity, check_seed_id){
		if( ! skippable_seeds[check_seed_id] ){ // skip uneeded ones
		    //console.log(' pass', check_seed_id);

		    //
		    if( clique.get_node(check_seed_id) ){
			//console.log(' in clique:', check_seed_id);

			// Add seed to map and skippable.
			clique_seed_map[clique_id][check_seed_id] = true;
			skippable_seeds[check_seed_id] = true;
			cliques[clique_id] = clique;
			// console.log('seed',check_seed_id,
			// 	    '\n in clique',clique_id);
		    }else{
			// Pass.
			// console.log('seed',check_seed_id,
			// 	    'not in clique',clique_id);
		    }
		}
	    });
	}
    });

    //console.log('cliques', cliques);
    //console.log('cliques', us.keys(cliques));
    //console.log('clique_seed_map', clique_seed_map);

    // Okay, we will do the folding on a clique-by-clique basis. See
    // the top of the file for rules.
    each(cliques, function(clique, clique_id){

	//console.log('clique_id', clique_id);

	// Nodes in the clique.
	var clique_nodes = {};
	each(clique.all_nodes(), function(cn){
	    var cnid = cn.id();
	    clique_nodes[cnid] = true;
	});

	// Collect the subcliques for every clique.
	var contained_seed_map = clique_seed_map[clique_id];
	//console.log('csm', contained_seed_map);
	var subcliques = {};
	each(contained_seed_map, function(bool, seed_id){

	    var subclique = anchor.get_evidence_clique(seed_id);
	    //console.log(subclique);

	    // Add to cache of subcliques.
	    subcliques[seed_id] = subclique;

	    // Mark out all of the clique_nodes seen.
	    each(subclique.all_nodes(), function(sub_node){
		var snid = sub_node.id();
		if( clique_nodes[snid] ){
		    delete clique_nodes[snid];
		}
	    });
	});

	//console.log('clique_nodes', clique_nodes);
	//console.log('subcliques', subcliques);

	// Okay, if the clique_nodes map is empty, that means it is
	// completely covered by the subcliques and can be removed.
	if( ! us.isEmpty(clique_nodes) ){
	    //console.log(' cannot fold clique due to maigo no node');
	}else{
	    // Add subcliques to initial referring nodes.
	    each(subcliques, function(subclique, seed_id){
		// Make sure that we fold into the original node of
		// the original graph.
		var origin_entity = seeds[seed_id]; // still a copy
		if( ! origin_entity ){
		    // console.log('skip addition (possibly edge uuid)');
		    // console.log('skip addition over (A)', seed_id);
		    // console.log('skip addition over (B)',
		    // 		origin_node_clone_maybe.id());
		}else{
		    // Since origin_entity is a copy, we'll modify it
		    // and re-add it to the graph to make change
		    // permanent.
		    origin_entity.add_referenced_subgraph(subclique);
		    // clobber non-ref version
		    var entity_is =  bbop.what_is(origin_entity);
		    //console.log(entity_is, entity_is);
		    if( entity_is === 'bbop-graph-noctua.node' ){
			anchor.add_node(origin_entity);
		    }else if( entity_is === 'bbop-graph-noctua.edge' ){
			anchor.add_edge(origin_entity);
		    }else{
			// Very Bad.
			console.log('ERROR: attempt to clobber unknown entity');
		    }
		}
	    });

	    // Disolve the entire clique from graph, depending on edge
	    // to auto-disolve.
	    each(clique.all_nodes(), function(removable_cn, cni){
		//console.log('remove', cni , removable_cn.id());
		//console.log(anchor.remove_node(removable_cn.id(), true));
		anchor.remove_node(removable_cn.id(), true);
	    });
	}
    });

    ret = true;

    return ret;
};

/**
 * In addition to everything we did for {fold_evidence},
 * we're going to search for nodes that have enabled_by and/or
 * occurs_in (or any other specified relation) targets (that are
 * themselves leaves) fold them in to the contained subgraph item, and
 * remove them from the top-level graph.
 *
 * TODO: inferred individuals
 *
 * @param {Array} relation_list of relations (as strings) to scan for for collapsing
 * @param {Array} relation_reverse_list of relations (as strings) to scan for for collapsing in the opposite direction.
 * @returns {Boolean} if data was loaded
 */
noctua_graph.prototype.fold_go_noctua = function(relation_list,
						 relation_reverse_list){
    var anchor = this;

    // Start out with the evidence folded graph.
    var ret = anchor.fold_evidence();
    if( ! ret ){ return false; } // Early bail on bad upstream.

    // It is foldable if it is a root node (re: opposite of leaf--only
    // target) and if it only has the one child (no way out--re: collapsible ).
    function _foldable_p(node){
	var ret = false;
	// console.log("  " + dd(node.id()));
	if( anchor.is_root_node(node.id()) &&
	    ! node.subgraph() && // cannot fold if already folded into
	    anchor.get_child_nodes(node.id()).length === 1 ){
		// console.log("    Y root_p: " +
		// 	    anchor.is_root_node(node.id()) +
		// 	    ";  kids: " +
		// 	    anchor.get_child_nodes(node.id()).length);
		ret = true;
	    }else{
		// console.log("    N root_p: " +
		// 	    anchor.is_root_node(node.id()) +
		// 	    ";  kids: " +
		// 	    anchor.get_child_nodes(node.id()).length);
	    }
	return ret;
    }

    // It is reverse foldable if it is a leaf node (re: opposite of
    // root--only source) and if it only has the one child (no way
    // out--re: collapsible ).
    function _reverse_foldable_p(node){
	var ret = false;
	if( anchor.is_leaf_node(node.id()) &&
	    ! node.subgraph() && // cannot fold if already folded into
	    anchor.get_parent_nodes(node.id()).length === 1 ){
		//console.log("is foldable: " + node.id());
		ret = true;
	    }else{
		//console.log("not foldable: " + node.id());
	    }
	// console.log("  leaf_p: " + anchor.is_leaf_node(node.id()) +
	// 	    ";  parents: " + anchor.get_parent_nodes(node.id()).length);
	return ret;
    }

    // Okay, first scan all nodes for our pattern.
    // each(anchor.all_nodes(), function(pattern_seed_indv){
    // Temporarily enforce an ordering so we can debug/have consistent
    // results.
    var todo_nodes = anchor.all_nodes();
    todo_nodes = todo_nodes.sort(function(a,b){
	//console.log(a.id()+' vs. '+ b.id() +': '+a.id().localeCompare(b.id()));
	return a.id().localeCompare(b.id());
    });
    each(todo_nodes, function(pattern_seed_indv){
	//console.log( dd(pattern_seed_indv.id()) );

	var pattern_seed_id = pattern_seed_indv.id();

	// The possible base subgraph (seeding with current node--note
	// the clone so we don't have infinite recursion) we might
	// capture.
	var subgraph = anchor.create_graph();
	subgraph.add_node(pattern_seed_indv.clone());

	// Fold checking is independent of reverse or not.
	var fold_occurred_p = false;

	// Check a set of relations for completeness.
	var collapsable_relations = relation_list || [];
	each(collapsable_relations, function(relation){

	    var parents = anchor.get_parent_nodes(pattern_seed_id, relation);
	    each(parents, function(parent){

		if( _foldable_p(parent) ){
		    fold_occurred_p = true;

		    // Preserve it and its edge in the new subgraph.
		    var p = parent.clone();
		    // if( p.subgraph() ){
		    // 	console.log('  has SUB');
		    // }
		    subgraph.add_node(p);
		    var eta = anchor.get_edge(pattern_seed_id, p.id(), relation);
		    subgraph.add_edge(eta.clone());
		    // subgraph.report_state();

		    // Remove same from the original graph, edge will be
		    // destroyed in the halo.
		    anchor.remove_node(parent.id(), true);
		    // console.log('  *destroyed: ' + dd(parent.id()) );
		}
	    });
	});

	// ...and now the other way.
	var collapsable_reverse_relations = relation_reverse_list || [];
	each(collapsable_reverse_relations, function(relation){

	    var children = anchor.get_child_nodes(pattern_seed_id, relation);
	    each(children, function(child){

		if( _reverse_foldable_p(child) ){
		    fold_occurred_p = true;

		    // Preserve it and its edge in the new subgraph.
		    subgraph.add_node(child.clone());
		    subgraph.add_edge( // we know it's just one from above
			anchor.get_child_edges(pattern_seed_id,
					       relation)[0].clone());

		    // Remove same from the original graph, edge will
		    // be destroyed in the halo.
		    anchor.remove_node(child.id(), true);
		}
	    });
	});

	// A usable folding subgraph only occurred when the are more
	// than 1 node in it; i.e. we actually actually added things
	// to our local subgraph and removed them from the master
	// graph.
	if( fold_occurred_p ){
	    // console.log('slurpable subgraph ('+ subgraph.all_nodes().length +
	    // 		') for: ' + pattern_seed_id);
	    pattern_seed_indv.subgraph(subgraph);
	}

    });

    return ret;
};

/**
 * Essentially, undo anything that could be done in a folding
 * step--return the graph to its most expanded form.
 *
 * @param {Object} [incoming_graph] subgraph to unfold into the calling graph (default behaviour would be calling itself; only really used internally by this method for recursion)
 * @returns {Boolean} if unfolded (should always be true)
 */
noctua_graph.prototype.unfold = function(incoming_graph){
    var anchor = this;

    // If not a recursive, we will operate on ourselves.
    if( ! incoming_graph ){
    	incoming_graph = anchor;
    }

    // For any entity, remove its referenced individuals and re-add
    // them to the graph.
    function _unfold_subgraph(sub){

	// Restore to graph.
	// console.log('   unfold # (' + sub.all_nodes().length + ', ' +
	// 	    sub.all_edges().length + ')');
	each(sub.all_nodes(), function(node){
	    anchor.add_node(node);
	});
	each(sub.all_edges(), function(edge){
	    anchor.add_edge(edge);
	});
    }

    // Apply to all nodes.
    each(incoming_graph.all_nodes(), function(node){

	// Get references (ev).
	var ref_graphs = node.referenced_subgraphs();

	// Restore to graph.
	each(ref_graphs, function(sub){
	    _unfold_subgraph(sub);
	});

	// Remove references.
	node.referenced_subgraphs([]);

	// Now that they've been removed (to help prevent loops) try
	// and recur.
	each(ref_graphs, function(sub){
	    anchor.unfold(sub);
	});

	// Repeat with any absorbed subgraph.
	var asub = node.subgraph();
	if( asub ){
	    _unfold_subgraph(asub);
	    node.subgraph(null); // eliminate after it has been re-added
	    // Recur on any found subgraphs, safer since the elimination.
	    anchor.unfold(asub);
	}

    });

    // Apply to all edges.
    each(incoming_graph.all_edges(), function(edge){

	// Get references (ev).
	var ref_graphs = edge.referenced_subgraphs();

	// Restore to graph.
	each(ref_graphs, function(sub){
	    _unfold_subgraph(sub);
	});

	// Remove references.
	edge.referenced_subgraphs([]);

	// Now that they've been removed, try and recur (to help
	// prevent loops).
	each(ref_graphs, function(sub){
	    anchor.unfold(sub);
	});
    });

    // Revisit if we want something meaningful out of here.
    var retval = true;
    return retval;
};

/**
 * Provide a verbose report of the current state of the graph and
 * subgraphs. Writes using console.log; only to be used for debugging.
 *
 * @returns {null} just the facts
 */
noctua_graph.prototype.report_state = function(incoming_graph, indentation){
    var anchor = this;

    // If not a recursive, we will operate on ourselves.
    if( ! incoming_graph ){
    	incoming_graph = anchor;
    }

    // Start with no indentation.
    if( typeof(indentation) === 'undefined' ){
    	indentation = 0;
    }

    // Collect spacing for this indentation level of logging.
    var spacing = '';
    for( var i = 0; i < indentation; i++ ){
	spacing += '   ';
    }
    function ll(str){
	console.log(spacing + str);
    }
    function short(str){
	return str.substr(str.length - 16);
    }

    // Restore to graph.
    var gid = incoming_graph.id() || '(anonymous graph)';
    ll(gid);
    ll(' entities # (' + incoming_graph.all_nodes().length +
       ', ' + incoming_graph.all_edges().length + ')');

    // Show node information, arbitrary, but fixed, order.
    each(incoming_graph.all_nodes().sort(function(a,b){
	if( a.id() > b.id() ){
	    return 1;
	}else if( a.id() < b.id() ){
	    return -1;
	}
	return 0;
    }), function(node){
	ll(' node: ' + dd(node.id()));

	// Subgraph.
	var subgraph = node.subgraph();
	if( subgraph ){
	    ll('  subgraph: ');
	    anchor.report_state(subgraph, indentation +1);
	}

	// Refs.
	var ref_graphs = node.referenced_subgraphs();
	if( ref_graphs.length > 0 ){
	    ll('  references: ');
	    each(ref_graphs, function(sub){
		anchor.report_state(sub, (indentation +1));
	    });
	}
    });

    // Show edge information, arbitrary, but fixed, order.
    each(incoming_graph.all_edges().sort(function(a,b){
	if( a.id() > b.id() ){
	    return 1;
	}else if( a.id() < b.id() ){
	    return -1;
	}
	return 0;
    }), function(edge){
	var s = dd(edge.subject_id());
	var o = dd(edge.object_id());
	var p = dd(edge.predicate_id());
	//ll(' edge: ' + edge.id());
	ll(' edge: ' + s + ', ' + o + ': ' + p);

	// Refs.
	var ref_graphs = edge.referenced_subgraphs();
	if( ref_graphs.length > 0 ){
	    ll('  references: ');
	    each(ref_graphs, function(sub){
		anchor.report_state(sub, (indentation +1));
	    });
	}
    });

    if( ! us.isEmpty(incoming_graph._os_table) ){
	console.log(spacing + 'OS:', incoming_graph._os_table);
    }
    if( ! us.isEmpty(incoming_graph._so_table) ){
	console.log(spacing + 'SO:', incoming_graph._so_table);
    }
    if( ! us.isEmpty(incoming_graph._predicates) ){
	console.log(spacing + 'PRED:', incoming_graph._predicates);
    }

    return null;
};

///
/// Node subclass and overrides.
///

var bbop_node = bbop_model.node;
/**
 * Sublcass of bbop-graph.node for use with Noctua ideas and concepts.
 *
 * @constructor
 * @see module:bbop-graph
 * @alias node
 * @param {String} [in_id] - new id; otherwise new unique generated
 * @param {String} [in_label] - node "label"
 * @param {Array} [in_types] - list of Objects or strings--anything that can be parsed by class_expression
 * @param {Array} [in_inferred_types] - list of Objects or strings--anything that can be parsed by class_expression
 * @returns {this}
 */
function noctua_node(in_id, in_label, in_types, in_inferred_types, in_inferred_types_with_all){
    bbop_node.call(this, in_id, in_label);
    this._is_a = 'bbop-graph-noctua.node';
    var anchor = this;

    // Let's make this an OWL-like world.
    this._types = [];
    this._id2type = {}; // contains map to both types and inferred types
    this._inferred_types = [];
    this._inferred_types_with_all = [];
    this._annotations = [];
    this._referenced_subgraphs = [];
    this._embedded_subgraph = null;

    // Incoming ID or generate ourselves.
    if( typeof(in_id) === 'undefined' ){
	this._id = bbop.uuid();
    }else{
	this._id = in_id;
    }

    // Roll in any types that we may have coming in.
    if( us.isArray(in_types) ){
	each(in_types, function(in_type){
	    var new_type = new class_expression(in_type);
	    anchor._id2type[new_type.id()] = new_type;
	    anchor._types.push(new class_expression(in_type));
	});
    }
    // Same with inferred types.
    if( us.isArray(in_inferred_types) ){
	each(in_inferred_types, function(in_inferred_type){
	    var new_type = new class_expression(in_inferred_type);
	    anchor._id2type[new_type.id()] = new_type;
	    anchor._inferred_types.push(new class_expression(in_inferred_type));
	});
    }
    // Same with inferred types with all.
    if( us.isArray(in_inferred_types_with_all) ){
	each(in_inferred_types_with_all, function(in_inferred_type_with_all){
	    var new_type = new class_expression(in_inferred_type_with_all);
	    anchor._id2type[new_type.id()] = new_type;
	    anchor._inferred_types_with_all.push(new class_expression(in_inferred_type_with_all));
	});
    }
}
bbop.extend(noctua_node, bbop_node);

/**
 * Get a fresh new copy of the current node (using bbop.clone for
 * metadata object).
 *
 * @returns {node} node
 */
noctua_node.prototype.clone = function(){
    var anchor = this;

    // Fresh.
    var new_clone = new noctua_node(anchor.id(), anchor.label(), anchor.types(),
				    anchor.inferred_types(),
				    anchor.inferred_types_with_all());

    // Base class stuff.
    new_clone.type(this.type());
    new_clone.metadata(bbop.clone(this.metadata()));

    // Transfer over the new goodies, starting with annotations and
    // referenced individuals.
    each(anchor._annotations, function(annotation){
	new_clone._annotations.push(annotation.clone());
    });
    each(anchor._referenced_subgraphs, function(sub){
	new_clone._referenced_subgraphs.push(sub.clone());
    });

    // Embedded subgraph.
    if( anchor._embedded_subgraph ){
	new_clone._embedded_subgraph = anchor._embedded_subgraph.clone();
    }else{
	new_clone._embedded_subgraph = null;
    }

    return new_clone;
};

/**
 * Get current types; replace current types.
 *
 * Parameters:
 * @returns {Array} array of types
 */
noctua_node.prototype.types = function(){
    var anchor = this;
    return this._types;
};

/**
 * Get current inferred types; replace current inferred types.
 *
 * Parameters:
 * @returns {Array} array of types
 */
noctua_node.prototype.inferred_types = function(){
    var anchor = this;
    return this._inferred_types;
};

/**
 * Get current inferred types with all; replace current inferred types.
 *
 * Parameters:
 * @returns {Array} array of types
 */
noctua_node.prototype.inferred_types_with_all = function(){
    var anchor = this;
    return this._inferred_types_with_all;
};

/**
 * Add types to current types.
 *
 * Parameters:
 * @param {Object} in_types - raw JSON type objects
 * @param {Boolean} inferred_p - whether or not the argument types are inferred
 * @returns {Boolean} t|f
 */
noctua_node.prototype.add_types = function(in_types, inferred_p){
    var anchor = this;
    var inf_p = inferred_p || false;

    var ret = false;

    if( us.isArray(in_types) ){
	each(in_types, function(in_type){
	    var new_type = new class_expression(in_type);
	    anchor._id2type[new_type.id()] = new_type;
	    if( ! inferred_p ){
		anchor._types.push(new_type);
	    }else{
		anchor._inferred_types.push(new_type);
		// And with_all, as it is a superset.
		anchor._inferred_types_with_all.push(new_type);
	    }

	    ret = true; // return true if did something
	});
    }
    return ret;
};

/**
 * If extant, get the type by its unique identifier. This works for
 * both inferred and non-inferred types generally.
 *
 * @param {String} type_id - type id
 * @returns {type|null} type or null
 */
noctua_node.prototype.get_type_by_id = function(type_id){
    var anchor = this;

    var ret = null;
    ret = anchor._id2type[type_id];

    return ret;
};

/**
 * Essentially, get all of the "uneditable" direct inferred types from
 * the reasoner that are not duplicated in the regular (editable)
 * types listing.
 *
 * Returns originals.
 *
 * Note: the matching here is awful and should be redone (going by
 * very lossy string rep).
 *
 * @returns {Array} of {class_expression}
 */
noctua_node.prototype.get_unique_inferred_types = function(){
    var anchor = this;

    var ret = [];

    // Create a checkable representation of the types.
    var type_cache = {};
    each(anchor.types(), function(t){
	type_cache[t.signature()] = true;
    });

    // Do a lookup.
    each(anchor.inferred_types(), function(t){
	if( ! type_cache[t.signature()] ){
	    ret.push(t);
	}
    });

    return ret;
};

/**
 * Essentially, get all (the complete closure in this case) of the
 * "uneditable" inferred types from the reasoner that are not
 * duplicated in the regular (editable) types listing.
 *
 * Returns originals.
 *
 * Note: the matching here is awful and should be redone (going by
 * very lossy string rep).
 *
 * @returns {Array} of {class_expression}
 */
noctua_node.prototype.get_unique_inferred_types_with_all = function(){
    var anchor = this;

    var ret = [];

    // Create a checkable representation of the types.
    var type_cache = {};
    each(anchor.types(), function(t){
	type_cache[t.signature()] = true;
    });

    // Do a lookup.
    each(anchor.inferred_types_with_all(), function(t){
	if( ! type_cache[t.signature()] ){
	    ret.push(t);
	}
    });

    return ret;
};

/**
 * Get/set the "contained" subgraph. This subgraph is still considered
 * to be part of the graph, but is "hidden" under this node for most
 * use cases except serialization.
 *
 * To put it another way, unless you specifically load this with a
 * specialized loader, it will remain unpopulated. During
 * serialization, it should be recursively walked and dumped.
 *
 * @param {graph|null} [subgraph] - the subgraph to "hide" inside this individual in the graph, or null to reset it
 * @returns {graph|null} contained subgraph
 */
noctua_node.prototype.subgraph = function(subgraph){
    if( typeof(subgraph) === 'undefined' ){
	// Just return current state.
    }else if( subgraph === null ){
	// Reset state (and return).
	this._embedded_subgraph = null;
    }else if(bbop.what_is(subgraph) === 'bbop-graph-noctua.graph'){
	// Update state (and return).
	this._embedded_subgraph = subgraph;
    }
    return this._embedded_subgraph;
};

///
/// Edge subclass and overrides.
///

var bbop_edge = bbop_model.edge;
/**
 * Sublcass of bbop-graph.edge for use with Noctua ideas and concepts.
 *
 * @constructor
 * @see module:bbop-graph
 * @alias edge
 * @param {String} subject - required subject id
 * @param {String} object - required object id
 * @param {String} [predicate] - preidcate id; if not provided, will use defined default (you probably want to provide one--explicit is better)
 * @returns {this}
 */
function noctua_edge(subject, object, predicate){
    bbop_edge.call(this, subject, object, predicate);
    this._is_a = 'bbop-graph-noctua.edge';

    // Edges are not completely anonymous in this world.
    this._id = bbop.uuid();
    this._predicate_label = null;

    this._annotations = [];
    this._referenced_subgraphs = [];
}
bbop.extend(noctua_edge, bbop_edge);

/**
 * Get a fresh new copy of the current edge--no shared structure.
 *
 * @returns {edge} - new copy of edge
 */
noctua_edge.prototype.clone = function(){
    var anchor = this;

    // Fresh.
    var new_clone = new noctua_edge(anchor.subject_id(),
				    anchor.object_id(),
				    anchor.predicate_id());

    // Same id.
    new_clone._id = anchor._id;
    new_clone._predicate_label = anchor._predicate_label;

    // Base class stuff.
    new_clone.default_predicate = anchor.default_predicate;
    new_clone.type(anchor.type());
    new_clone.metadata(bbop.clone(anchor.metadata()));

    // Transfer over the new goodies.
    each(anchor._annotations, function(annotation){
	new_clone._annotations.push(annotation.clone());
    });
    each(anchor._referenced_subgraphs, function(ind){
	new_clone._referenced_subgraphs.push(ind.clone());
    });

    return new_clone;
};

/**
 * Access to the immutable "id".
 *
 * @returns {String} string
 */
noctua_edge.prototype.id = function(){
    return this._id;
 };

/**
 * Get/set "source" of edge.
 *
 * @deprecated
 * @param {String} [value] - string
 * @returns {String} string
 */
noctua_edge.prototype.source = function(value){
    if(value){ this._subject_id = value; }
    return this._subject_id;
};

/**
 * Get/set "target" of edge.
 *
 * @deprecated
 * @param {String} [value] - string
 * @returns {String} string
 */
noctua_edge.prototype.target = function(value){
    if(value){ this._object_id = value; }
    return this._object_id;
};

/**
 * Get/set "relation" of edge.
 *
 * @deprecated
 * @param {String} [value] - string
 * @returns {String} string
 */
noctua_edge.prototype.relation = function(value){
    if(value){ this._predicate_id = value; }
    return this._predicate_id;
};

/**
 * Access to the mutable "label" for the edge.
 *
 * @param {String} [value] - lbl
 * @returns {String|null} string
 */
noctua_edge.prototype.label = function(lbl){

    if( lbl === null || typeof(lbl) === 'string' ){
	this._predicate_label = lbl;
    }
    return this._predicate_label;
 };

// Add generic bulk annotation operations to: graph, edge, and node.
each([noctua_graph, noctua_node, noctua_edge], function(constructr){
    constructr.prototype.annotations = _annotations;
    constructr.prototype.add_annotation = _add_annotation;
    constructr.prototype.get_annotations_by_filter = _get_annotations_by_filter;
    constructr.prototype.get_annotations_by_key = _get_annotations_by_key;
    constructr.prototype.get_annotation_by_id = _get_annotation_by_id;
});

// Add generic evidence (referenced individuals) operations to: edge
// and node.
each([noctua_node, noctua_edge], function(constructr){
    constructr.prototype.referenced_subgraphs =
	_referenced_subgraphs;
    constructr.prototype.add_referenced_subgraph =
	_add_referenced_subgraph;
    constructr.prototype.get_referenced_subgraphs_by_filter =
	_get_referenced_subgraphs_by_filter;
    constructr.prototype.get_referenced_subgraph_by_id =
	_get_referenced_subgraph_by_id;
    constructr.prototype.get_referenced_subgraph_profiles =
	_get_referenced_subgraph_profiles;
    constructr.prototype.get_basic_evidence =
	_get_basic_evidence;
});

///
/// Exportable body.
///

module.exports = {

    annotation: annotation,
    node: noctua_node,
    edge: noctua_edge,
    graph: noctua_graph

};