// copyright(c) 2008-2009 The Allen Institute for Brain Science.
//All of the image strips here depend on the presence of prototype.js v1.6+

// A scrolling window of images.
// parentId   : The html ID of the element that will contain this image strip.
// orientation: horizontal or vertical, given as 'h' or 'v'
// imageServer: The path to the image service.
function simpleStrip(parentId, orientation, imageServer, maxTier, options) {

//public properties
    this.parent_id = parentId;
    this.id = 'simplestrip_' + this.parent_id;
    this.image_server = imageServer;
    this.orientation = orientation;
//TODO: there has to be a better way to deal with the max tier for images.
    this.max_tier = maxTier;
    this.trace_function = null;

//event hooks
    this.onSelect = null;
    this.onLoad = null;

//public methods

    // Add a single image to the strip.  THe image param
    // should be an object with at least two fields, "id" & "src".
    // The "id" field will be used as an html ID for an <img> element,
    // so it should be unique in the scope of the document.  The "src"
    // field shold be the complete path to the image, including host & port
    // if they are different than the document in which the stip will live.
    // NOTE that you can include more than one path field, for example
    // src & expSrc & nisslSrc, and then switch between them by calling the
    // setSrcKey method.
    this.addImage = function(image) {
        addImage(image);
    };

    // Adds a group of images to the strip.  The images param
    // is an array of image objects as described above.
    this.addImageSet = function(images) {
        for(var i = 0; i < images.length; i++)
            addImage(images[i]);
    };

    this.removeImage = function(id) {
        images[id].element.remove();
        delete images[id];
    };

    // remove all the images in the strip
    this.clear = function() {
        for(key in images) {
            var id = images[key].id;
            images[key].element.remove();
            delete images[id];
        }
    };

    this.select = function(id) {
        selectImage($(this.parent_id + "_" + id));
    };

    this.getImages = function() {
        return(images);
    };

    this.getImage = function(id) {
        for(key in images) {
            if(images[key].id == id)
                return(images[key]);
        }
        return(null);
    };

    // Note: images properties are not guaranteed to match DOM <img> order, so it is not used here
    //
    // return - first image, or null if error
    this.getFirstImage = function() {
        var imageElements = $(self.parent_id).select('img');
        if (imageElements === undefined || imageElements.length === 0) {
            return null;
        }

        // id of element looks like 'simstrip32934539_2468', where '2468' is the part we want
        var elementId = imageElements[0].id;
        var imageId = elementId.slice(elementId.indexOf("_") + 1);

        return self.getImage(imageId);
    };

    // Note: images properties are not guaranteed to match DOM <img> order, so it is not used here
    //
    // return - last image
    this.getLastImage = function() {
        var imageElements = $(self.parent_id).select('img');
        if (imageElements === undefined || imageElements.length === 0) {
            return null;
        }

        // id of element looks like 'simstrip32934539_2468', where '2468' is the part we want
        var elementId = imageElements[imageElements.length - 1].id;
        var imageId = elementId.slice(elementId.indexOf("_") + 1);

        return self.getImage(imageId);
    };

    // id of element looks like 'simstrip32934539_2468', where '2468' is the part we want
    // to find as an image id
    // Note: images properties are not guaranteed to match DOM <img> order, so it is not used here
    //
    // return - next image, or current image if already at last image, or null if error
    this.getNextImage = function() {
        var newElement;
        var newElementId;
        var newImageId;
        var index;

        var elementId = current_selection.id;
        var imageElements = $(self.parent_id).select('img');
        if (imageElements === undefined || imageElements.length === 0) {
            return null;
        }

        var len = imageElements.length;

        for (index = 0; index < len; ++index) {
            if (imageElements[index].id == elementId) {
                break;
            }
        }

        if (index === len) {
            // error if id not found
            return null;
        }
        else if (index === (len - 1)) {
            newElement = imageElements[index];
            newElementId = newElement.id;
            newImageId = newElementId.slice(newElementId.indexOf("_") + 1);
            return (self.getImage(newImageId));
        }
        else {
            newElement = imageElements[index + 1];
            newElementId = newElement.id;
            newImageId = newElementId.slice(newElementId.indexOf("_") + 1);
            return (self.getImage(newImageId));
        }
    };

    // Note: images properties are not guaranteed to match DOM <img> order, so it is not used here
    //
    // return - previous image, or current image if at first image, or null if error
    this.getPreviousImage = function() {
        var newElement;
        var newElementId;
        var newImageId;
        var index;

        var elementId = current_selection.id;
        var imageElements = $(self.parent_id).select('img');
        if (imageElements === undefined || imageElements.length === 0) {
            return null;
        }

        var len = imageElements.length;

        for (index = 0; index < len; ++index) {
            if (imageElements[index].id == elementId) {
                break;
            }
        }

        if (index === len) {
            // error if id not found
            return null;
        }
        else if (index === 0) {
            newElement = imageElements[0];
            newElementId = newElement.id;
            newImageId = newElementId.slice(newElementId.indexOf("_") + 1);
            return (self.getImage(newImageId));
        }
        else {
            newElement = imageElements[index - 1];
            newElementId = newElement.id;
            newImageId = newElementId.slice(newElementId.indexOf("_") + 1);
            return (self.getImage(newImageId));
        }
    };

    this.moveToImage = function(id) {
        var distance = 0;
        for(key in images) {
            if(id == images[key].id) {
                if(this.orientation == 'h')
                    outer_container_div.scrollLeft = distance;
                else
                    outer_container_div.scrollTop = distance;
                return;
            }

            if(this.orientation == 'h')
                distance += images[key].element.getWidth();
            else
                distance += images[key].element.getHeight();
        }
    };

    this.setSrcKey = function(newSrcKey) {
        srcKey = newSrcKey;
        for(key in images) {
            images[key].element.src = getPath(images[key]);
        }
    };

    this.setFilter = function(name, vals) {
    	filter = name;
    	filterVals = vals;
    	for(key in images) {
    	   images[key]['element'].src = getPath(images[key]);
    	}
    };

    // entry point for key events redistributed by Zap Viewer
    this.doKey = function(evt) {
        evt.fromZapViewer = true;
        doKey(evt);
    };

//private properties
    var self = this;
    var framework_div = null;
    var container_div = null;
    var outer_container_div = null;
    var images = new Object();
    var srcKey = (options.src_key ? options.src_key : "imagepath");
    var defer_init = options.defer_init == true ? true : false;
    var precalculated_path = options.precalculated_path == 'true' ? true : false;
    var default_srcKey = srcKey;
    var current_selection = null;
    var filter = "";
    var filterVals = "";

//private methods
    function trace(msg) {
        if(self.trace_function)
            self.trace_function(msg);
    }

    function init() {
//alert("initing strip");
        buildFramework();

        //Event.stopObserving(window, 'load', init);
        if(self.onLoad)
            self.onLoad();
//alert("done initing strip");
    }

    function buildFramework() {
        // There is a div frame, containing a div, containing a <nobr>,
        // which will be holding all of the images.
        var parent = $(self.parent_id);
        framework_div = new Element('div',{id:'simstripFrame'+self.id, className:'simstripFrame', style:'width:100%;height:100%;overflow:hidden'});

        if(self.orientation == 'h') {
            outer_container_div = new Element('div',{id:'simstripCont'+self.id, className:'simstripCont', style:'width:' + (parent.clientWidth+17) + 'px;height:100%;overflow:scroll;cursor:pointer'});
            container_div = new Element('nobr',{style:'width:100%;height:100%'});
        } else if(self.orientation == 'v') {
            outer_container_div = new Element('div',{id:'simstripCont'+self.id, className:'simstripCont', style:'width:100%;height:' + (parent.clientHeight+17) + 'px;overflow:scroll;cursor:pointer'});
            container_div = null;
        }

        parent.appendChild(framework_div);
        framework_div.appendChild(outer_container_div);
        if(container_div !== null)
            outer_container_div.appendChild(container_div);
        else
            container_div = outer_container_div;
    }

    // Note: setting attribute tabindex='0' allows the <img> to receive keyboard focus and keyboard events
    function addImage(image) {
        var path = getPath(image);
        if(self.orientation == 'h') {
            var height = outer_container_div.clientHeight;///outer_container_div.getHeight();
            var img = new Element('img',{id:self.parent_id + "_" + image.id, src:path, className:'simstripImg', tabindex:'0', style:'height:' + (height-4) + 'px;width:auto;border: 1px solid #ddd;'});
        }
        else {
            var width = outer_container_div.clientWidth;///outer_container_div.getHeight();
            var img = new Element('img',{id:self.parent_id + "_" + image.id, src:path, className:'simstripImg', tabindex:'0', style:'height:auto;width:'+ (width-2) + 'px;border: 1px solid #ddd;'});
        }

        image['element'] = img;
        images[img.id] = image;
        container_div.appendChild(img);

        img.observe('click', doClick);
        setupKeyListener(img);
        setupWheelListener(img);
    }

    function wheelZoom(evt) {
        // do nothing, let event bubble up to zap viewer
        // to handle zooming of zap image
    }

    function setupWheelListener(element) {
        //listen to mouse wheel events. (Credit http://www.ogonek.net/mousewheel/demo.html)
        Object.extend(Event, {
                wheel:function (event){
                        var delta = 0;
                        if (!event) event = window.event;
                        if (event.wheelDelta) {
                                delta = event.wheelDelta/120;
                                if (window.opera) delta = -delta;
                        } else if (event.detail) {
                                delta = -event.detail/3;
                        }
                        return Math.round(delta);
                }
        });
        Event.observe(element, 'mousewheel', wheelZoom, false);
        Event.observe(element, 'DOMMouseScroll', wheelZoom, false);
    }

    function doClick(evt) {
        selectImage(evt.element());
    }

    function setupKeyListener(elem) {
        //listen for key strokes for zoom & pan
        if(Prototype.Browser.WebKit) {
            // In Safari (& all webkit-based browsers) a <div> is not focusable,
            // so will never report keystrokes.  This adds an invisable text
            // element that will receive focus and process keyboard events.
            setupWebKitKeyListener(elem);
        }
        else
            Event.observe(elem, 'keydown', doKey);
    }

    var key_catcher;
    function setupWebKitKeyListener(elem) {
        if(Prototype.Browser.WebKit) {
            key_catcher = new Element('input',{id:'simplestripkey'+elem.id, type:'text', className:'key_catcher', style:'outline:none;background-color:transparent;font-size:0pt;border:0px;width:0px;height:0px'});
            elem.appendChild(key_catcher);
            Event.observe(elem, 'click', catchWebkitKeys);
            key_catcher.focus();
            Event.observe(key_catcher, 'keydown', doKey);
        }
    }

    function catchWebkitKeys() {
        key_catcher.focus();
    }

    // key events can be also originate from zapviewer, which catches them
    // bubbling up from zapimg, then redirected to the public doKey, which then
    // call this private doKey. So you must do an Event.stop for any events processed here,
    // or else they will bubble back up to zapviewer again.
    function doKey(evt) {
        var image;

        if (self.zapWantsKey === true && evt.fromZapViewer !== true) {
            return;
        }

         switch(evt.keyCode) {
            case 69://"e" select 1st image
                image = self.getFirstImage();
                self.moveToImage(image.id);
                self.select(image.id);
                Event.stop(evt);
                break;
            case 82://"r" select last image
                image = self.getLastImage();
                self.moveToImage(image.id);
                self.select(image.id);
                Event.stop(evt);
                break;
            case 68://"d" select previous image
                image = self.getPreviousImage();
                self.moveToImage(image.id);
                self.select(image.id);
                Event.stop(evt);
                break;
            case 70://"f" select next image
                image = self.getNextImage();
                self.moveToImage(image.id);
                self.select(image.id);
                Event.stop(evt);
                break;
            default:
                break;
        }
    }

    function selectImage(img) {
        if(!self.onSelect) {
            showSelection(img);
            return(null);
        }

        //the user wants to know about image selections,
        //so pass the image object to their handler.  Unless the
        //user's handler explicitly returns false, we will mark
        //the image as selected.
        for(key in images)
            if(key == img.id) {
                ret = self.onSelect(images[key]);
                if(ret == undefined || ret)
                    return(showSelection(img));
                else
                    return(null);
            }

        return(null);
    }

    function showSelection(img) {
        if(current_selection != null) {
            current_selection.className = "simstripImg";
            current_selection.setStyle('border:1px solid #ddd;');
        }
        img.className = "simstripImgSel";
        img.setStyle('border:1px solid #000;');
        current_selection = img;
    }

    function getPath(image) {

        if(precalculated_path)
		return(image[srcKey]);

        var path = self.image_server + "&path=" + image[srcKey] + "&zoom=" + image.tier;

        var sub = null;
        if(image['subimage_' + srcKey])
            sub = image['subimage_' + srcKey];
        else if(image['subimage_' + default_srcKey])
            sub = image['subimage_' + default_srcKey];

        if(sub) {
            var scale = Math.pow(2,(self.max_tier-image.tier));
            path += "&top=" + sub.top;
            path += "&left=" + sub.left;
            path += "&width=" + sub.width/scale;
            path += "&height=" + sub.height/scale;
        }
	if(filter.length > 0) {
            path += "&filter=" + filter;
            path += "&filterVals=" + filterVals;
	}
	else {
	        if(image['filter_'+srcKey]) {
	            path += "&filter=" + image['filter_'+srcKey];
	            path += "&filterVals=" + image['filterVals_'+srcKey];
	        }
	}
        if(image.quality)
            path += "&quality=" + sub.quality;

        return(path);
    }


//initialization
    if(defer_init) {
//alert("defering strip init");
      Event.observe(window, 'load', init);
    }
    else
      init();
}

// A window of images with left & right (or top & bottom) controls
// that cause the images to slide into and out of the viewport
function sliderStrip(parentId, orientation, imageServer, maxTier) {

//public properties
    this.parent_id = parentId;
    this.id = 'simplestrip_' + this.parent_id;
    this.image_server = imageServer;
    this.orientation = orientation;
//TODO: there has to be a better way to deal with the max tier for images.
    this.max_tier = maxTier;

//event hooks
    this.onSelect = null;

//public methods

    // Add a single image to the strip.  THe image param
    // should be an object with at least two fields, "id" & "src".
    // The "id" field will be used as an html ID for an <img> element,
    // so it should be unique in the scope of the document.  The "src"
    // field shold be the complete path to the image, including host & port
    // if they are different than the document in which the stip will live.
    // NOTE that you can include more than one path field, for example
    // src & expSrc & nisslSrc, and then switch between them by calling the
    // setSrcKey method.
    this.addImage = function(image) {
        addImage(image);
    }

    // Adds a group of images to the strip.  The images param
    // is an array of image objects as described above.
    this.addImageSet = function(images) {
        for(var i = 0; i < images.length; i++)
            addImage(images[i]);
    }

    this.removeImage = function(id) {
        images[id].element.remove();
        delete images[id];
    }

    // remove all the images in the strip
    this.clear = function() {
        for(key in images) {
            var id = images[key].id;
            images[key].element.remove();
            delete images[id];
        }
    }

    this.select = function(id) {
        selectImg($(id));
    }

    this.moveToImage = function(id) {
        var distance = 0;
        for(key in images) {
            if(id == images[key].id) {
                if(this.orientation == 'h')
                    outer_container_div.scrollLeft = distance;
                else
                    outer_container_div.scrollTop = distance;
                return;
            }

            if(this.orientation == 'h')
                distance += images[key].element.getWidth();
            else
                distance += images[key].element.getHeight();
        }
    }

    this.setSrcKey = function(newSrcKey) {
        srcKey = newSrcKey;
        for(key in images) {
            $(images[key].id).src = images[key][srcKey];
        }
    }

//private properties
    var self = this;
    var framework_div = null;
    var container_div = null;
    var outer_container_div = null;
    var images = new Object();
    var srcKey = "path";
    var current_selection = null;

//private methods
    function init() {
        buildFramework();
    }

    function buildFramework() {
        // There is a div frame, containing a div, containing a <nobr>,
        // which will be holding all of the images.
        var parent = $(self.parent_id);
        framework_div = new Element('div',{id:'simstripFrame'+self.id, className:'simstripFrame', style:'width:100%;height:100%;overflow:hidden'});

        if(self.orientation == 'h') {
            outer_container_div = new Element('div',{id:'simstripCont'+self.id, className:'simstripCont', style:'width:' + (parent.clientWidth+17) + 'px;height:100%;overflow:scroll;cursor:pointer'});
            container_div = new Element('nobr',{style:'width:100%;height:100%'});
        } else if(self.orientation == 'v') {
            outer_container_div = new Element('div',{id:'simstripCont'+self.id, className:'simstripCont', style:'width:100%;height:' + (parent.clientHeight+17) + 'px;overflow:scroll;cursor:pointer'});
            container_div = null;
        }

        parent.appendChild(framework_div);
        framework_div.appendChild(outer_container_div);
        if(container_div !== null)
            outer_container_div.appendChild(container_div);
        else
            container_div = outer_container_div;
    }

    function addImage(image) {
        var path = getPath(image);
        if(self.orientation == 'h') {
            var height = outer_container_div.clientHeight;///outer_container_div.getHeight();
            var img = new Element('img',{id:image.id, src:path, className:'simstripImg', style:'height:' + (height-2) + 'px;width:auto;border: 1px solid #ddd;'});
        }
        else {
            var width = outer_container_div.clientWidth;///outer_container_div.getHeight();
            var img = new Element('img',{id:image.id, src:path, className:'simstripImg', style:'height:auto;width:'+ (width-2) + 'px;border: 1px solid #ddd;'});
        }

        image['element'] = img;
        images[img.id] = image;
        img.observe('click', doClick);
        container_div.appendChild(img);
    }

    function doClick(evt) {
        selectImage(evt.element());
    }

    function selectImage(img) {
        if(!self.onSelect) {
            showSelection(img);
            return;
        }

        //the user wants to know about image selections,
        //so pass the image object to their handler.  Unless the
        //user's handler explicitly returns false, we will mark
        //the image as selected.
        for(key in images)
            if(key == img.id) {
                ret = self.onSelect(images[key]);
                if(ret == undefined || ret)
                    return(showSelection(img));
                else
                    return;
            }
    }

    function showSelection(img) {
        if(current_selection != null)
            current_selection.setStyle('border:1px solid #ddd;');

        img.setStyle('border:1px solid #000;');
        current_selection = img;
    }

    function getPath(image) {
        var path = self.image_server + "?path=" + image[srcKey] + "&zoom=" + image.tier;
        if(image['subimage_'+srcKey]) {
            var scale = Math.pow(2,(self.max_tier-image.tier));
//trace("scale = " + scale);
            var sub = image['subimage_'+srcKey];
            path += "&top=" + sub.top;
            path += "&left=" + sub.left;
            path += "&width=" + sub.width/scale;
            path += "&height=" + sub.height/scale;
        }
        if(image.quality)
            path += "&quality=" + sub.quality;

        return(path);
    }

//initialization
    init();
}

// a stip of images, all of which are visible, that
// bloom on mouseover and subside on mouseout.
function scaleStrip(parentId, orientation, imageServer, maxTier, options) {

//public properties
    this.parent_id = parentId;
    this.id = 'scalestrip_' + this.parent_id;
    this.image_server = imageServer;
    this.orientation = orientation;
    this.image_height = (options ? (options.item_height ? options.item_height : 20) : 20);
    this.scale_percent = (options ? (options.scale_percent ? options.scale_percent : 400) : 400);
    this.max_tier = maxTier;

//event hooks
    this.onSelect = null;

//public methods

    this.addImage = function(image) {
        addImage(image);
    }

    // Adds a group of images to the strip.  The images param
    // is an array of image objects as described above.
    this.addImageSet = function(images) {
        for(var i = 0; i < images.length; i++)
            addImage(images[i]);
    }

    this.removeImage = function(id) {
        images[id].element.remove();
        delete images[id];
    }

    // remove all the images in the strip
    this.clear = function() {
        for(key in images) {
            var id = images[key].id;
            images[key].element.remove();
            delete images[id];
        }
    }

    this.select = function(id) {
        selectImage($(id));
    }

    this.moveToImage = function(id) {
    }

    this.setSrcKey = function(newSrcKey) {
        srcKey = newSrcKey;
        for(key in images) {
            $(images[key].id).src = images[key][srcKey];
        }
    }

//private properties
    var self = this;
    var framework_div = null;
    var container_div = null;
    var image_table = null;
    var horizontal_image_row = null;
    var images = new Object();
    var srcKey = "path";
    var current_selection = null;
    var current_row = 0;

//private methods
    function init() {
        buildFramework();
        if(defer_init)
            Event.stopObserving(window, 'load', init);
    }

    function buildFramework() {
        var parent = $(self.parent_id);
        var framework_div = new Element('div',{id:'barstripFrame'+self.id, className:'barstripFrame', style:'height:100%;width:100%;margin-left:auto;margin-right:auto;overflow:hidden'});
        image_table = new Element('table',{id:'barstripTable'+self.id, className:'barstripTable', style:'width:100%;height:100%;margin-left:auto;margin-right:auto;'});

        if(self.orientation == 'h')
            horizontal_image_row = image_table.insertRow(current_row++);

        parent.appendChild(framework_div);
        framework_div.appendChild(image_table);
    }

    function addImage(image) {
        var path = getPath(image);
        var img = new Element('img',{id:image.id,
                                    src:path,
                                    className:'barstripImg',
                                    style:'height:' + self.image_height + 'px;width:auto;border: 1px solid #ccc;cursor:pointer'});

        img.observe('mouseover', overBar);
        img.observe('mouseout', offBar);
        img.observe('click', doClick);

        if(self.orientation == 'h') {
            var cell = horizontal_image_row.insertCell(-1);
            $(cell).setStyle('text-align:center');
            cell.appendChild(img);
        } else {
            var row = image_table.insertRow(current_row++);
            var cell = row.insertCell(0);
            $(cell).setStyle('width:100%;text-align:center');
            cell.appendChild(img);
        }

        image['element'] = img;
        images[img.id] = image;
    }

    function getLength(o) {
        var i = 0;
        for(var x in o)
            i++;
        return(i);
    }

    function doClick(evt) {
        selectImage(evt.element());
    }

    function selectImage(img) {
        if(!self.onSelect) {
            showSelection(img);
            return;
        }

        //the user wants to know about image selections,
        //so pass the image object to their handler.  Unless the
        //user's handler explicitly returns false, we will mark
        //the image as selected.
        for(key in images)
            if(key == img.id) {
                ret = self.onSelect(images[key]);
                if(ret == undefined || ret)
                    return(showSelection(img));
                else
                    return;
            }
    }

    function showSelection(img) {
        if(current_selection != null)
            current_selection.setStyle('border:1px solid #ddd;');

        img.setStyle('border:1px solid #000');
        current_selection = img;
    }

    function getPath(image) {
        var path = self.image_server + "&path=" + image[srcKey] + "&zoom=" + image.tier;
        if(image['subimage_path']) {
            var scale = Math.pow(2,(self.max_tier-image.tier));
            var sub = image['subimage_path'];
            path += "&top=" + sub.top;
            path += "&left=" + sub.left;
            path += "&width=" + sub.width/scale;
            path += "&height=" + sub.height/scale;
        }
        if(image.quality)
            path += "&quality=" + sub.quality;

        return(path);
    }

    var over = false;
    function overBar(evt) {
        if(!over)
            new Effect.Scale(Event.element(evt), self.scale_percent, {beforeStart:beforeOver, afterFinish:doneOver, duration:0.1, scaleX:false, scaleY:true, scaleFromCenter:true, scaleMode:{originalHeight:self.image_height}});
    }

    function offBar(evt) {
        var el = Event.element(evt);
        var height = el.getHeight()-2;
        var pct = (self.image_height*100)/height;
        new Effect.Scale(el, pct, {duration:0.2, scaleX:false, scaleY:true, scaleFromCenter:true, scaleMode:{originalHeight:height}});
    }

    function beforeOver() {
        over = true;
    }
    function doneOver() {
        over = false;
    }

//initialization
    if(defer_init)
        Event.observe(window, 'load', init);
    else
      init();
}
