//
// Globals
//
ShowHide.default_styles = ["shown", "hidden"]
ShowHide.default_masterstyles = ["showing", "hiding"]

// Constructor
function ShowHide(node, udata) {
   this.setNode(node)
   this.setUdata(udata)

   // Define properties
   this.defineProperty("current", 0)
   this.defineProperty("masterStyle", "")

   // Create targets array
   this.targets = new Array()
   this.stylevec = new Array()

   // Init values
   this.init(node, udata)

   // Hook up a reporter
//   this.myReporter = new Reporter(this, "masterStyle")
}

subClass(ShowHide, JsComponent)

// Set target nodes for this object. If the argument is an
// array, just set the array. If it's a string, assume it's a list
// of IDs and iterate them.
ShowHide.prototype.setTargets = function(targetNodes) {

  if (typeof(targetNodes) == 'string') {
      if (this.targets.clear) {
         this.targets.clear()
      } else {
         alert("Type of targets is " + dltypeof(this.targets))
         var p
         p = ""
         for (var q in this.targets) {
             p = p + q + "='" + this.targets[q]+"'\n"
         }
         alert(p)
      }
      this.stylevec.clear()

      var tn = targetNodes.split(" ")
      for (var i = 0; i < tn.length; i++) {
          var nn = document.getElementById(tn[i])
          if (nn != null) {
             this.addTarget(nn)
          } 
      }
   } else if (typeof(targetNodes) == 'array') {
      this.targets.clear()
      this.stylevec.clear()
      for (i = 0; i < targetNodes.length; i++) {
         this.addTarget(targetNodes[i])
      }
   }
}

// Return an array of target nodes
ShowHide.prototype.getTargets = function() {
   return this.targets
}

ShowHide.prototype.addTarget = function(node) {
   var v
   this.targets.push(node)
   v = this.getInitialStyle(node)
   this.stylevec.push(v)
   addClass(node, this.styles[v])
}

ShowHide.prototype.removeTarget = function(node) {
   for (var i = 0; this.targets && i < this.targets.length; i++) {
      if (this.targets[i] == node) {
         this.targets.splice(i, 1)
         this.stylevec.splice(i, 1)
         return true
      }
   }
   return false
}

ShowHide.prototype.isTarget = function(node) {
   for (var i = 0; this.targets && i < this.targets.length; i++) {
      if (this.targets[i] == node) {
         return true
      }
   }
   return false
}

// Initialize an instance
ShowHide.prototype.init = function(node, data) {
   var i

   // Instead of setting the "display" attribute, this component
   // cycles setting a ring of style names each time the node is
   // clicked.  By default, these styles are the classes "shown" and
   // "hidden", but there may be more than two, and they may be
   // anything. Specify them in the source document by listing them in
   // attr "showhideStyles" as a list of space-separated strings.
   var styles = node.getAttribute("showhideStyles")
   if (styles == null) {
       this.styles = ShowHide.default_styles
   } else {
       this.styles = styles.split(" ")
   }
   this.styledict = new Object()

   // Create index of style names to order in ring.
//   alert("styles = " + this.styles +", length = " + this.styles.length)
   for (i = 0; i < this.styles.length; i++) {
       this.styledict[this.styles[i]] = i
   }

   // The control element ("master") is this node: the one that
   // receives the event. It also has a style ring, called
   // "masterstyles". By default, these styles are called "showing"
   // and "hiding", but there may be more than two, and they may be
   // anything. Specify them in the source document by listing them in
   // attr "showhideMasterstyles" as a list of space-separated strings
   var masterstyles = node.getAttribute("showhideMasterstyles")
   if (masterstyles == null) {
       this.masterstyles = ShowHide.default_masterstyles
   } else {
       this.masterstyles = masterstyles.split(" ")
   }

   // Initialize style of this node. If the node has a class matching somethign
   // in masterstyles, then we set the index to correspond to that style. If more than
   // one, first one wins

   var classes

   // If one of the classes on this node matches a class in "masterstyles", set the
   // "masterStyle" property to its name. This also automatically updates "current"
   if (classes = getClassList(node)) {
      for (i = 0; i < classes.length; i++) {
         if (this.masterstyles.indexOf(classes[i]) >= 0) {
            this.setMasterStyle(classes[i])
         }
      }
   } 

   // If the node has "showhideTargets" attribute, look up the
   // targets and assign them
   var n = node.getAttribute("showhideTargets")

   if (n) {
      this.setTargets(n)
   }

}

// Indicates whether this module is usable in this browser
ShowHide.usable = function() {
   return true
}


//
// ======== Class methods
//

// Class method: usable--This method indicates whether the class is usable in
// the current browser.
ShowHide.usable = function() {
    return true
}

// This method indicates whether this component applies to the given node.
// This component applies only to elements. (As do all components, I think)
ShowHide.applicable = function(node) { 
   return hasAttribute(node, "showhideTargets")
}

//
// ========== Instance methods
//

ShowHide.prototype.currentStyle = function(i) {
   return this.styles[this.stylevec[i]]
}

ShowHide.prototype.nextStyle = function(i) {
   this.stylevec[i] = (this.stylevec[i] + 1) % this.styles.length
   return this.currentStyle(i)
}

function nodeString(node) {
   if (node) {
      if (node.firstChild) {
         if (node.firstChild.nodeValue) {
            return node.firstChild.nodeValue
         }
      } else {
         return typeof(node)
      }
   } else {
      return node.toString()
   } 
}

// Return the "initial style" of a target.
ShowHide.prototype.getInitialStyle = function(node) {

   var classes = getClassList(node)
//   _dbgWrite("Classes = " + (classes ? classes.toSource() : "[]") +"<br/>")

   // If anything in the style ring matches a name in the class list,
   // initialize current style in stylevec. If more than one, first one
   // in style list wins.
   for (j = 0; classes && j < this.styles.length; j++) {
      if (classes.indexOf(this.styles[j]) >= 0) {
//        _dbgWrite("getInitialStyle: " + j +"<br/>")
        return j
      }
   }
   return 0 /* Default style is first in target ring */
}

// Handle click event. This means that for each target,
// replace the current style with the next one.
// Note that each target has its own place in the style "ring"
//
// The showhide control element (the one to which this object
// is bound, and that processes the click event)
// keeps track of its own place in the style "ring", and also
// receives a style! It also automatically gets the style
// "showhidemaster". So to change the presentation of
// the control element when it changes state, define styles:
//
// .showhidemaster.shown {
//   background-color: blue;
// }
//
// .showhidemaster.hidden {
//   background-color: gray;
// }
//
// Example:
// <a href="#" showhideStyles="a b c" showhideTargets="a1 b1 c1">click</a>
// <span class="a">a</span><span class="b">b</span><span class="c">c</span>
//
// With two consecutive clicks,
// a''s style will go a->b->c, b''s will go b->c->a, and c''s will go c->a->b.
//
// FIXME: Currently this forces all targets to have the same style. Actually
// each target should be able to start anywhere in the
// cycle and advance one each time, allowing a single click
// to show some nodes and hide others. Hmm....
//
ShowHide.prototype.click = function(node, event, data) {
   var target
   var i
   var data = this.getUdata()

//   _dbgWrite("Node class attribute is '" + getClassAttribute(node) +"'<br />")

   this.setCurrent((this.getCurrent() + 1) % this.masterstyles.length)

   // Change styles of targets
   if (this.targets) {
      for (i = 0; i < this.targets.length; i++) {
          thisStyle = this.currentStyle(i)
          nextStyle = this.nextStyle(i)
          if (!hasClass(this.targets[i], thisStyle)) {
//             _dbgWrite("Adding class " + nextStyle + " for " + nodeString(this.targets[i]) +"<br/>")
             addClass(this.targets[i], nextStyle)
          } else {
//             _dbgWrite("Replacing class " + thisStyle + " to " + nextStyle + " for " + nodeString(this.targets[i]) +"<br/>")
             replaceClass(this.targets[i], thisStyle, nextStyle)
          }
      }
   }

   // Return false for links (so the link is not followed), and return
   // true for everything else (so it doesn''t interfere with controls)
   return node.tagName.toLowerCase() != 'a'
}

// Disallow text selection
ShowHide.prototype.mousedown = function (node, event) {
   return false
}

// Disable text selection in IE
ShowHide.prototype.selectstart = function (node, event) {
   return false;
}

// Property setter for "current" style for this node.
// Setting this node also updates "master style"
ShowHide.prototype.setCurrent = function (i) {
   var thisStyle, nextStyle

   // Tell listeners that this property is about to change
   this.notifyPropertyChange("current", this.current, i)

   // Change style for control element
   thisStyle = this.masterstyles[this.current]
   this.current = i  // Change property
   nextStyle = this.masterstyles[i]

   // Change class on node for this object after notifying
   // listeners that this property is about to change
   this.notifyPropertyChange("masterStyle", thisStyle, nextStyle)
   replaceClass(this.getNode(), thisStyle, nextStyle)
}

// Set "master style", and update "current" to reflect value
ShowHide.prototype.setMasterStyle = function (s) {
   var thisCurrent, nextCurrent

   thisCurrent = this.getCurrent()

   // Get "next" value of "current", which is the index
   // of "s" in the "masterstyles" array.
   nextCurrent = this.masterstyles.indexOf(s)
   if (nextCurrent < 0) {
      alert("Can't set master style to '" + s + "': no such style")
      return
   }

   // Set master style
   this.notifyPropertyChange("masterStyle", this.masterstyles[thisCurrent], s)
   this.masterStyle = s

   // Set current to reflect value
   this.notifyPropertyChange("current", thisCurrent, nextCurrent)
   this.current = nextCurrent
}

function Reporter(obj, propname) {
   obj.addPropertyChangeListener(propname, this)
   this.tracking = obj
   this.trackingname = propname
}

Reporter.prototype.onPropertyChange = function(obj, name, oldVal, newVal) {
//   _dbgWrite("Changed property " + name +" on " + dltypeof(obj) + " from '" + oldVal + "' to '" + newVal + "'<br />")
}

Reporter.prototype.disable = function() {
   this.tracking.removePropertyChangeListener(this.trackingname, this)
}

// The following stylesheet defines the standard styles "shown" and "hidden"
document.addStyleSheet("/entrez/query/Gene/showhide.css")
//registerTag("A","click mousedown","ShowHide","TagdecoratedwithShowHide")
//registerTag("DIV","click mousedown","ShowHide","DIVdecoratedwithShowHide")
//registerTag("INPUT","click mousedown","ShowHide","INPUTdecoratedwithShowHide")
//registerTag("H1","click mousedown","ShowHide","INPUTdecoratedwithShowHide")

