/** 
 * Class expressions.
 * 
 * A handling library for OWL-style class expressions in JavaScript.
 * 
 * The idea here is to have a generic class expression class that can
 * be used at all levels of communication an display (instead of the
 * previous major/minor models).
 *
 * This is a full-bodied implementation of all the different aspects
 * that we need to capture for type class expressions: information
 * capture from JSON, on-the-fly creations, and display
 * properties. These used to be separate behaviors, but with the
 * client taking over more responsibility from Minerva, a more robust
 * and testable soluton was needed.
 * 
 * Types can be: class ids and the expressions: SVF, union, and
 * intersection. Of the latter group, all are nestable.
 * 
 * Categories is a graphical/UI distinction. They can be: instance_of,
 * <relation id>, union, and intersection.
 * 
 * @module class-expression
 */

var us = require('underscore');
var each = us.each;
var keys = us.keys;
var bbop = require('bbop-core');
var what_is = bbop.what_is;

/**
 * Core constructor.
 *
 * The argument "in_type" may be:
 *  - a class id (string)
 *  - a JSON blob as described from Minerva
 *  - another <class_expression>
 *  - null (user will load or interactively create one)
 *
 * @constructor
 * @param {String|Object|class_expression|null} - the raw type description (see above)
 */
class_expression = function(in_type){
    this._is_a = 'class_expression';

    var anchor = this;

    ///
    /// Initialize.
    ///

    // in_type is always a JSON object, trivial catch of attempt to
    // use just a string as a class identifier.
    if( in_type ){
    	if( what_is(in_type) == 'class_expression' ){
    	    // Unfold and re-parse (takes some properties of new
    	    // host).
    	    in_type = in_type.structure();
    	}else if( what_is(in_type) == 'object' ){
	    // Fine as it is.
    	}else if( what_is(in_type) == 'string' ){
	    // Convert to a safe representation.
	    in_type = {
		'type': 'class',
		'id': in_type,
		'label': in_type
	    };
    	}
    }

    // Every single one is a precious snowflake (which is necessary
    // for managing some of the aspects of the UI for some use cases).
    this._id = bbop.uuid();

    // Derived property defaults.
    this._type = null;
    this._category = 'unknown';
    this._class_id = null;
    this._class_label = null;
    this._property_id = null;
    this._property_label = null;
    // Recursive elements.
    this._frame = [];

    // 
    this._raw_type = in_type;
    if( in_type ){
	anchor.parse(in_type);
    }
};

/**
 * Get the unique ID of this class expression.
 * 
 * @returns {String} string
 */
class_expression.prototype.id = function(){
    return this._id;
};

/** 
 * If the type has a recursive frame.
 *
 * @returns {Boolean} true or false
 */
class_expression.prototype.nested_p = function(){
    var retval = false;
    if( this._frame.length > 0 ){
	retval = true;
    }
    return retval;
};

/**
 * A cheap way of identifying if two class_expressions are the same.
 * This essentially returns a string of the main attributes of a type.
 * It is meant to be semi-unique and collide with dupe inferences.
 *
 * BUG/WARNING: At this point, colliding signatures should mean a
 * dupe, but non-colliding signatures does *not* guarantee that they
 * are not dupes (think different intersection orderings).
 *
 * @returns {String} string
 */
class_expression.prototype.signature = function(){
    var anchor = this;

    var sig = [];

    // The easy ones.
    sig.push(anchor.category() || '');
    sig.push(anchor.type() || '');
    sig.push(anchor.class_id() || '');
    sig.push(anchor.property_id() || '');

    // And now recursively on frames.
    if( anchor.frame() ){
	each(anchor.frame(), function(f){
	    sig.push(f.signature() || '');
	});
    }

    return sig.join('_');
};

/** 
 * Try to put an instance type into some kind of rendering category.
 *
 * @returns {String} string (default 'unknown')
 */
class_expression.prototype.category = function(){
    return this._category;
};

/** 
 * The "type" of the type.
 *
 * @returns {String|null} string or null
 */
class_expression.prototype.type = function(){
    return this._type;
};

/** 
 * The class expression when we are dealing with SVF.
 *
 * @returns {String|null} type or null
 */
class_expression.prototype.svf_class_expression = function(){
    var ret = null;
    if( this.type() == 'svf' ){
	ret = this._frame[0];
    }    
    return ret; 
};

/** 
 * The class expression when we are dealing with a ComplementOf.
 *
 * @returns {String|null} type or null
 */
class_expression.prototype.complement_class_expression = function(){
    var ret = null;
    if( this.type() == 'complement' ){
	ret = this._frame[0];
    }    
    return ret; 
};

/** 
 * If the type has a recursive frame, a list of the cls expr it
 * contains.
 *
 * @returns {Array} list of {class_expression}
 */
class_expression.prototype.frame = function(){
    return this._frame;
};

/** 
 * The considered class id.
 *
 * @returns {String|null} string or null
 */
class_expression.prototype.class_id = function(){
    return this._class_id;
};

/** 
 * The considered class label, defaults to ID if not found.
 *
 * @returns {String|null} string or null
 */
class_expression.prototype.class_label = function(){
    return this._class_label;
};

/** 
 * The considered class property id.
 * Not defined for 'class' types.
 *
 * @returns {String|null} string or null
 */
class_expression.prototype.property_id = function(){
    return this._property_id;
};

/** 
 * The considered class property label.
 * Not defined for 'class' types.
 *
 * @returns {String|null} string or null
 */
class_expression.prototype.property_label = function(){
    return this._property_label;
};

/**
 * Parse a JSON blob into the current instance, clobbering anything in
 * there, except id.
 *
 * @params {Object} in_type - conformant JSON object
 * @returns {this} self
 */
class_expression.prototype.parse = function(in_type){

    var anchor = this;

    // Helper.
    function _decide_type(type){
	var rettype = null;
 
	// Easiest case.
	var t = type['type'] || null;
	if( t == 'class' ){
	    rettype = 'class';
	}else if( t == 'union' ){
	    rettype = 'union';
	}else if( t == 'intersection' ){
	    rettype = 'intersection';
	}else if( t == 'svf' ){
	    rettype = 'svf';
	}else if( t == 'complement' ){
	    rettype = 'complement';
	}else{
	    // No idea...
	}

	return rettype;
    }

    // Define the category, and build up an instant picture of what we
    // need to know about the property.
    var t = _decide_type(in_type);
    if( t == 'class' ){

	// Easiest to extract.
	this._type = t;
	this._category = 'instance_of';
	this._class_id = in_type['id'];
	this._class_label = in_type['label'] || this._class_id;
	// No related properties.
	
    }else if( t == 'union' || t == 'intersection' ){ // conjunctions

	// These are simply recursive.
	this._type = t;
	this._category = t;

	// Load stuff into the frame.
	this._frame = [];
	var f_set = in_type['expressions'] || [];
	each(f_set, function(f_type){
	    anchor._frame.push(new class_expression(f_type));
	}); 
    }else if( t == 'svf' ){ // SVF
	    
	// We're then dealing with an SVF: a property plus a class
	// expression. We are expecting a "restriction", although we
	// don't really do anything with that information (maybe
	// later).
	this._type = t;
	// Extract the property information
	this._category = in_type['property']['id'];
	this._property_id = in_type['property']['id'];
	this._property_label =
	    in_type['property']['label'] || this._property_id;	    

	// Okay, let's recur down the class expression. It should just
	// be one, but we'll just reuse the frame. Access should be
	// though svf_class_expression().
	var f_type = in_type['filler'];
	this._frame = [new class_expression(f_type)];
    }else if( t == 'complement' ){ // ComplementOf
	    
	// We're then dealing with a ComplementOf. Not too bad.
	this._type = t;
	this._category = t;

	// Okay, let's recur down the class expression. It should just
	// be one, but we'll just reuse the frame. Access should be
	// though complement_class_expression().
	var f2_type = in_type['filler'];
	this._frame = [new class_expression(f2_type)];
    }else{
	// Should not be possible, so let's stop it here.
	//console.log('unknown type :', in_type);
	throw new Error('unknown type leaked in');
    }

    return anchor;
};

/**
 * Parse a JSON blob into the current instance, clobbering anything in
 * there, except id.
 *
 * @params {String} in_type - string
 * @returns {this} self
 */
class_expression.prototype.as_class = function(in_type){

    if( in_type ){
	var ce = new class_expression(in_type);
	this.parse(ce.structure());
    }

    return this;
};

/** 
 * Convert a null class_expression into an arbitrary SVF.
 *
 * @params {String} property_id - string
 * @params {String|class_expression} class_expr - ID string (e.g. GO:0022008) or <class_expression>
 * @returns {this} self
 */
class_expression.prototype.as_svf = function(property_id, class_expr){

    // Cheap our way into this--can be almost anything.
    var cxpr = new class_expression(class_expr);

    // Our list of values must be defined if we go this way.
    var expression = {
	'type': 'svf',
	'property': {
	    'type': "property",
	    'id': property_id
	},
	'filler': cxpr.structure()
    };

    this.parse(expression);

    return this;
};

/** 
 * Convert a null class_expression into an arbitrary complement.
 *
 * @params {String|class_expression} class_expr - ID string (e.g. GO:0022008) or <class_expression>
 * @returns {this} self
 */
class_expression.prototype.as_complement = function(class_expr){

    // Cheap our way into this--can be almost anything.
    var cxpr = new class_expression(class_expr);

    // Our list of values must be defined if we go this way.
    var expression = {
	'type': 'complement',
	'filler': cxpr.structure()
    };

    this.parse(expression);

    return this;
};

/**
 * Convert a null class_expression into a set of class expressions.
 *
 * @params {String} set_type - 'intersection' || 'union'
 * @params {Array} set_list - list of ID strings of <class_expressions>
 * @returns {this} self
 */
class_expression.prototype.as_set = function(
    set_type, set_list){

    // We do allow empties.
    if( ! set_list ){ set_list = []; }

    if( set_type == 'union' || set_type == 'intersection' ){

	// Work into a viable argument.
	var set = [];
	each(set_list, function(item){
	    var cexpr = new class_expression(item);
	    set.push(cexpr.structure());
	}); 

	// A little massaging is necessary to get it into the correct
	// format here.
	var fset = set_type;
	var parsable = {};
	parsable['type'] = fset;
	parsable['expressions'] = set;
	this.parse(parsable);
    }

    return this;
};

/** 
 * Hm. Essentially dump out the information contained within into a
 * JSON object that is appropriate for consumption my Minerva
 * requests.
 *
 * @returns {Object} JSON object
 */
class_expression.prototype.structure = function(){

    var anchor = this;

    // We'll return this.
    var expression = {};
    
    // Extract type.
    var t = anchor.type(); 
    if( t == 'class' ){ // trivial

	expression['type'] = 'class';
	expression['id'] = anchor.class_id();

    }else if( t == 'svf' ){ // SVF
	
	// Easy part of SVF.
	expression['type'] = 'svf';
	expression['property'] = {
	    'type': 'property',
	    'id': anchor.property_id()
	};
	
	// Recur for someValuesFrom class expression.
	var svfce = anchor.svf_class_expression();
	var st = svfce.type();
	expression['filler'] = svfce.structure();
	
    }else if( t == 'complement' ){ // ComplementOf
	
	expression['type'] = 'complement';
	
	// Recur for someValuesFrom class expression.
	var cce = anchor.complement_class_expression();
	var ct = cce.type();
	expression['filler'] = cce.structure();
	
    }else if( t == 'union' || t == 'intersection' ){ // compositions
	
	// Recursively add all of the types in the frame.
	var ecache = [];
	var frame = anchor.frame();
	each(frame, function(ftype){
	    ecache.push(ftype.structure());
	});

	// Correct structure.
	expression['type'] = t;
	expression['expressions'] = ecache;
	
    }else{
	throw new Error('unknown type in request processing: ' + t);
    }
    
    return expression;
};

/** 
 * An attempt to have a simple attempt at a string representation for
 * a class expression.
 *
 * @param {String} front_str - (optional) start the output string with (default '')
 * @param {String} back_str - (optional) end the output string with (default '')
 * @returns {String} simple string rep
 */
class_expression.prototype.to_string = function(front_str, back_str){
    var anchor = this;

    function _inner_lbl(ce){
	var inner_lbl = '???';

	var cetype = ce.type();
	if( cetype == 'class' ){
	    inner_lbl = ce.class_label();
	}else if( cetype == 'union' || cetype == 'intersection' ){
	    var cef = ce.frame();
	    inner_lbl = cetype + '[' + cef.length + ']';
	}else if( cetype == 'complement' ){
	    inner_lbl = '[!]';
	}else if( cetype == 'svf' ){
	    inner_lbl = '[SVF]';
	}else{
	    inner_lbl = '???';
	}

	return inner_lbl;
    }

    var ret = '[???]';
    
    var t = anchor.type();
    var f = anchor.frame();

    if( t == 'class' ){
	ret = anchor.class_label();
    }else if( t == 'union' || t == 'intersection' ){
	ret = t + '[' + f.length + ']';
    }else if( t == 'complement' ){
	ret = '!' + 
	    //'[' + anchor.to_string(anchor.complement_class_expression()) + ']';
	    '[' + _inner_lbl(anchor.complement_class_expression()) + ']';
    }else if( t == 'svf' ){
	// SVF a little harder.
	var ctype = anchor.property_label();

	// Probe it a bit.
	var ce = anchor.svf_class_expression();
	ret = 'svf[' + ctype + '](' + _inner_lbl(ce) + ')';
    }else{
	ret = '???';
    }

    // A little special "hi" for inferred types, or something.
    if( front_str && typeof(front_str) === 'string' ){
	ret = front_str + ret;
    }
    if( back_str && typeof(back_str) === 'string' ){
	ret = ret + back_str;
    }

    return ret;    
};

///
/// "Static" functions in package.
///

/** 
 * "Static" function that creates an intersection from a list of
 * whatever.
 *
 * @param {Array} list - list of conformant whatever
 * @returns {class_expression} object
 */
class_expression.intersection = function(list){
    var ce = new class_expression();
    ce.as_set('intersection', list);
    return ce;
};

/** 
 * "Static" function that creates a union from a list of whatever.
 *
 * @param {Array} list - list of conformant whatever
 * @returns {class_expression} object
 */
class_expression.union = function(list){
    var ce = new class_expression();
    ce.as_set('union', list);
    return ce;
};

/** 
 * "Static" function that creates a SomeValueFrom from a property ID
 * and a class_expression (or string or whatever).
 *
 * @param {String} prop_id - ID
 * @param {class_expression|String} cls_expr - thing
 * @returns {class_expression} object
 */
class_expression.svf = function(prop_id, cls_expr){
    var ce = new class_expression();
    ce.as_svf(prop_id, cls_expr);
    return ce;
};

/** 
 * "Static" function that creates the complement of a given class
 * expression.
 *
 * @param {class_expression|String} cls_expr - thing
 * @returns {class_expression} object
 */
class_expression.complement = function(cls_expr){
    var ce = new class_expression();
    ce.as_complement(cls_expr);
    return ce;
};

/** 
 * "Static" function that creates a class_expression from a class ID.
 *
 * @param {String} id - string id
 * @returns {class_expression} object
 */
class_expression.cls = function(id){
    var ce = new class_expression();
    ce.as_class(id);
    return ce;
};

// Exportable body.
module.exports = class_expression;