/**
 * @fileoverview
 * This script defines a widget for fetching and displaying images from www.fly-ted.org.
 * @author <a href="http://purl.org/net/aliman">Alistair Miles</a>, Graham Klyne, Jun Zhao
 * @version $Revision:538 $ on $Date: 2008-08-27 09:08:41 +0100 (Wed, 27 Aug 2008) $ by $Author: aliman $
 * @requires YAHOO.util.Connect
 * @requires flykit
 * @requires flykit.bdgp.Service
 * @requires flykit.flybase.Gene
 * TODO license terms
 */
 
// create a namespace if not already defined
flykit.namespace("flykit.bdgp");


/*
 * --------------------------------------------------
 * WIDGET
 * --------------------------------------------------
 */


/**
 * Create a widget.
 * @class
 * A widget for displaying in situ images from BDGP.
 * @constructor
 * @param {flykit.bdgp.Service} service the service to use to fetch data
 * @param {flykit.bdgp.DefaultRenderer} renderer the renderer to use
 */
flykit.bdgp.ImageWidget = function( service, renderer ) {
	try {
    	// do initialisation
    	this._init(service, renderer);
    }catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget", error);
    }

};


/**
 * @private
 * @type flykit.bdgp.Controller
 */
flykit.bdgp.ImageWidget.prototype._controller = null;

	
/**
 * @private
 * Initialise the widget.
 * @param {flykit.bdgp.Service} service the service to use to fetch data
 * @param {flykit.bdgp.DefaultRenderer} renderer the renderer to use
 */
flykit.bdgp.ImageWidget.prototype._init = function( service, renderer ) {
	try {
		// create a model
		var model = new flykit.mvcutils.GenericModel2();
		model.setDefinition(flykit.bdgp.ImageWidget.modelDefinition);
		
		// instantiate the controller
		this._controller = new flykit.bdgp.ImageWidget.Controller(model, service, this);
		
		// connect the renderer to the model
		renderer.connect(model);
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.prototype._init", error);
    }
	
};


/**
 * Find images by any label of a gene product.
 * @param {String} geneLabel any gene product label
 */
flykit.bdgp.ImageWidget.prototype.findImagesByAnyGeneLabel = function( geneLabel ) {
	try {
		// pass through to controller
		this._controller.findImagesByAnyGeneLabel(geneLabel);
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.prototype.findImagesByAnyGeneLabel", error);
    }
	
};


/**
 * Find images by the given gene.
 * @param {flykit.flybase.Gene} gene
 */
flykit.bdgp.ImageWidget.prototype.findImagesByGene = function( gene ) {
	try {
		// pass through to controller
		this._controller.findImagesByGene(gene);
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.prototype.findImagesByGene", error);
    }
	
}


/*
 * --------------------------------------------------
 * CONTROLLER
 * --------------------------------------------------
 */


/**
 * Create a controller.
 * @class
 * A controller class for the BDGP image widget internal MVC.
 * @constructor
 * @param {flykit.mvcutils.GenericModel2} model the model to store widget state data
 * @param {flykit.bdgp.Service} service the service to use to fetch data
 * @param {flykit.bdgp.ImageWidget} widget the widget to control
 */

flykit.bdgp.ImageWidget.Controller = function( model, service, widget ) {
	
	var that = this;
	
	/**
	 * @private
	 */
	this._model = model;
	
	/**
	 * @private
	 */
	this._service = service;
	
	/**
	 * @private
	 */
	this._parent = widget;

    /**
     * @private
     * Success case callback for find images operations.
     * @param {Array<flykit.bdgp.Image>} images 
     */
	this._findImagesSuccess = function (images){
		
		flykit.info("request success");
		
		// set the results
		that._model.set("RESULTS", images);
		
		// set the state
		that._model.set("STATE", "READY");
		
	};
	
	/**
	 * @private
	 * Failure case callback for the find images operations.
	 * @param response HTTP response object (YUI)
	 */
	this._findImagesFailure = function (response) {
		
		flykit.err("request failed, status "+response.status+" "+response.statusText);
		
		// set an error message
		var msg = "There was an error retrieving data from BDGP, see the logs for more info. The server may be busy or down, please try again later. If this message persists, please contact the Image Bioinformatics Research Group at bioimage@mail.ontonet.org.";		
		that._model.set("ERRORMESSAGE", msg);

		// set the state
		that._model.set("STATE", "SERVERERROR");
			
	}
	
};


/**
 * Find images by any gene product label
 * @param {String} query any gene product label
 */
flykit.bdgp.ImageWidget.Controller.prototype.findImagesByAnyGeneLabel = function (query){
	try {
		// pass through to private implementation
		this._findImagesByAnyGeneLabel(query, this._findImagesSuccess, this._findImagesFailure);
	
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.Controller.prototype.findImagesByAnyGeneLabel", error);
    }
};


/**
 * @private
 * Private implementation of find images operation, with callback injection to enable testing.
 * @param {String} geneLabel any gene product label
 * @param {Function} success success case callback
 * @param {Function} failure failure case callback
 */
flykit.bdgp.ImageWidget.Controller.prototype._findImagesByAnyGeneLabel = function(geneLabel, success, failure) {
	try {
		flykit.info("flykit.bdgp.ImageWidget.Controller._findImagesByAnyGeneLabel :: request: "+geneLabel);
		
		// set the model pending
		this._model.set("STATE", "PENDING");
		
		// set the query property
		this._model.set("QUERY", geneLabel);
		
		this._model.set("RESULTS", null);
		
		// kick off the request
		this._service.findImagesByAnyGeneLabel(geneLabel, success, failure);
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.Controller.prototype._findImagesByAnyGeneLabel", error);
    }
};


/**
 * Find images by a given gene.
 * @param {flykit.flybase.Gene} gene 
 */
flykit.bdgp.ImageWidget.Controller.prototype.findImagesByGene = function( gene ) {
	try {
		// pass through to private implementation
		this._findImagesByGene(gene, this._findImagesSuccess, this._findImagesFailure);
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.Controller.prototype.findImagesByGene", error);
    }
	
};


/**
 * @private
 * Private implementation of find images operation, with callback injection to enable testing.
 * @param {flykit.flybase.Gene} gene 
 * @param {Function} success success case callback
 * @param {Function} failure failure case callback
 */
flykit.bdgp.ImageWidget.Controller.prototype._findImagesByGene = function( gene, success, failure ) {
	try {
		flykit.info("flykit.flyted.ImageWidget.Controller._findImagesByGene :: request: "+gene.flybaseID);
		
		// set the model pending
		this._model.set("STATE", "PENDING");
		
		// set the query property
		this._model.set("QUERY", gene);

        this._model.set("RESULTS", null);
		
		// kick off the request
		this._service.findImagesByAnyGeneLabel(gene.flybaseID, success, failure);
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.Controller.prototype._findImagesByGene", error);
    }
	
};


/*
 * --------------------------------------------------
 * MODEL DEFINITION
 * --------------------------------------------------
 */


/**
 * Widget MVC model definition.
 */
flykit.bdgp.ImageWidget.modelDefinition = {
	properties : [ "STATE", "RESULTS", "QUERY", "ERRORMESSAGE" ],
	
	values : {
		"STATE" : [ "PENDING", "READY", "SERVERERROR", "UNEXPECTEDERROR" ]
	},
	
	initialize : function( data ) {
		data["STATE"] = "READY";
		data["RESULTS"] = null;
		data["QUERY"] = null;
		data["ERRORMESSAGE"] = null;
	}
}


/*
 * --------------------------------------------------
 * DEFAULT RENDERER
 * --------------------------------------------------
 */


/**
 * Create a default renderer for the widget.
 * @class
 * A default renderer for the BDGP image widget.
 */
flykit.bdgp.ImageWidget.DefaultRenderer = function() {};


/**
 * @private
 * @type Element
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._canvas = null;

/**
 * @private
 * @type Element
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._pendingPane = null;

/**
 * @private
 * @type Element
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._resultsPane = null;

/**
 * @private
 * @type Element
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._resultsSummaryPane = null;

/**
 * @private
 * @type Element
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._messagePane = null;


/**
 * Set the element (canvas) to which this renderer will apply and initialise it.
 * @param {Element} canvas
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype.setCanvas = function( canvas ) {
	
	this._canvas = canvas;
	this._initCanvas();
	
}


/**
 * @private
 * Initialise the canvas.
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._initCanvas = function() {
 	try {
	    YAHOO.util.Dom.addClass(this._canvas, "bdgpImageWidget");
	    
	    // set up the pending pane
	    this._pendingPane = document.createElement("p");
	    this._pendingPane.innerHTML = "pending...";
	    this._canvas.appendChild(this._pendingPane);
	    YAHOO.util.Dom.addClass(this._pendingPane, "pendingPane");
	    flykit.mvcutils.hide(this._pendingPane);
	    
	    // set up the message pane
	    this._messagePane = document.createElement("p");
	    this._messagePane.innerHTML = "this should never be displayed";
	    this._canvas.appendChild(this._messagePane);
	    YAHOO.util.Dom.addClass(this._messagePane, "messagePane");
	    flykit.mvcutils.hide(this._messagePane);
	    
	    // setup results summary pane
	    this._resultsSummaryPane = document.createElement("p");
	    this._canvas.appendChild(this._resultsSummaryPane);
	    YAHOO.util.Dom.addClass(this._resultsSummaryPane, "resultsSummaryPane");
	    flykit.mvcutils.hide(this._resultsSummaryPane);
	        
	    // setup results pane
	    this._resultsPane = document.createElement("div");
	    this._canvas.appendChild(this._resultsPane);
	    YAHOO.util.Dom.addClass(this._resultsPane, "resultsPane");
	    flykit.mvcutils.hide(this._resultsPane);
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype._initCanvas", error);
    }    
}


/**
 * @private
 * Main callback function for model changes.
 * @param {String} type the model property name
 * @param {Array} args the from and to values
 * @param {flykit.bdgp.ImageWidget.DefaultRenderer} self a self reference
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._onModelChanged = function( type, args, self ) {
	try {
		// map property names to functions
	    var handlers = {
	        "STATE":"_onStateChanged",
	        "QUERY":"_onQueryChanged",
	        "RESULTS":"_onResultsChanged",
	        "ERRORMESSAGE":"_onErrorMessageChanged"
	    };
	    
	    // select the correct handler function
	    var handler = handlers[type];
	    
	    // call the handler
	    self[handler](args[0], args[1]);
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype._onModelChanged", error);
    }
    	
}


/**
 * @private
 * Handle a model state change.
 * @param {String} from the old state
 * @param {String} to the new state
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._onStateChanged = function (from, to) {
	try {
	    if ( to == "PENDING" ) {
	        flykit.mvcutils.show(this._pendingPane);
	        flykit.mvcutils.hide(this._messagePane);
	        flykit.mvcutils.hide(this._resultsSummaryPane);
	        flykit.mvcutils.hide(this._resultsPane);
	    } 
	    else if (to == "READY") {
	        flykit.mvcutils.hide(this._pendingPane);
	        flykit.mvcutils.hide(this._messagePane);
	        flykit.mvcutils.show(this._resultsSummaryPane);
	        flykit.mvcutils.show(this._resultsPane);     
	    }
	    else if ( to == "SERVERERROR" || to == "UNEXPECTEDERROR" ) {
	        flykit.mvcutils.hide(this._pendingPane);
	        flykit.mvcutils.show(this._messagePane);
	        flykit.mvcutils.hide(this._resultsSummaryPane);
	        flykit.mvcutils.hide(this._resultsPane);         
	    } 
	    else {
	        // this should never happen
	        throw {name:"flykit.bdgp.ImageWidget.UnexpectedStateError", message:"Invalid state: "+newState};
	    }
    }catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype._onStateChanged", error);
    }
};


/**
 * @private
 * Handle an error message change.
 * @param {String} from the old message
 * @param {String} to the new message
 */    
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._onErrorMessageChanged = function (from, to){
	try {
    	this._messagePane.innerHTML = to;
    }catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype._onErrorMessageChanged", error);
    }
    
};
    
    
/**
 * @private
 * Handle model results change.
 * @param {Array<flykit.bdgp.Image>} from the old results
 * @param {Array<flykit.bdgp.Image>} to the new results
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._onResultsChanged = function(from, to) {
	try {
	    if (to == null) {
            this._resultsSummaryPane.innerHTML = "";
	        this._resultsPane.innerHTML = "";
	    } else {

            // render the results summary
            var numberOfResults = 0;
            
            for (var i=0; i < to.length; i++){
                numberOfResults += to[i].images.length;
            }
            
            flykit.debug("the number of results " + to.length);

            this._renderResultsSummary(this._query, numberOfResults);
            this._renderResults(this._query, to);
            
	    }
	    
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype._onResultsChanged", error);
    }
};


/**
 * Handle model query changed.
 * @param {String} from the old query
 * @param {String} to the new query
 */    
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._onQueryChanged = function (from, to){
	try {
    	// don't do anything now, store the query and wait till we have more
    	this._query = to;
    }catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype._onQueryChanged", error);
    }

}
   
   
/**
 * Connect the renderer to a model.
 * @param {flyiu.mvcutils.GenericModel2} model
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype.connect = function( model ) {
	try {
    	model.subscribeAll(this._onModelChanged, this);
    
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype.connect", error);
    }
}    


/**
 * @private
 * Render the results summary pane.
 * @param {String or flykit.flybase.Gene} query the user's query
 * @param {Number} count the number of results
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._renderResultsSummary = function( query, count ) {
	try {
		// build the inner HTML content
	    var content = "found ";
	    content += count;
	    content += " matching image";    
	    content += (count == 0 || count > 1) ? "s " : " ";
	    content += "from <a href='http://fruitfly.org'>fruitfly.org</a> ("+flykit.bdgp.provenance+") for ";
	
	    if (query instanceof flykit.flybase.Gene) {
	        content += "gene <strong>"+query.symbols.join(" / ")+"</strong>";
	        if (count > 0) {
	            content += " (BDGP report: <a href='http://www.fruitfly.org/cgi-bin/ex/bquery.pl?qtype=report&amp;find="+query.annotationSymbols[0]+"&amp;searchfield=CG'>"+query.annotationSymbols[0]+"</a>)";
	        }
	    } else {
	        content +=  "query '"+query+"'";
	    }
	    if (count > 0) {
    	    content += " ...";
	    }
	    // render the content
	    this._resultsSummaryPane.innerHTML = content;
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype._renderResultsSummary", error);
    }
    
};



flykit.bdgp.ImageWidget.DefaultRenderer.prototype._renderResults = function( query, results ) {
    
    if (results.length > 0) { // if there are some results to render
        
        // set the content
        var resultsNode = this._imagesToDivHTML(results);
        flykit.debug("results node content:"+resultsNode.innerHTML);
        this._resultsPane.appendChild(resultsNode);
        
    }
    
};





flykit.bdgp.ImageWidget.DefaultRenderer.renderDomElementFromList = function(outerTag, innerTag, list) {
	try {
		var outerElem = document.createElement(outerTag);
	
		// can we defer this to caller?  tbody.appendChild(headerRow);
		
		flykit.debug("creating outer "+outerTag);
		for (var i=0; i<list.length; i++) {
			var innerElem = document.createElement(innerTag);
			innerElem.innerHTML = list[i];
			outerElem.appendChild(innerElem);
		}	
	
		return outerElem;
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.renderDomElementFromList", error);
    }	
}

/**
 * @private
 * Render an array of image objects as HTML content.
 * Override this method to get custom rendering.
 * @param {Array<flykit.bdgp.Image>} images the images to render
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._imagesToDivHTML = function( stages ) {
    try {
	    // build the content
	    
	    flykit.debug("create the table");
		
		var table = document.createElement("table");
		var thead = document.createElement("thead");
		var tbody = document.createElement("tbody");
		table.appendChild(thead);
		table.appendChild(tbody);
		
		// set up header row
		var headers = ["stage and expressions", "images"];
		var headerRow = flykit.bdgp.ImageWidget.DefaultRenderer.renderDomElementFromList("tr", "th", headers); 
		thead.appendChild(headerRow);
		
		flykit.debug("rendering the header " + headers.innnerHTML);
	   
	    for ( var s=0; s<stages.length; s++){
	    	flykit.debug("creating result row "+stages[s].stageName);
	   	
	    	var cellContents = this._imageToTableCellContents(stages[s]);
			var row = flykit.bdgp.ImageWidget.DefaultRenderer.renderDomElementFromList("tr", "td", cellContents);
			tbody.appendChild(row);    	
	    	
	    }
	    return table;
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype._imagesToDivHTML", error);
    }	
    
};

flykit.bdgp.ImageWidget.DefaultRenderer.prototype._imageToTableCellContents = function( stage ) {
	try {
		var imageSet = stage.images;
		
		var firstColumn = stage.stageName + this._expressionTermsToDivHTML(imageSet[0]);
			
		var secondColumn = "";
		
		for ( var i=0; i<imageSet.length; i++ ) {
	    
	    	secondColumn += this._imageToDivHTML(imageSet[i]);
		}
		
		return [firstColumn, secondColumn];
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype._imageToTableCellContents", error);
    }
}

flykit.bdgp.ImageWidget.DefaultRenderer.prototype._expressionTermsToDivHTML = function ( image ){
	
	try {
		var content = "<div class=\"terms\">";
		var expressionTerms = image.expressions;
		content += "<ul>";
		for (var t=0; t<expressionTerms.length; t++){
			content += "<li>" + expressionTerms[t] + "</li>";
		}
		content += "</ul></div>";
		return content;
	}catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype._expressionTermsToDivHTML", error);
    }
}

/**
 * @private
 * Render an image object as HTML content.
 * Override this method to get custom rendering.
 * @param {flykit.bdgp.Image} image the image to render
 */
flykit.bdgp.ImageWidget.DefaultRenderer.prototype._imageToDivHTML = function( image, query, stage ) {
    
    try {
        var alttext = "thumbnail image of embryo in situ hybridisation";
        if (query instanceof flykit.flybase.Gene && stage) {
	        alttext = "thumbnail image depicting expression of "+query.symbols[0]+" in "+stage+" embryo"
	    }
	    var content =   "<div class=\"result\">";
	    content +=          "<a href=\"" + image.fullImageURL + "\">";
	    content +=              "<img src=\"" + image.thumbnailURL + "\" alt=\""+alttext+"\">";
	    content +=          "</img></a>"; 
	    content +=      "</div>";
	    
	    return content;
    }catch (error) {
        	throw new flykit.UnexpectedException("flykit.bdgp.ImageWidget.DefaultRenderer.prototype._imageToDivHTML", error);
    }
};
 




/**
 * @class
 * @extends flykit.bdgp.ImageWidget.DefaultRenderer
 */
flykit.bdgp.ImageWidget.CompactRenderer = function() {};




// extend
flykit.bdgp.ImageWidget.CompactRenderer.prototype = new flykit.bdgp.ImageWidget.DefaultRenderer();



/**
 * @override
 */
flykit.bdgp.ImageWidget.CompactRenderer.prototype._renderResults = function( query, results ) {
    
    if (results.length > 0) { // if there are some results to render
        
        var content = "<table><tbody>";
        
        for (var i=0; i<results.length; i++) {
            var stage = results[i];
            var sn = stage.stageName.replace(/stage(.*)/, "stage $1");
            content += "<tr class='stagename'><td>" + sn + "</td></tr>";
            content += "<tr class='stageimages'><td>";
            
            content += "<p class='terms'>";
            for ( var j=0; j<stage.annotations.length; j++ ) {
                content += stage.annotations[j] + "; ";
            }
            content += "</p>";

            for ( var j=0; j<stage.images.length; j++ ) {
                content += this._imageToDivHTML(stage.images[j], query, sn);
            }
            
            
            content += "</td></tr>";
        }

        content += "</tbody></table>";
        
        var resultsNode = document.createElement("div");
        resultsNode.innerHTML = content;

        // set the content
        flykit.debug("results node content:"+resultsNode.innerHTML);
        this._resultsPane.appendChild(resultsNode);
        
    }
    
};

