//-----------------------------------------------------------------------
// Module name   : GtContentExpand
// Author        : Paul Battersby
// Creation Date : June 23/09
//  This provides a mootools animation for expanding a thumbnail into
//  a larger image.
//
//  Usage:
//    var imgExpand = new GtContentExpand();
//    imgExpand.setupMouseover($$(".thumb"));
//
//                   OR
//
//    imgExpand.setupClick($$(".thumb"));
//
//  This class can be configured in any of the following ways
//    - large image appears near top left of scrolled window and disappears
//      (large image does not obscure thumbnail)
//    - large image appears within a specific target bounding box, the box is resized
//      to accomodate the large image
//
//    - display of large images can be triggered by mouseover or click
//
//  $Log: gtContentExpand.js $
//  Revision 1.11  2009-08-07 09:29:33-04  Battersby
//  - was not determining image path because I only moved that code inside one
//  if statement
//
//  Revision 1.10  2009-08-07 09:00:27-04  Battersby
//  - renamed to GtContentExpand
//  - can now handle html as well as just images
//  - clicking same thumb twice, toggles expanded display
//
//  Revision 1.9  2009-07-08 21:52:46-04  Battersby
//  - fixed small problem with mouseout being triggered when mouse still over border
//  of large image
//
//  Revision 1.8  2009-07-08 14:44:01-04  Battersby
//  - since overlap problem is fixed, full sized image now starts on top of thumb and
//  same size as thumb
//
//  Revision 1.7  2009-07-07 10:24:13-04  Battersby
//  - improved the timing of setting/clearing of activeThumb
//
//  Revision 1.6  2009-07-06 16:02:11-04  Battersby
//  - fixed problem of large image overlapping thumb and triggering mouseout
//
//  Revision 1.5  2009-07-06 13:59:02-04  Battersby
//  - made hide() a public function
//
//  Revision 1.4  2009-06-24 22:25:12-04  Battersby
//  - corrected bug where image was not placed properly in target element when
//  target element was automatically centered each time it is resized
//
//  Revision 1.3  2009-06-24 18:41:32-04  Battersby
//  - thumb name can now have "." in the same before the extension
//  - corrected bug where a scoll amount of non 0 kept getting repeatedly added
//  so that the large image kept creaping down the screen on each subsequent
//  redisplay
//  - large image expands from a size of 0 when not using a target (this removed
//  problem of large image able to overlap small image and causing an immediate
//  mouseout
//  - no longer morphing opacity on expansion
//
//  Revision 1.2  2009-06-24 10:35:32-04  Battersby
//  - added onload for asset image load otherwise img width
//  may not be known in time for img morph
//
//  Revision 1.1  2009-06-24 10:18:09-04  Battersby
//  Initial revision
//
//------------------------------------------------------------------------

//---------------------------- INCLUDE FILES -----------------------------

var GtContentExpand = new Class({

//----------------------------- CONSTANTS --------------------------------
  CLOSE_IMG_RADIUS : 15, // width of the close image radius

//----------------------------- VARIABLES --------------------------------
  Implements: [Events, Options],

  options : {
    border : "1px solid #000000",
    background : "#ffffff",
    suffix : "_thumb", // suffix (just prior to file extension) that distinguishes
                       // the large image from the thumbnail image
    suffixAct : "remove", // "remove" indicates that suffix needs to be removed to
                          //  get large image name
                          // "add" indicates that suffix needs to be added to
                          //  get large image name
    targetElement : null, // instead of the image being placed at the top
                          //  left of the brower window, this specifies a
                          //  target element where the large image is to be displayed
    zIndex : 2,           // zIndex of the image cell so that it will appear ontop of the rest of the
                          //  web site
    windowOffsetX : 15,   // offset from the top left corner of the scrolled window
    windowOffsetY : 15,   //  if not using targetElement

    closeImgPath  : "gtContentExpand/closebox.gif", // location of the close "X" image
                                                    //  used when clicking thumbnails
    contentType : "image", // type of content, one of {"image","html"}

    htmlFileProperty : "file", // element property name that contains the name of the
                               //  file that contains the html content to be inserted

    // use this if all html content has the same dimensions
    //  each thumb can expand to it's own unique size by setting
    //  a property like this fileWidth="300" fileHeight="250"
    //  assuming htmlFileProperty is set to "file".
    htmlWidth  : 300,  // default width  of html box
    htmlHeight : 200,   // default height of html box

    autoMouseout : true // if mouseout event is missed, a periodic check of the mouse
                        //  position will be made to see if it is no longer over the
                        //  thumb or the display cell
  },

  dispCell : null, // div that holds the expanded img
  img : null,     // the expanded image
  closeImg : null,
  targetElementBorder : 0, // size of border around target element
  endPos : null, // object with x, y of location on screen where large image will appear
  activeThumb : null, // indicates the currently activated thumb image


//----------------------------- FUNCTIONS --------------------------------

  //************************************************************************
  // Name   : _getLargeImgName
  //  Given the name of the thumbnail image, this determines the name of the
  //  large image
  //
  //  (string) thumbName - name of the thumb nail image
  //
  // Returns : (string) large image name with suffix added or removed as
  //                    configured by the options
  //************************************************************************
  _getLargeImgName : function(thumbName) {

    // if the suffix is to be removed to get the large image name
    if (this.options.suffixAct == "remove") {
      return thumbName.replace(this.options.suffix,"");

    // suffix is to be added to get the large image name
    } else {
      // find the extension
      var extensionPos = thumbName.lastIndexOf(".");

      // insert the suffix between the name and the extension
      return thumbName.substr(0,extensionPos) + this.options.suffix + thumbName.substr(extensionPos);
    } // endif
  },

  //************************************************************************
  // Name   : hide
  //  This hides the expanded content
  //
  // POST : this.activeThumb has been set to null
  //
  // Returns : (nothing)
  //************************************************************************
  hide : function() {

    // only do this if the image is actually visible
    if (this.dispCell.getStyle("display") == "block") {

      // if there is a close button, hide it first
      if (this.closeImg) {
        this.closeImg.setStyle("display","none");
      } // endif

      // reduce the opacity and render the display cell invisible
      this.dispCell.morph({
        opacity : [1,0]
      }).get("morph").chain(function() {
        this.dispCell.setStyle("display","none");

        // if displaying an image
        if (this.options.contentType == "image") {
          this.img.setStyle("display","none");

        // if displaying html
        } else {
          // empty the cell
          this.dispCell.innerHTML = "";
        } // endif

        // record that there is no longer an active thumb image
        this.activeThumb = null;

      }.bind(this));
    } // endif

  },

  //************************************************************************
  // Name   : _insertImageInTarget
  //  This inserts the desired image, and morphs the target to the image size
  //  then reveals the image
  //
  //  (element) image    - the image element to be inserted
  //  (string) imagePath - path to the image file
  //
  // Returns : (nothing)
  //************************************************************************
  _insertImageInTarget : function(image,imagePath) {
    var endPos;

    // insert the image
    this.img.setProperty("src",imagePath);

    // morph to the size of the large image
    this.options.targetElement.morph({
      width  : image.width,
      height : image.height
    }).get("morph").chain(function() {

      // make it displayable, but still invisible
      this.img.setStyle("display","block");

      // recalculate this since the target element has been resized
      endPos = this.options.targetElement.getPosition();
      endPos.x += this.targetElementBorder - this.dispCellBorder;
      endPos.y += this.targetElementBorder - this.dispCellBorder;

      // ensure the display cell is displayable but still hidden and in the right place
      this.dispCell.setStyles({
        display :"block",
        left : endPos.x,
        top : endPos.y
      });

      // reveal the display cell
      this.dispCell.tween("opacity",1);
    }.bind(this));

  },

  //************************************************************************
  // Name   : _insertImageNoTarget
  //
  //  This inserts the desired image, and morphs the dipsplay cell to the
  //  image size then reveals the image
  //
  //  (element) image        - the image element to be inserted
  //  (string)  imagePath    - path to the image file
  //  (element) thumbElement - element that triggered the expand effect
  //
  // Returns : (nothing)
  //************************************************************************
  _insertImageNoTarget : function(image,imagePath,thumbElement) {
    var endPos;
    var border;

    // find the thumb image
    var startPos = thumbElement.getCoordinates();

    // determine the current scroll position of the window
    var winScroll = $(document.body).getScroll();
    this.img.setProperty("src",imagePath);

    // morph from thumb image to top left corner of screen
    this.dispCell.morph({
      display : "block",
      opacity : [0,1], // set opacity, but don't morph it
      left: [startPos.left,this.endPos.x + winScroll.x],
      top : [startPos.top,this.endPos.y + winScroll.y],
      width : [startPos.width,image.width],
      height : [startPos.height,image.height]
    }).get("morph").chain(function() {
      this.img.setStyle("display","block");

      // if there is a close image, display it
      if (this.closeImg) {
        this.closeImg.setStyles({
          left : this.endPos.x + image.width - this.CLOSE_IMG_RADIUS + this.dispCellBorder + winScroll.x,
          top : this.endPos.y  - this.CLOSE_IMG_RADIUS + this.dispCellBorder + winScroll.y,
          display : "block"
        });
      }
    }.bind(this));

  },

  //************************************************************************
  // Name   : _insertHtmlNoTarget
  //  This morphs the display cell to the desired size, then inserts html
  //  into the display cell
  //
  //  (element) thumbElement - element that triggered the expand effect
  //
  // Returns : (nothing)
  //************************************************************************
  _insertHtmlNoTarget : function(thumbElement) {

    // find the thumb image
    var startPos = thumbElement.getCoordinates();

    // determine the current scroll position of the window
    var winScroll = $(document.body).getScroll();

    // use the width specified in the thumbElement or the default configured
    // width
    var htmlWidth = thumbElement.getProperty(this.options.htmlFileProperty + "Width");
    htmlWidth = parseInt(htmlWidth ? htmlWidth : this.options.htmlWidth);

    // use the height specified in the thumbElement or the default configured
    // height
    var htmlHeight = thumbElement.getProperty(this.options.htmlFileProperty + "Height");
    htmlHeight = parseInt(htmlHeight ? htmlHeight : this.options.htmlHeight);

    // morph from thumb image to top left corner of screen
    this.dispCell.morph({
      display : "block",
      opacity : [0,1],
      left: [startPos.left,this.endPos.x + winScroll.x],
      top : [startPos.top,this.endPos.y + winScroll.y],
      width : [startPos.width,htmlWidth],
      height : [startPos.height,htmlHeight]
    }).get("morph").chain(function() {

      // load the html file into the display cell
      this.dispCell.load(thumbElement.getProperty(this.options.htmlFileProperty));

      // if there is a close image, display it
      if (this.closeImg) {
        this.closeImg.setStyles({
          left : this.endPos.x + htmlWidth - this.CLOSE_IMG_RADIUS + this.dispCellBorder + winScroll.x,
          top  : this.endPos.y  - this.CLOSE_IMG_RADIUS + this.dispCellBorder + winScroll.y,
          display : "block"
        });
      } // endif

    }.bind(this));

  },

  //************************************************************************
  // Name   : _insertHtmlInTarget
  //  This inserts the desired html, and morphs the target to the desired size
  //  then reveals the html
  //
  //  (element) thumbElement - element that triggered the expand effect
  //
  // Returns : (nothing)
  //************************************************************************
  _insertHtmlInTarget : function(thumbElement) {
    var endPos;

    // use the width specified in the thumbElement or the default configured
    // width
    var htmlWidth = thumbElement.getProperty(this.options.htmlFileProperty + "Width");
    htmlWidth = htmlWidth ? htmlWidth : this.options.htmlWidth;
    htmlWidth = parseInt(htmlWidth);

    // use the height specified in the thumbElement or the default configured
    // height
    var htmlHeight = thumbElement.getProperty(this.options.htmlFileProperty + "Height");
    htmlHeight = htmlHeight ? htmlHeight : this.options.htmlHeight;
    htmlHeight = parseInt(htmlHeight);

    // morph the target to the size of the content
    this.options.targetElement.morph({
      width  : htmlWidth,
      height : htmlHeight
    }).get("morph").chain(function() {

      // recalculate this since the target element has been resized
      endPos = this.options.targetElement.getPosition();
      endPos.x += this.targetElementBorder - this.dispCellBorder;
      endPos.y += this.targetElementBorder - this.dispCellBorder;

      // ensure the display cell is displayable but still hidden and in the right place
      this.dispCell.setStyles({
        display :"block",
        left : endPos.x,
        top : endPos.y,
        width : htmlWidth,
        height : htmlHeight
      });

      // reveal the display cell
      this.dispCell.tween("opacity",1);

      // load the html file into the display cell
      this.dispCell.load(thumbElement.getProperty(this.options.htmlFileProperty));

    }.bind(this));

  },

  //************************************************************************
  // Name   : _expand
  //  This morphs from the thumbnail image to the large content cell
  //
  //  (element) thumbElement - the thumbnail element image that triggered
  //                            the expand
  //
  // POST : this.activeThumb has been set
  //
  // Returns : (nothing)
  //************************************************************************
  _expand : function(thumbElement) {
    var imagePath;
    var image = null;
    var winScroll;

    // record the active image (note image might not actually be visible
    // yet because we wait for asset onload event before morphing the image
    this.activeThumb = thumbElement;

    // if there is a targetElement for the image
    if (this.options.targetElement) {
      // hide the display cell
      this.dispCell.setStyles({
        display : "none",
        opacity : 0
      });

      // an image is to be inserted
      if (this.options.contentType == "image") {
        imagePath = this._getLargeImgName($(thumbElement).getProperty("src"));
        image = new Asset.image(imagePath,{
          // once the image has loaded
          onload : function() {
            this._insertImageInTarget(image,imagePath);
          }.bind(this)
        });

      // html is to be inserted
      } else {
        this._insertHtmlInTarget(thumbElement);
      } // endif

    // using the top left corner of the scrolled window as the large image location
    } else {
      // if the display cell is currently displayed, hide it before morphing another
      if (this.dispCell.getStyle("display") == "block") {

        // if there is a close button, hide it first
        if (this.closeImg) {
          this.closeImg.setStyle("display","none");
        }
        this.dispCell.setStyles({
          display : "none",
          opacity : 0
        });

        // if content type is image
        if (this.options.contentType == "image") {
          this.img.setStyle("display","none");
        } else {
          // remove the content
          this.dispCell.innerHTML = "";
        } // endif
      } // endif

      // an image is to be inserted
      if (this.options.contentType == "image") {
        imagePath = this._getLargeImgName($(thumbElement).getProperty("src"));

        // load the image, once it's loaded, the effect can begin
        image = new Asset.image(imagePath,{
          // once the image has loaded
          onload : function() {
            this._insertImageNoTarget(image,imagePath,thumbElement);
          }.bind(this)
        });

      // html is to be inserted
      } else {
        this._insertHtmlNoTarget(thumbElement);
      } // endif

    } // endif

  },

  //************************************************************************
  // Name   : setupMouseover
  //  This attaches the mouseover and mouseout events to the given image or
  //  list of images such that mouseover the thumb shows the large image,
  //  mouseout of the same thumb, hides the large image. This is useful when
  //  there is no chance of overlap between large and small images
  //
  //  (mixed) elementOrArray - an element or array of elements that are to
  //                           trigger the expand effect
  //
  // Returns : (nothing)
  //************************************************************************
  setupMouseover : function(elementOrArray) {
    var elements = $splat(elementOrArray);

    elements.each(function(item,index) {
      item.addEvent("mouseover",function(event) {

        // if we are not already displaying the same thumb image
        if (this.activeThumb != event.target) {
          this._expand(event.target);
        } // endif

      }.bind(this));

      // don't need to hide the large image on mouseout when there is a
      // target area
      if (!this.options.targetElement) {
        item.addEvent("mouseout",function(event) {
          // determine position of image cell
          var dispCellPos = this.dispCell.getCoordinates();

          // determine how much the window has scrolled
          var winScroll = $(document.body).getScroll();

          // if we just left the thumb image and we are not now inside the large image
          // then it's save to hide the large image
          if ((event.client.x + winScroll.x >= dispCellPos.left) &&
              (event.client.x + winScroll.x <= dispCellPos.right+1) &&
              (event.client.y + winScroll.y >= dispCellPos.top) &&
              (event.client.y + winScroll.y <= dispCellPos.bottom+1) ) {
          } else {
            this.hide();
          } // endif

        }.bind(this));
      } // endif
    }.bind(this));

    // don't need any mouseout events when there is a target element
    if (!this.options.targetElement) {
      // for the cases where the thumb overlaps the large image
      // add a mouse out event for the large image
      this.dispCell.addEvent("mouseleave",function(event) {

        // if there is an active thumbnail image
        if (this.activeThumb) {

          // get position of thumb nail image
          var thumbPos = this.activeThumb.getCoordinates();

          // determine how much the window has scrolled
          var winScroll = $(document.body).getScroll();

          // if we just left the large image, and the mouse is not over the
          // active thumb image then it's safe to hide the large image
          if ((event.client.x + winScroll.x >= thumbPos.left) &&
              (event.client.x + winScroll.x <= thumbPos.right+1) &&
              (event.client.y + winScroll.y >= thumbPos.top) &&
              (event.client.y + winScroll.y <= thumbPos.bottom+1)) {
          } else {
            this.hide();
          } // endif
        } // endif
      }.bind(this));
    } // endif

    // if a test for lost mousout events is enabled
    if (this.options.autoMouseout) {

      // add an event to track the mouse position at all times
      $(document.body).addEvent("mousemove",function(event) {
        this.mouseX = event.client.x;
        this.mouseY = event.client.y;
      }.bind(this));

      // periodically, check to see if the mouse has moved outside
      // the thumb or the expanded context in case the mouseout event
      // was missed
      var mouseTest = function() {
        if (this.activeThumb) {
          var winScroll = $(document.body).getScroll();

          // get position of thumb nail image and display cell
          var thumbPos =  this.activeThumb.getCoordinates();
          var dispCellPos = this.dispCell.getCoordinates();

          var mousePosX = this.mouseX + winScroll.x;
          var mousePosY = this.mouseY + winScroll.y;

          // if the cursor is over the active thumb, or
          // the cursor is over the display cell
          if (
              ((mousePosX >= thumbPos.left) &&
              (mousePosX <= thumbPos.right+1) &&
              (mousePosY >= thumbPos.top) &&
              (mousePosY <= thumbPos.bottom+1))
              ||
              ((mousePosX >= dispCellPos.left) &&
              (mousePosX <= dispCellPos.right+1) &&
              (mousePosY >= dispCellPos.top) &&
              (mousePosY <= dispCellPos.bottom+1))

          ) { // do nothing

          // mouse is on the loose and mouseover did not fire
          } else {
            this.hide();
          } // endif

        } // endif
      };

      mouseTest.periodical(1000,this);

    } // endif
  },

  //************************************************************************
  // Name   : setupClick
  //  This attaches a click event to the given selector and then requires that
  //  the enlarged window be clicked closed to hide it again. This is useful when
  //  large and small images will overlap
  //
  //  (mixed) elementOrArray - an element or array of elements that are to
  //                           trigger the expand effect
  //
  // Returns : (nothing)
  //************************************************************************
  setupClick : function(selector) {
    var selectors = $splat(selector);

    // create a close image
    this.closeImg = new Element("img",{
      src : this.options.closeImgPath,
      styles : {
        "z-index" : this.options.zIndex + 1,
        position : "absolute",
        display : "none"
      }
    });

    // add the close image to the body
    $(document.body).adopt(this.closeImg);

    // setup the click event
    selectors.each(function(item,index) {
      item.addEvent("click",function(event) {

        // if the same trigger is clicked again, close the expanded content
        // this produces a toggle effect click on, click off
        if (event.target == this.activeThumb) {
          this.hide();

        // different trigger clicked
        } else {
          this._expand(event.target);
        } // endif

      }.bind(this));
    }.bind(this));

    // only if there is no target element, provide a close image click event
    if (!this.options.targetElement) {
      // use the close image icon to hide the image
      this.closeImg.addEvent("click",function(event) {
        this.hide();
      }.bind(this));

      if (this.options.contentType == "image") {
        // clicking the image will hide it too
        this.img.addEvent("click", function(event) {
          this.hide();
        }.bind(this));
      } // endif
    } // endif
  },

  //************************************************************************
  // Name   : initialize (constructor)
  //  This creates the dispCell and img elements that are needed by this class
  //  for display of large images
  //
  //  (object) options - (optional) configuration options. See options declaration
  //                     above for the available options
  //
  // Returns : (nothing)
  //*************************************************************************
  initialize : function(options) {
    this.setOptions(options);
    this.dispCellBorder = this.options.border.toInt();

    // if there is a target element
    if (this.options.targetElement) {
      this.options.targetElement = $(this.options.targetElement);
      this.targetElementBorder = this.options.targetElement.getStyle("border-left-width").toInt();

    // the upper left of the document body is the target
    } else {
      this.endPos = $(document.body).getPosition();
      this.endPos.x += this.options.windowOffsetX;
      this.endPos.y += this.options.windowOffsetX;
    } // endif

    // create the div
    this.dispCell = new Element("div",{
      styles : {
        border : this.options.border,
        "background-color" : this.options.background,
        position : "absolute",
        "z-index" : this.options.zIndex,
        display : "none"
      }
    });

    // if displaying an image
    if (this.options.contentType == "image") {
      // create the internal image
      this.img = new Element("img",{
        styles : {
          display : "none",
          "z-index" : this.options.zIndex
        }
      });
      // add the image to the div
      this.dispCell.adopt(this.img);
    } // endif

    // add the div to the window
    $(document.body).adopt(this.dispCell);

  }

});
