// jquery.jparallax.js
// 0.9
// Stephen Band
//
// Dependencies:
// jQuery 1.2.6 (jquery.com)
//
// Project and documentation site:
// http://webdev.stephband.info/parallax.html


// CLOSURE

(function(jQuery) {


// PRIVATE FUNCTIONS

function stripFiletype(ref) {
  var x=ref.replace('.html', '');
  return x.replace('#', '');
}

function initOrigin(l) {
  if (l.xorigin=='left')  {l.xorigin=0;}  else if (l.xorigin=='middle' || l.xorigin=='centre' || l.xorigin=='center')  {l.xorigin=0.5;}  else if (l.xorigin=='right')  {l.xorigin=1;}
  if (l.yorigin=='top')    {l.yorigin=0;}  else if (l.yorigin=='middle' || l.yorigin=='centre' || l.yorigin=='center')  {l.yorigin=0.5;}  else if (l.yorigin=='bottom')  {l.yorigin=1;}
}

function positionMouse(mouseport, localmouse, virtualmouse) {

  var difference = {x: 0, y: 0, sum: 0};

  // Set where the virtual mouse is, if not on target
  if (!mouseport.ontarget) {

    // Calculate difference
    difference.x    = virtualmouse.x - localmouse.x;
    difference.y    = virtualmouse.y - localmouse.y;
    difference.sum  = Math.sqrt(difference.x*difference.x + difference.y*difference.y);

    // Reset virtualmouse
    virtualmouse.x = localmouse.x + difference.x * mouseport.takeoverFactor;
    virtualmouse.y = localmouse.y + difference.y * mouseport.takeoverFactor;

    // If mouse is inside the takeoverThresh set ontarget to true
    if (difference.sum < mouseport.takeoverThresh && difference.sum > mouseport.takeoverThresh*-1) {
      mouseport.ontarget=true;
    }
  }
  // Set where the layer is if on target
  else {
    virtualmouse.x = localmouse.x;
    virtualmouse.y = localmouse.y;
  }
}

function setupPorts(viewport, mouseport) {

  var offset = mouseport.element.offset();

  jQuery.extend(viewport, {
    width:     viewport.element.width(),
    height:   viewport.element.height()
  });

  jQuery.extend(mouseport, {
    width:    mouseport.element.width(),
    height:    mouseport.element.height(),
    top:      offset.top,
    left:      offset.left
  });
}

function parseTravel(travel, origin, dimension) {

  var offset;
  var cssPos;

  if (typeof(travel) === 'string') {
    if (travel.search(/px/)) {
      travel = travel.replace('px', '');
      travel = parseInt(travel, 10);
      // Set offset constant used in moveLayers()
      offset = origin * (dimension-travel);
      // Set origin now because it won't get altered in moveLayers()
      cssPos = origin * 100 + '%';
      return {travel: travel, travelpx: true, offset: offset, cssPos: cssPos};
    }
    else if (travel.search(/%/)) {
      travel.replace('%', '');
      travel = parseInt(ytravel, 10) / 100;
    }
    else {
      travel=1;
    }
  }
  // Set offset constant used in moveLayers()
  offset = origin * (1 - travel);
  return {travel: travel, travelpx: false, offset: offset}
}

function setupLayer(layer, i, mouseport) {

  var xStuff;
  var yStuff;
  var cssObject = {};

  layer[i]=jQuery.extend({}, {
    width:    layer[i].element.width(),
    height:    layer[i].element.height()
  }, layer[i]);

  xStuff = parseTravel(layer[i].xtravel, layer[i].xorigin, layer[i].width);
  yStuff = parseTravel(layer[i].ytravel, layer[i].yorigin, layer[i].height);

  jQuery.extend(layer[i], {
    // Used in triggerResponse
    diffxrat:    mouseport.width / (layer[i].width - mouseport.width),
    diffyrat:    mouseport.height / (layer[i].height - mouseport.height),
    // Used in moveLayers
    xtravel:     xStuff.travel,
    ytravel:     yStuff.travel,
    xtravelpx:   xStuff.travelpx,
    ytravelpx:   yStuff.travelpx,
    xoffset:     xStuff.offset,
    yoffset:     yStuff.offset
  });

  // Set origin now if it won't be altered in moveLayers()
  if (xStuff.travelpx) {cssObject.left = xStuff.cssPos;}
  if (yStuff.travelpx) {cssObject.top = yStuff.cssPos;}
  if (xStuff.travelpx || yStuff.travelpx) {layer[i].element.css(cssObject);}
}

function setupLayerContents(layer, i, viewportOffset) {

  var contentOffset;

  // Give layer a content object
  jQuery.extend(layer[i], {content: []});
  // Layer content: get positions, dimensions and calculate element offsets for centering children of layers
  for (var n=0; n<layer[i].element.children().length; n++) {

    if (!layer[i].content[n])          layer[i].content[n]             = {};
    if (!layer[i].content[n].element)  layer[i].content[n]['element']  = layer[i].element.children().eq(n);

    // Store the anchor name if one has not already been specified.  You can specify anchors in Layer Options rather than html if you want.
    if(!layer[i].content[n].anchor && layer[i].content[n].element.children('a').attr('name')) {
      layer[i].content[n]['anchor'] = layer[i].content[n].element.children('a').attr('name');
    }

    // Only bother to store child's dimensions if child has an anchor.  What's the point otherwise?
    if(layer[i].content[n].anchor) {
      contentOffset = layer[i].content[n].element.offset();
      jQuery.extend(layer[i].content[n], {
        width:     layer[i].content[n].element.width(),
        height:    layer[i].content[n].element.height(),
        x:        contentOffset.left - viewportOffset.left,
        y:        contentOffset.top - viewportOffset.top
      });
      jQuery.extend(layer[i].content[n], {
        posxrat:  (layer[i].content[n].x + layer[i].content[n].width/2) / layer[i].width,
        posyrat:  (layer[i].content[n].y + layer[i].content[n].height/2) / layer[i].height
      });
    }
  }
}

function moveLayers(layer, xratio, yratio) {

  var xpos;
  var ypos;
  var cssObject;

  for (var i=0; i<layer.length; i++) {

    // Calculate the moving factor
    xpos = layer[i].xtravel * xratio + layer[i].xoffset;
    ypos = layer[i].ytravel * yratio + layer[i].yoffset;
    cssObject = {};
    // Do the moving by pixels or by ratio depending on travelpx
    if (layer[i].xparallax) {
      if (layer[i].xtravelpx) {
        cssObject.marginLeft = xpos * -1 + 'px';
      }
      else {
        cssObject.left = xpos * 100 + '%';
        cssObject.marginLeft = xpos * layer[i].width *-1 + 'px';
      }
    }
    if (layer[i].yparallax) {
      if (layer[i].ytravelpx) {
        cssObject.marginTop = ypos * -1 + 'px';
      }
      else {
        cssObject.top = ypos * 100 + '%';
        cssObject.marginTop = ypos * layer[i].height * -1 + 'px';
      }
    }
    layer[i].element.css(cssObject);
  }
}

// PLUGIN DEFINITION **********************************************************************

jQuery.fn.jparallax = function(options) {

  // Organise settings into objects (Is this a bit of a mess, or is it efficient?)
  var settings = jQuery().extend({}, jQuery.fn.jparallax.settings, options);
  var settingsLayer = {
        xparallax:        settings.xparallax,
        yparallax:        settings.yparallax,
        xorigin:          settings.xorigin,
        yorigin:          settings.yorigin,
        xtravel:          settings.xtravel,
        ytravel:          settings.ytravel
      };
  var settingsMouseport = {
        element:          settings.mouseport,
        takeoverFactor:    settings.takeoverFactor,
        takeoverThresh:    settings.takeoverThresh
      };
  if (settings.mouseport) settingsMouseport['element'] = settings.mouseport;

  // Populate layer array with default settings
  var layersettings = [];
  for(var a=1; a<arguments.length; a++) {
    layersettings.push( jQuery.extend( {}, settingsLayer, arguments[a]) );
  }

  // Iterate matched elements
  return this.each(function() {

    // VAR

    var localmouse = {
          x:        0.5,
          y:        0.5
    };

    var virtualmouse = {
          x:        0.5,
          y:        0.5
    };

    var timer = {
      running:    false,
      frame:      settings.frameDuration,
      fire:        function(x, y) {
                    positionMouse(mouseport, localmouse, virtualmouse);
                    moveLayers(layer, virtualmouse.x, virtualmouse.y);
                    this.running = setTimeout(function() {
                      if ( localmouse.x!=x || localmouse.y!=y || !mouseport.ontarget ) {
                        timer.fire(localmouse.x, localmouse.y);
                      }
                      else if (timer.running) {
                        timer.running=false;
                      }
                    }, timer.frame);
                  }
    };

    var viewport  =  {element: jQuery(this)};

    var mouseport = jQuery.extend({}, {element: viewport.element}, settingsMouseport, {
      xinside:          false,    // is the mouse inside the mouseport's dimensions?
      yinside:          false,
      active:            false,    // are the mouse coordinates still being read?
      ontarget:         false      // is the top layer inside the takeoverThresh?
    });

    var layer      = [];

    // FUNCTIONS

    function matrixSearch(layer, ref, callback) {
      for (var i=0; i<layer.length; i++) {
        var gotcha=false;
        for (var n=0; n<layer[i].content.length; n++) {
          if (layer[i].content[n].anchor==ref) {
            callback(i, n);
            return [i, n];
          }
        }
      }
      return false;
    }

    // RUN

    setupPorts(viewport, mouseport);

    // Cycle through and create layers
    for (var i=0; i<viewport.element.children().length; i++) {
      // Create layer from settings if it doesn't exist
      layer[i]=jQuery.extend({}, settingsLayer, layersettings[i], {
        element:  viewport.element.children('*:eq('+i+')')
      });

      setupLayer(layer, i, mouseport);

      if (settings.triggerResponse) {
        setupLayerContents(layer, i, viewport.element.offset());
      }
    }



    // Set up layers CSS and initial position
    viewport.element.children().css('position', 'absolute');
    moveLayers(layer, 0.5, 0.5);

    // Mouse Response
    if (settings.mouseResponse) {
      jQuery().mousemove(function(mouse){
        // Is mouse inside?
        mouseport.xinside = (mouse.pageX >= mouseport.left && mouse.pageX < mouseport.width+mouseport.left) ? true : false;
        mouseport.yinside = (mouse.pageY >= mouseport.top  && mouse.pageY < mouseport.height+mouseport.top)  ? true : false;
        // Then switch active on.
        if (mouseport.xinside && mouseport.yinside && !mouseport.active) {
          mouseport.ontarget = false;
          mouseport.active = true;
        }
        // If active is on give localmouse coordinates
        if (mouseport.active) {
          if (mouseport.xinside) { localmouse.x = (mouse.pageX - mouseport.left) / mouseport.width; }
          else { localmouse.x = (mouse.pageX < mouseport.left) ? 0 : 1; }
          if (mouseport.yinside) { localmouse.y = (mouse.pageY - mouseport.top) / mouseport.height; }
          else { localmouse.y = (mouse.pageY < mouseport.top) ? 0 : 1; }
        }

        // If mouse is inside, fire timer
        if (mouseport.xinside && mouseport.yinside)  { if (!timer.running) timer.fire(localmouse.x, localmouse.y); }
        else if (mouseport.active) { mouseport.active = false; }
      });
    }

    // Trigger Response
    if (settings.triggerResponse) {
      viewport.element.bind("jparallax", function(event, ref){

        ref = stripFiletype(ref);

        matrixSearch(layer, ref, function(i, n) {
          localmouse.x = layer[i].content[n].posxrat * (layer[i].diffxrat + 1) - (0.5 * layer[i].diffxrat);
          localmouse.y = layer[i].content[n].posyrat * (layer[i].diffyrat + 1) - (0.5 * layer[i].diffyrat);

          if (!settings.triggerExposesEdges) {
            if (localmouse.x < 0) localmouse.x = 0;
            if (localmouse.x > 1) localmouse.x = 1;
            if (localmouse.y < 0) localmouse.y = 0;
            if (localmouse.y > 1) localmouse.y = 1;
          }

          mouseport.ontarget = false;

          if (!timer.running) timer.fire(localmouse.x, localmouse.y);
        });
      });
    }

    // Window Resize Response
    jQuery(window).resize(function() {

      setupPorts(viewport, mouseport);
      for (var i=0; i<layer.length; i++) {
        setupLayer(layer, i, mouseport);
      }
    });


  });
};

// END OF PLUGIN DEFINITION **********************************************************************

// PLUGIN DEFAULTS

jQuery.fn.jparallax.settings = {
  mouseResponse:        true,            // Sets mouse response
  triggerResponse:      true,            // Sets trigger response
  triggerExposesEdges:  false,          // Sets whether the trigger pulls layer edges into view in trying to centre layer content.
  xparallax:            true,            // Sets directions to move in
  yparallax:            true,            //
  xorigin:              0.5,            // Sets default alignment - only comes into play when travel is not 1
  yorigin:              0.5,            //
  xtravel:              1,              // Factor by which travel is amplified
  ytravel:              1,              //
  takeoverFactor:        0.65,            // Sets rate of decay curve for catching up with target mouse position
  takeoverThresh:        0.002,          // Sets the distance within which virtualmouse is considered to be on target, as a multiple of mouseport width.
  frameDuration:        25              // In milliseconds
};

// RUN

initOrigin(jQuery.fn.jparallax.settings);

jQuery(function() {

});


// END CLOSURE

})(jQuery);
