// $Id: alignMap.js,v 1.25 2007/08/24 19:12:39 sherrimn Exp $
// $Source: /cm/cvs/webApps/shared/plugins/alignMap/alignMap.js,v $

var eventHandlerMap;
var host_element_id;

// In order to drag items around, use domdrag.js:
//document.write('<script type="text/javascript" src="/thirdParty/domdrag.js"><\/' + 'script>');

// Figure out our directory, so we can call our back-end reliably
var gMapServerPath = '';
$$('script').each(function(script){
    if (script.src.match(/alignMap\.js/)) 
    {
      gMapServerPath = script.src.replace(/js\/alignMap\.js/, '');
    }
}.bind(this));

// Include IE-specific styles:
if (Prototype.Browser.IE) {
document.write('<link rel="stylesheet" href="'+gMapServerPath+'css/alignMap_IE.css" type="text/css"></link>');
}

var map_x_offset;
var map_y_offset;
var alignMapPtr;
var gMapData = null;


var AlignMap = Class.create();

AlignMap.prototype = {
  initialize: function(element_id, session_object, app_name)
  {
    // Remember our host element
    host_element_id = element_id;
    
    if ($(host_element_id) == null) throw('alignMap host element is null');
    
    $(host_element_id).makePositioned();
    
    // Add a hidden element, to show that we can (debug)
    if ($(host_element_id).innerHTML == "&nbsp;")
    {
      $(host_element_id).innerHTML = '<a style="display:none;">placeholder</a>';
    }
    
    // Remember our own self
    alignMapPtr = this;
    this.sessionPtr = session_object;
    
    // Remember our app name
    this.appName = app_name;
    
    // The default help file:
    this.helpPath = 'help/maphelp0.html';

    // an associative array to hold our event handler pointers
    this.eventHandlerMap = []; 
    
    this.selectedItemsHash  = $H({});
    
    // Which optional features shall we show?
    this.includedFeaturesVal = "";
    this.labels = ""; // The labels to enable
    
    this.sessionData = {
                         mapID:         '',
                         mapType:       '',
                         species:       '',
                         chromosome:    '',
                         chr_left:       0,
                         chr_right:      0,
                         selectedItems: '',
                         overlayFeatures: $H({})
                       };
    
    this.zoomStack = [];
    
    this.serverpath = gMapServerPath; // this was deduced above
    
    // The default help file:
    this.helpPath = this.serverpath + 'help/maphelp0.html';
    
    this.firstLoad = true;
  },
  
  setAppName: function(app_name)
  {
    this.appName = app_name;
  },
  
  // Allow the map caller to set the helpFile path:
  setHelpFile: function(helpPath)
  {
    this.helpPath = alignMapPtr.serverpath + helpPath;
    
    // If we already have a helpBalloon instance, set it to the spec'd file:
    if ($('mapHelp') != null && $('mapHelp').firstDescendant() != null)
    {
      $('mapHelp').firstDescendant().writeAttribute({href:this.helpPath});
    }
  },
  
  saveSessionData: function()
  {
    if (this.sessionPtr)
    {
      this.sessionPtr.setParam("alignMapSessionData", 
                               Object.toJSON(this.sessionData));
    }
  },

  keepSelectedElementsSelected: function ()
  {
    // Iterate through the selectedItemsHash and draw as selected:
    this.selectedItemsHash.keys().each(function(key){
      // Draw it as selected
      this.drawSelected(key);
    }.bind(this));
  },

  //////////////////////////////////////////////////////////////////////////////
  // id_type can be "gene_id", "transcript_id", or "assay_id"
  showMap: function(id_type, id_val)
  {
    // Show an hourglass while we fetch the map
    this.show_progress();
    
    var cookieParams = this.getCookieParams();

    var URL = this.serverpath + 'alignMap.rb?wanted=json'+
             '&input_type='     + id_type +
             '&acc='            + id_val +
             '&include_features='+ this.includedFeaturesVal +
             '&labels='          + this.labels +
             '&app='             + this.appName +
             '&server='          + this.serverpath +
             cookieParams        +
             '&cachestopper='    + Math.random();

    this.mapAjaxCall(URL, this.setup_incoming_map);

    // Reset the zoomStack
    alignMapPtr.zoomStack = [];
    
    // Remember the current id and id_type in a browser cookie
    this.sessionData.mapID   = id_val;
    this.sessionData.mapType = id_type;
    this.saveSessionData();
  },


  //////////////////////////////////////////////////////////////////////////////
  // 
  showMapRegion: function (species, chromosome, left, right, acc, acc_type, orient)
  {
    this.m_current_species = species;
    this.m_current_chrom   = chromosome;
    
    this.show_progress();

    var cookieParams = this.getCookieParams();

    var URL = this.serverpath + 'alignMap.rb?wanted=json'+
             '&input_type=region' +
             '&acc='            + acc +
             '&acc_type='       + acc_type +
             '&species='        + species +
             '&chrom='          + chromosome +
             '&left='           + left +
             '&right='          + right +
             '&orient='          + orient +
             '&include_features='+ this.includedFeaturesVal +
             '&labels='          + this.labels +
             '&app='             + this.appName +
             '&server='          + this.serverpath +
             cookieParams        +
             '&cachestopper='    + Math.random();

    this.mapAjaxCall(URL, this.setup_incoming_map);
  },
  
  mapAjaxCall: function(url, callback)
  {
    gMapData = null;
    this.node = document.createElement('SCRIPT');
    this.node.type = 'text/javascript';
    this.node.src = url;

        /* FF and Opera properly support onload. MSIE has its own implementation. Safari and Konqueror need some polling */

    if (Prototype.Browser.IE) {
        
        function mybind(obj) {
            temp = function() {
                if (this.readyState == "complete" || this.readyState == "loaded") {
                    return callback(obj);
                }
            };
            return temp;
        }
        /* MSIE doesn't support the "onload" event on
           <script> nodes, but it at least supports an
           "onreadystatechange" event instead. But notice:
           according to the MSDN documentation we would have
           to look for the state "complete", but in practice
           for <script> the state transitions from "loading"
           to "loaded". So, we check for both here... */
        this.node.onreadystatechange = mybind(this);
    } else if (Prototype.Browser.WebKit) { // || this.browser.konqueror) {
      this.timepassed = 0;

      /* Safari/WebKit and Konqueror/KHTML do not emit
           _any_ events at all, so we need to use some primitive polling */
      this.checkTimer = setInterval(function()
            {
              this.timepassed = this.timepassed+100;
              if(typeof(gMapData) != 'undefined' && gMapData != null)
              {
                callback();
                clearInterval(this.checkTimer);
              }
              if(this.timepassed > 20000)
                clearInterval(this.checkTimer);
              }.bind(this),100);
    } else {

        /* Firefox, Opera and other reasonable browsers can
           use the regular "onload" event... */
        this.node.onload = callback;
    }

    /* inject <script> node into <head> of document */
    this.readyState = 3;
    //this.onreadystatechange();
    var head = document.getElementsByTagName('HEAD')[0];
    head.appendChild(this.node);

  },
  
  reloadMap: function()
  {
    
    if (typeof(alignMapPtr.map_data) == 'undefined') 
      return; // Don't try to draw if we have no info:
    
    if (alignMapPtr.map_data.map_type == 'region')
    {
      alignMapPtr.showMapRegion(alignMapPtr.map_data.species, 
                         alignMapPtr.map_data.chromosome,
                         alignMapPtr.map_data.chr_left,
                         alignMapPtr.map_data.chr_right,
                         alignMapPtr.map_data.acc,
                         alignMapPtr.map_data.acc_type,
                         alignMapPtr.map_data.orientation);
    }
    else if (alignMapPtr.map_data.acc != '')
    {
      alignMapPtr.showMap(alignMapPtr.map_data.map_type, alignMapPtr.map_data.acc);
    }

  },

  //////////////////////////////////////////////////////////////////////////////
  // feature_string tells the map what optional features to show
  // It should be a comma-delimited list like "miRNA,CNV"
  setIncludedFeatures: function(feature_string)
  {
    this.includedFeaturesVal = feature_string;
  },
  
  setLabels: function(feature_string)
  {
    this.labels = feature_string;
  },

  show_progress: function()
  {
    $(host_element_id).innerHTML +=
    "<img style=\"position:absolute; left:240px; top:10px;\" src=\"images/progress.gif\" />";

    alignMapPtr.raiseEvent('onLoading');
  },
  
  setup_incoming_map: function (transport) 
  {
    if (typeof(gMapData) == 'undefined' || gMapData === null || gMapData == '')
    {
      gMapData = 'Error: No data from alignMap';
    }
    var myHtml = gMapData;// from teh JSON
    
    myHtml += '<span id="temp_features_overlay"></span>'+
              '<span id="selected_features_overlay"></span>'+
              '<span id="colorBar_overlay"></span>'+
              '<span id="mapHelp"></span>';

    // Insert map HTML into the host element:
    $(host_element_id).innerHTML = myHtml;

    // Grab the map metadata hidden in the HTML:
    var map_data = $("alignMap_data");
    if (map_data !== null)
    {
      var map_data_json = map_data.firstChild.nodeValue;
      alignMapPtr.map_data = map_data_json.evalJSON();
    
      var map_height = parseInt(alignMapPtr.map_data.features_bottom) + 45
      $(host_element_id).setStyle({height: map_height + 'px'});
    }
    
    // Show any overlayed features
    var temp_features = $H({});
    var selected_features = $H({});
    alignMapPtr.sessionData.overlayFeatures.values().each(function(feature){
        
      var feature_id = feature.type+'_'+feature.name;
      // Split the features between the overlayers by selected v unselected:
      if (alignMapPtr.selectedItemsHash.get(feature_id))
      {
        selected_features.set(feature_id, feature);
      }
      else
      {
        temp_features.set(feature_id, feature);
      }
    });
    
    // Now draw the overlay features
    alignMapPtr.addOverlayedFeatures(selected_features,'selected_features_overlay');
    alignMapPtr.addOverlayedFeatures(temp_features,'temp_features_overlay');
    
    alignMapPtr.keepSelectedElementsSelected();
    
    alignMapPtr.initializeZoomDrag();
    
    // Add a help balloon:
    if (typeof(HelpBalloon) != 'undefined' &&
        (Prototype.Browser.IE || // IE OK
         Prototype.Browser.Gecko)) // Gecko(Firefox) OK
        //!navigator.vendor.match(/Apple/)))// not Safari
    {
      if (alignMapPtr.helpBalloon == null) 
      {
        alignMapPtr.helpBalloon = new HelpBalloon({
            returnElement: true,
            dataURL: alignMapPtr.helpPath,
            icon:    alignMapPtr.serverpath + 'images/icon_question.gif',
            altText: 'Click to view map help',
            guid:    '33333'
        });
      }

      $('mapHelp').appendChild(alignMapPtr.helpBalloon._elements.icon);

    }
    else if (typeof(popUpHelp) != 'undefined')
    {
      $('mapHelp').innerHTML =
      '<a target="_blank" href="javascript:popUpHelp(\''+alignMapPtr.helpPath+'\');">'+
            '<img border="0" src="'+alignMapPtr.serverpath+'images/icon_question.gif"></img></a>';
    }
    else // bare pop-up:
    {
      $('mapHelp').innerHTML =
      '<a target="_blank" href="'+alignMapPtr.helpPath+'">'+
            '<img border="0" src="'+alignMapPtr.serverpath+'images/icon_question.gif"></img></a>';
    }
    
    if (alignMapPtr.firstLoad)
    {
      alignMapPtr.firstLoad = false;
      alignMapPtr.raiseEvent('onFirstLoaded', alignMapPtr.map_data);
      alignMapPtr.raiseEvent('onLoaded', alignMapPtr.map_data);
    }
    else
    {
      alignMapPtr.raiseEvent('onLoaded', alignMapPtr.map_data);
    }
  },


  // Zoom Interface:

  zoomBoxStart: null,
  zoomBoxLeft:  null,
  zoomBoxRight: null,
  zoomBoxTop:   0,
  zoomBoxBottom:null,

  zoomDragMove: function(e)
  {
    e = alignMapFixE(e);

    // Keep track of the start position
    var curr_pos = e.layerX - map_x_offset;
    //curr_pos = Event.pointerX(e);
    var target = Event.element(e); 
    // If we're mousing over the span, not the image, then add span X
    if (target.tagName.toLowerCase() == 'span')
    {
      var span_x = target.getStyle('left'); 
      if (span_x === null) {span_x = '0';}
      curr_pos += parseInt(span_x);
    }
    
    if (curr_pos > zoomBoxStart)
    {
      zoomBoxLeft  = zoomBoxStart;
      zoomBoxRight = curr_pos;
    }
    else
    {
      zoomBoxLeft  = curr_pos;
      zoomBoxRight = zoomBoxStart;
    }
    
    var zoomBox = $("zoomBox");
    zoomBox.style.left = zoomBoxLeft  + "px";
    zoomBox.style.width = (zoomBoxRight - zoomBoxLeft) + "px";
  },

  zoomDragStart: function(e)
  {
    e = alignMapFixE(e);

    // Keep track of the start position
    var start_x = e.layerX - map_x_offset;
    zoomBoxStart = 
    zoomBoxLeft  = 
    zoomBoxRight = start_x;
    
    var zoomBox = $("zoomBox");
    zoomBox.show();
    zoomBox.setStyle({
                       left: (zoomBoxLeft - 3) + "px",
                       width: "1px",
                       borderColor: "#0F0",
                       opacity: 0.5 });
    
    var image = $("alignment");

    image.observe('mousemove', alignMapPtr.zoomDragMove);
    zoomBox.observe('mousemove', alignMapPtr.zoomDragMove);
    zoomBox.observe('mouseup', alignMapPtr.zoomDragStop);
    image.ondrag = function() {return false;}; // disable i.e. default dragging
    
    // Prevent the default firefox image-dragging behavior 
    if (e.preventDefault) {
       e.preventDefault();
    }
  },
  
  mapCoordToChrom: function(map_coord)
  {
    var chrom_coord;
    if (alignMapPtr.map_data.orientation == 1)
    {
      chrom_coord = Math.round(alignMapPtr.map_data.chr_left +
        ((map_coord - 110) / 465) * alignMapPtr.map_data.chr_range);
    }
    else
    {
      chrom_coord = Math.round(alignMapPtr.map_data.chr_right +
        ((465 - (zoomBoxLeft - 110)) / 465) * alignMapPtr.map_data.chr_range);
    }
    
    return chrom_coord;
  },
  
  chromCoordToMap: function(chrom_coord)
  {
    var map_coord;
    if (alignMapPtr.map_data.orientation == 1)
    {
      // Calculate based on forward orientation
      map_coord = Math.round(110 + (465 * (chrom_coord - alignMapPtr.map_data.chr_left) /
                        (alignMapPtr.map_data.chr_right - alignMapPtr.map_data.chr_left)));
    }
    else // reversed orientation:
    {
      map_coord = Math.round(110 + 465 - (465 * (chrom_coord - alignMapPtr.map_data.chr_right) /
                        (alignMapPtr.map_data.chr_left - alignMapPtr.map_data.chr_right)));
    }
    
    return map_coord;
  },
  
  zoomIn: function(e)
  {
    $("zoomBox").hide();
    
    if (!alignMapPtr.zoomEnabled)
      return;

    // Calc'late new chrom coords based on the current ones and the 
    // zoomBox they drew:
    if (alignMapPtr.map_data.orientation == 1 || alignMapPtr.map_data.orientation == 0)
    {
      old_chr_range = alignMapPtr.map_data.chr_right - alignMapPtr.map_data.chr_left;
      new_chr_left  = Math.round(alignMapPtr.map_data.chr_left +
                      ((zoomBoxLeft - 110) / 465) * old_chr_range);
      new_chr_right = Math.round(alignMapPtr.map_data.chr_left +
                      ((zoomBoxRight - 110) / 465) * old_chr_range);

      if ((new_chr_right - new_chr_left) < 200)
      {
        return;
      }

      // Push the current left/right onto the zoomstack, for use when zooming out
      alignMapPtr.zoomStack.push([alignMapPtr.map_data.chr_left,
                                  alignMapPtr.map_data.chr_right]);

      alignMapPtr.showMapRegion(alignMapPtr.map_data.species,
                                alignMapPtr.map_data.chromosome,
                                new_chr_left, new_chr_right,
                                alignMapPtr.map_data.acc,
                                alignMapPtr.map_data.acc_type,
                                alignMapPtr.map_data.orientation);

    }
    else // reversed orientation (IS THIS OBSOLETE??)
    {
      old_chr_range = alignMapPtr.map_data.chr_left - alignMapPtr.map_data.chr_right;
      new_chr_left  = Math.round(alignMapPtr.map_data.chr_right +
                      ((465 - (zoomBoxLeft - 110)) / 465) * old_chr_range);
      new_chr_right = Math.round(alignMapPtr.map_data.chr_right +
                      ((465 - (zoomBoxRight - 110)) / 465) * old_chr_range);

      if ((new_chr_left - new_chr_right) < 200)
      {
        return;
      }

      // Push the current left/right onto the zoomstack, for use when zooming out
      alignMapPtr.zoomStack.push([alignMapPtr.map_data.chr_right,
                                  alignMapPtr.map_data.chr_left]);

      alignMapPtr.showMapRegion(alignMapPtr.map_data.species,
                                alignMapPtr.map_data.chromosome,
                               new_chr_right, new_chr_left, 
                                alignMapPtr.map_data.acc,
                                alignMapPtr.map_data.acc_type,
                                alignMapPtr.map_data.orientation);
    }
  },
  
  zoomOut: function(factor)
  {
    var old_chr_range;
    var new_chr_left;
    var new_chr_right;
    
    if (alignMapPtr.zoomStack.size())
    { // The new style, zoom out to previous coords
      var left_right = alignMapPtr.zoomStack.pop();
      new_chr_left = left_right[0]; 
      new_chr_right = left_right[1];
    }
    else // The old style, zoom out by a multiple
    {
      if (alignMapPtr.appName != 'SNP' &&
          alignMapPtr.appName != 'CNV')
        return;//Ignore zoom out beyond initial map for non-GT maps
      factor = 1;
      alignMapPtr.map_data.acc = '';//clear the acc, to view more
      old_chr_range = alignMapPtr.map_data.chr_right - alignMapPtr.map_data.chr_left;
      new_chr_left  = alignMapPtr.map_data.chr_left  - (old_chr_range * factor);
      new_chr_right = alignMapPtr.map_data.chr_right + (old_chr_range * factor);
      // Handle some boundary cases
      if (new_chr_left < 0) new_chr_left = 0;
      if (new_chr_right - new_chr_left > 10000000) return;
    }
    
    alignMapPtr.showMapRegion(alignMapPtr.map_data.species,
                              alignMapPtr.map_data.chromosome,
                              new_chr_left, new_chr_right,
                              alignMapPtr.map_data.acc,
                              alignMapPtr.map_data.acc_type,
                              alignMapPtr.map_data.orientation);
  },
  
  zoomDragStop: function(e)
  {
    $("alignment").onmousemove = function() {return false;};
    var zoomBox = $("zoomBox");
    zoomBox.onmousemove = function() {return false;};
    
    if ((zoomBoxRight - zoomBoxLeft) < 5)
    {
      zoomBox.hide();
      return; // Do nothing if it's too small a space
    }
    else
    {
      alignMapPtr.zoomIn();
    }
    if (true)// this is what we used to do. kept in case we go back:
    {
      //zoomBox.setStyle({borderColor: "#30E"});
      //zoomBox.onmousedown = alignMapPtr.zoomIn;
      
      alignMapPtr.raiseEvent("map_region_highlighted", 
                      [ alignMapPtr.map_data.chromosome,
                        alignMapPtr.mapCoordToChrom(zoomBoxLeft),
                        alignMapPtr.mapCoordToChrom(zoomBoxRight)]);
    }
    
  },

  initializeZoomDrag: function()
  {
    var image = $("alignment");
    
    if (image === null) {return 0;}
    
    // Find the x and y offset of our host element
    var host_element = $(host_element_id);

    var left_str = host_element.getStyle('left');
    if (left_str === null) left_str = '0';
    map_x_offset = parseInt(left_str);//(document.layers) ? host_element.x : host_element.offsetLeft;
    map_y_offset = parseInt(host_element.getStyle('top'));//(document.layers) ? host_element.y : host_element.offsetTop;
    
    image.onmousedown = this.zoomDragStart;
    image.onmouseup   = this.zoomDragStop;
    
    this.enableZoomDrag();
  },
  
  disableZoomDrag: function()
  {
    alignMapPtr.zoomEnabled = false;   
  },
  
  enableZoomDrag: function()
  {
    alignMapPtr.zoomEnabled = true;   
  },
  
  clearZoomStack: function()
  {
    alignMapPtr.zoomStack = [];
  },
  //////////////////////////////////////////////////////////////////////////////
  setEventHook: function(event_name, javascript_func)
  {
    this.eventHandlerMap[event_name] = javascript_func;  
  },// End of setHook()
  

  //////////////////////////////////////////////////////////////////////////////
  raiseEvent: function(event_name, parameter, optional_param)
  {
    var function_ptr = this.eventHandlerMap[event_name];
    if (function_ptr)
    {
      function_ptr(parameter, optional_param);
    }
  },// End of raiseEvent()

  getCookieParams: function()
  {
    var params = '&show_mRNAs=' + cookieVal("show_mRNAs") +
                 '&show_silencersiRNAs=' + cookieVal("show_silencersiRNAs") +
                 '&show_siSelectsiRNAs=' + cookieVal("show_siSelectsiRNAs") +
                 '&show_assays=' + cookieVal("show_assays");
                 
    return params;
  },
  
  elementClicked: function (element_type, element_acc, optional_arg)
  {
    if (element_type == "Switch")
    {
      if (element_acc == "ZoomOut")
      {
        this.zoomOut(optional_arg);  
      }
      else if (element_acc == "mRNASwitch")
      {
        if (cookieVal("show_mRNAs") == "allmRNA")
        {
          this.hideGBmRNAs();
        }
        else
        {
          this.showGBmRNAs();
        }
    
        this.reloadMap();  
      }
      else if (element_acc == "silencersiRNASwitch")
      {
        if (cookieVal("show_silencersiRNAs") == "yes")
        {
          this.hideSilencersiRNAs();
          this.raiseEvent('display_flag_set', 'silencersiRNAs', 'no');
        }
        else
        {
          this.showSilencersiRNAs();
          this.raiseEvent('display_flag_set', 'silencersiRNAs', 'yes');
        }

        this.reloadMap();
      }
      else if (element_acc == "siSelectsiRNASwitch")
      {
        if (cookieVal("show_siSelectsiRNAs") == "allsiRNA")
        {
          this.hideAdditionalSiSelectsiRNAs();
          this.raiseEvent('display_flag_set', 'siSelectsiRNAs', 'topsiRNAs');
        }
        else
        {
          this.showAdditionalSiSelectsiRNAs();
          this.raiseEvent('display_flag_set', 'siSelectsiRNAs', 'allsiRNAs');
        }

        this.reloadMap();
      }
      else if (element_acc == "AssaySwitch")
      {
        if (cookieVal("show_assays") == "allAssays")
        {
          this.hideAdditionalGeXassays();
          this.raiseEvent('display_flag_set', 'GeXassays', 'BestGex');
        }
        else
        {
          this.showAdditionalGeXassays();
          this.raiseEvent('display_flag_set', 'GeXassays', 'allAssays');
        }
    
        this.reloadMap();    
      }
      else
      {
        alert ("unknown Switch type clicked");
      }
    }
    else // If it wasn't a Switch, it must be a feature:
    {
      // Raise the Event with the element type as subject
      // (The subject might ought to be "alignMap_<type>_clicked"...)
      this.raiseEvent(element_type, element_acc, optional_arg);
    }
    
  }, //
  
  showAdditionalGeXassays: function()
  {
    document.cookie = "show_assays=allAssays;path=/";
  },
  
  hideAdditionalGeXassays: function()
  {
    document.cookie = "show_assays=BestGex;path=/";
  },
  
  showSilencersiRNAs: function()
  {
    document.cookie = "show_silencersiRNAs=yes;path=/";
  },
  
  hideSilencersiRNAs: function()
  {
    document.cookie = "show_silencersiRNAs=no;path=/";
  },
  
  showAdditionalSiSelectsiRNAs: function()
  {
    document.cookie = "show_siSelectsiRNAs=allsiRNA;path=/";
  },
  
  hideAdditionalSiSelectsiRNAs: function()
  {
    document.cookie = "show_siSelectsiRNAs=topsiRNA;path=/";
  },
  
  showGBmRNAs: function()
  {
    document.cookie = "show_mRNAs=allmRNA;path=/";
  },
  
  hideGBmRNAs: function()
  {
    document.cookie = "show_mRNAs=refseq;path=/";
  },
  
  popup: function()
  {
    var hostElement = document.getElementById(host_element_id);
    var imageElement = document.getElementById("alignment");

    var popupElement = document.createElement("div");
    
    var oldImageOpacity = imageElement.style.opacity;
    
    imageElement.style.opacity = "0.2";
        
    popupElement.className = "popupTest";    

    var popupText = "Overlaid Control Box\nThis should be a non-faded "+
                    "box over a faded image.";
    popupElement.appendChild(document.createTextNode(popupText));

    hostElement.appendChild (popupElement);
    
    alert ("test of control box");
    
    hostElement.removeChild (popupElement);
    imageElement.style.opacity = oldImageOpacity;
      
  },
  
  // This highlight is mainly for rollovers:
  highlight: function (element_id)
  {
    if ((element = $(element_id)) === null) { return; }
    
    //highlightID tells it to highlight another element in addition
    if ((highlightID = element.readAttribute('highlightID')) !== null)
    {
      element.setStyle({opacity: 0.5});
      element = $(highlightID);
    }
    
    element.setStyle({opacity: 0.5, background:'#ee2'});

    var related = element.readAttribute('relatedIDs');
    if (related !== null)
    {
      related.split(',').each(function(elem_id){
        var elem = null;
        if ((elem = $(elem_id)) !== null)
          elem.setStyle({opacity: 0.5, background:'#ee2'});
      });
    }
  },

  // This unhighlight is mainly for rollovers:
  unHighlight: function (element_id)
  {
    
    if ((element = $(element_id)) === null) { return; }
    
    //highlightID tells it to (un)highlight another element in addition
    if ((highlightID = element.readAttribute('highlightID')) !== null)
    {
      element.setStyle({opacity: 0});
      element = $(highlightID);
    }
    
    // Don't unHighlight *selected* items, they should stay highlighted
    if (!this.selectedItemsHash.get(element_id))
    {
      element.setStyle({opacity: 0});
    }
    else // it is selected, so keep it so
    {
      alignMapPtr.drawSelected(element_id);
    }
    
    
    var related = element.readAttribute('relatedIDs');
    if (related !== null)
    {
      related.split(',').each(function(elem_id){
        var elem = null;
        if ((elem = $(elem_id)) !== null)
        {
          if (!alignMapPtr.selectedItemsHash.get(elem_id))
          {
            elem.setStyle({opacity: 0});
          }
          else // it is selected, so keep it so
          {
            alignMapPtr.drawSelected(elem_id);
          }
        }
      });
    }

  },

  // A wee wrapper:
  getFeature: function(feature_id)
  {
    return $(feature_id);
  },
  
  unselectAll: function()
  {
    this.selectedItemsHash.keys().each(function(key) {
        this.selectedItemsHash.unset(key);
        this.unHighlight(key);
        this.moveFeatureOverlay(key, 'selected_features_overlay',
                                       'temp_features_overlay');
    }.bind(this));
        
    this.selectedItemsHash = $H({});
    this.sessionData.selectedItems = "";
    this.saveSessionData();
  },

  
  unselect: function(element_id)
  {
    this.selectedItemsHash.unset(element_id);
    this.unHighlight(element_id);
    this.moveFeatureOverlay(element_id, 'selected_features_overlay',
                                          'temp_features_overlay');

    this.saveSessionData();
  },
  
  // Set, not toggle:
  setSelected: function (element_ids)
  {
    element_ids.each(function(element_id){
      if (this.selectedItemsHash.get(element_id))
      {
        return; // Do nothing, it's already selected
      }

      // Otherwise, select it:
      this.selectedItemsHash.set(element_id, 1);

      // If this element_id isn't on the map:
      if ($(element_id) === null)
      {
        // Try showing additional siRNAs or GeXassays:
        var switch_changed = false;
        if (element_id.match(/siRNA/))
        {
          this.showAdditionalSiSelectsiRNAs();
          this.showSilencersiRNAs();
          switch_changed = true;
        }
        else if (element_id.match(/GeXassay/))
        {
          this.showAdditionalGeXassays();
          //this.showGBmRNAs(); <-- No longer an issue
          switch_changed = true;
        }

        if (switch_changed)
          this.reloadMap();

        return;
      }
      
      this.drawSelected(element_id);
      
      // If this is selecting a temp_overlay feature, move it 
      // to the selected overlay:
      this.moveFeatureOverlay(element_id, 'temp_features_overlay',
                                            'selected_features_overlay');

    }.bind(this));

    // Set a cookie to reflect the current status
    var cookie_text = Object.toJSON(this.selectedItemsHash);
    
    this.sessionData.selectedItems = cookie_text;
    this.saveSessionData();
  },

  unselect: function(element_id)
  {
    this.selectedItemsHash.unset(element_id);
    this.unHighlight(element_id);
    
    this.saveSessionData();
  },

  toggleSelected: function (element_id)
  {

    // If this element ID is one of our selected ones...
    if (this.selectedItemsHash.get(element_id))
    {
      // Remove it and unhighlight it
      this.selectedItemsHash.unset(element_id);
      this.unHighlight (element_id);
    }
    else // If it is not selected, then make it so:
    {
      this.selectedItemsHash.set(element_id, 1);
      this.drawSelected(element_id);
    }

    // Set a cookie to reflect the current status
    var cookie_text = Object.toJSON(this.selectedItemsHash);
    
    this.sessionData.selectedItems = cookie_text;
    this.saveSessionData();
  },

  drawSelected: function (element_id)
  {
    var element = $(element_id);

    if (element === null)
    {
      return;
    }
    
    element.setStyle({
                      border:'1px solid #777',
//                      background:"#afc",
                      background:"#1CDBE6",//"#99ffff",
                      opacity: 0.5});
  },

  // The structure for the feature_list is:
  // [
  //   {id:,species:,chromosome:,start:,stop:,fill_color:,border_color:},
  //   . . .
  // ]
  //
  addOverlayedFeatures: function (feature_list, overlay_name, purgeOld)
  {
    // We will maintain the list of elements, and redraw when we get a new map
    if (feature_list.keys().size() === 0) 
    {
      return;
    }
    
    if (typeof overlay_name == 'undefined' || overlay_name === '')
    {
      overlay_name = 'temp_features_overlay';
    }
    
    $('zoomBox').hide();
    
    $(overlay_name).innerHTML = "";
    
    // Remove any non-selected features from the overlayFeatures:
    if (purgeOld == 'purgeOld')
    {
      alignMapPtr.sessionData.overlayFeatures.each(function(entry){
          if (typeof(alignMapPtr.selectedItemsHash.get(entry.key)) == 'undefined')
            alignMapPtr.sessionData.overlayFeatures.unset(entry.key);
      });
    }
    
    var html = '';
    
    // Draw each feature
    $H(feature_list).values().each(function(feature)
      {
        if (feature.chromosome != alignMapPtr.map_data.chromosome)
        {
          return;
        }
        
        var feature_name = feature.type+'_'+feature.name;
        var map_left  = alignMapPtr.chromCoordToMap(feature.start);
        var map_right = alignMapPtr.chromCoordToMap(feature.stop);
        
        // Don't draw items outside the map
        if (map_left < 0) {map_left = 0;}
        if (map_right < 0) {return;}
        if (map_left > 600) {return;}
        if (map_right > 600) {map_right = 597;}
        
        var feature_height = 5;
        var feature_top = 33;
        var feature_width = Math.round(map_right - map_left) + 1;
        var feature_style = "position:absolute;top:" +
                    feature_top + "px;left:" + map_left +
                    "px;height:" + feature_height +
                    "px;width:" + feature_width + "px;" +
                    "background:" + feature.fill_color + 
                    ";border:1px solid "+feature.outline_color +
                    "; opacity:0.8;font-size: 0px;z-index:0;";
        var center_xoffset = Math.round(((map_right - map_left) / 2) - 3);
        var my_span = $span({
            id:    feature_name + "_shape",
            style: feature_style}
        );
        
        $(overlay_name).appendChild(my_span);
        
        var my_img = $img({
                id:    feature_name + "_img",
                style: "position:absolute;top:"+(feature_top-2)+
                       "px;left:"+(map_left+center_xoffset)+
                       "px;z-index:1;",
                src: "../../webApps/shared/images/target.png"
            });
        
        $(overlay_name).appendChild(my_img);
        
       var highlight_style = "position:absolute;top:" +
                    (feature_top - 3) + "px;left:" + (map_left - 3) +
                    "px;height:" + (feature_height + 6) +
                    "px;width:" + (feature_width + 6) + 
                    "px;z-index:2;";
                    
       // var highlight_span = $span({
           // id:  feature_name,
           // 'class':     'highlight',
           // style:        highlight_style,
           // onmouseover: 'javascript:_mapHighlight(\''+feature_name+'\');',
           // onmouseout:  'javascript:_mapUnHighlight(\''+feature_name+'\');',
           // onclick:     'javascript:alignMapPtr.overlayFeatureClicked(\''+feature_name+'\');',
           // title:        feature_name
           // });
    
        var span_html = '<span id="'+feature_name+'" title="'+feature_name+'" class="highlight" style="'+highlight_style+
            '" onmouseover="javascript:_mapHighlight(\''+feature_name+'\');"'+
            ' onmouseout="javascript:_mapUnHighlight(\''+feature_name+'\');"'+
            ' onclick="javascript:alignMapPtr.overlayFeatureClicked(\''+feature_name+'\');"'+
            '></span>';
            
    
        //$(overlay_name).appendChild(highlight_span);
        $(overlay_name).innerHTML += span_html;
        // Make it draggable via domdrag.js
        //Drag.init($("map_overlay_" + region[0]), null, 0,600,feature_top,feature_top);
        alignMapPtr.sessionData.overlayFeatures.set(feature_name, feature);
                                              
      }
    );
    
    // Save our list of overlay features in the session
    this.saveSessionData();
    
  },

  // This function looks up the overlay feature and sends it in the event
  overlayFeatureClicked: function(feature_name)
  {
    this.raiseEvent('OverlayFeature', feature_name, 
                     alignMapPtr.sessionData.overlayFeatures.get(feature_name));
    
    // If they click on a temp_overlay feature, move it 
    // to the selected overlay:
    if ($(feature_name).childOf('temp_features_overlay'))
    {
      this.moveFeatureOverlay(feature_name, 'temp_features_overlay',
                                              'selected_features_overlay');
    }
    else if ($(feature_name).childOf('selected_features_overlay')) 
    {
      this.moveFeatureOverlay(feature_name, 'selected_features_overlay',
                                              'temp_features_overlay');
    }
    
  },
  
  moveFeatureOverlay: function(feature_name, from_overlay, to_overlay)
  {
    if ($(feature_name) == null) return;
    // Move the features from one overlay to the other:
    if ($(feature_name).childOf(from_overlay))
    {
      $(to_overlay).appendChild($(feature_name).remove());
      $(to_overlay).appendChild($(feature_name + "_shape").remove());
      $(to_overlay).appendChild($(feature_name + "_img").remove());
    }
  },

  
  showColorBar: function (caption, left, right, colors)
  {
    colors = $A(colors);
    var chrom_width = right - left;
    var bar_start = alignMapPtr.chromCoordToMap(left);
    var bar_end   = alignMapPtr.chromCoordToMap(right);
    var bar_top   = alignMapPtr.map_data.features_bottom;
    var bar_width = bar_end - bar_start;
    var div_width = bar_width / colors.length;
    var index = 0;
    var colorBar_container = $('colorBar_overlay'); 
    colorBar_container.innerHTML = '';
    
    var caption_span = $span({style:'position:absolute;top:'+(bar_top - 13) +
                                     'px;left:20px;font-size:11px;'},
                             caption);
    colorBar_container.appendChild(caption_span);
    
    colors.each(function (color){
     
     var bar_left = bar_start + (index * div_width);
     var highlight_style = "position:absolute;top:" +
                  (bar_top - 7) + "px;left:" + bar_left +
                  "px;height:4px;width:" + div_width + 
                  "px;z-index:2;font-size:2px;background:#"+color+";";
                  
     var color_block = $span({
         style:        highlight_style
         });
      
      colorBar_container.appendChild(color_block);
      
      index++;
    })
  },
  
  restorePreviousMapAndSelections: function ()
  {
  // If the user has session data for selected items, load them up again on reload:
    var sessionDataString = this.sessionPtr.getParam("alignMapSessionData");
    if (sessionDataString !== '')
    {
      this.sessionData = sessionDataString.evalJSON();
    }
    // Prototype-ize this
    alignMapPtr.sessionData.overlayFeatures = 
    $H(alignMapPtr.sessionData.overlayFeatures);
    
    // Whoa, JSON inside of JSON.  Will it work?
    var selected_items = this.sessionData.selectedItems;
    if (selected_items !== null && selected_items !== "")
    {
      this.selectedItemsHash = $H(selected_items.evalJSON());
    }
    
      // Remove unselected features
    alignMapPtr.sessionData.overlayFeatures.values().each(function(feature){
      var feature_id = feature.type+'_'+feature.name;
      if (!alignMapPtr.selectedItemsHash.get(feature_id))
      {
        alignMapPtr.sessionData.overlayFeatures.unset(feature_id);
      }
    });

  //NOTE - WE LET THE OTHER PLUGINS TELL US WHAT TO SHOW. (???)
  // We remember the selected items, though.

  }
  
};


// The following does some cross-browser massaging of the event object:
function alignMapFixE(e)
{
  if (typeof e == 'undefined') {e = window.event;}
  if (typeof e.layerX == 'undefined') {e.layerX = e.offsetX;}
  if (typeof e.layerY == 'undefined') {e.layerY = e.offsetY;}
  return e;
}

function _mapHighlight (element_id)
{
  alignMapPtr.highlight(element_id);
}

function _mapUnHighlight (element_id)
{
  alignMapPtr.unHighlight(element_id);
}


function _mapElementClicked (element_type, element_acc, optional_arg)
{
  alignMapPtr.elementClicked(element_type, element_acc, optional_arg);
}


function cookieVal (cookieName)
{
  thisCookie = document.cookie.split("; ");
  for (i=0; i <thisCookie.length; i++)
  {
    if (cookieName == thisCookie[i].split("=")[0])
    {
      return thisCookie[i].split("=")[1];
    }
  }

  return '';
}

