/*  ******************************
  Nag
  Author: Jack Lukic
  Notes: First Commit July 19, 2012

  Simple fixed position nag
******************************  */

;(function ($, window, document, undefined) {

  $.fn.nag = function(parameters) {
    var
      settings = $.extend(true, {}, $.fn.nag.settings, parameters),
      // hoist arguments
      moduleArguments = arguments || false
    ;
    $(this)
      .each(function() {
        var
          $module   = $(this),
          $close    = $module.find(settings.selector.close),

          $context  = $(settings.context),

          instance  = $module.data('module'),
          className = settings.className,

          moduleOffset,
          moduleHeight,

          contextWidth,
          contextHeight,
          contextOffset,

          yOffset,
          yPosition,

          timer,
          module,

          requestAnimationFrame = window.requestAnimationFrame
            || window.mozRequestAnimationFrame
            || window.webkitRequestAnimationFrame
            || window.msRequestAnimationFrame
            || function(callback) { setTimeout(callback, 0); }
        ;
        module = {

          initialize: function() {
            // calculate module offset once
            moduleOffset  = $module.offset();
            moduleHeight  = $module.outerHeight();
            contextWidth  = $context.outerWidth();
            contextHeight = $context.outerHeight();
            contextOffset = $context.offset();

            $module
              .data('module', module)
            ;
            $close
              .on('mouseenter mouseleave', module.event.hover)
              .on('click', module.dismiss)
            ;
            // lets avoid javascript if we dont need to reposition
            if(settings.context == window && settings.position == 'fixed') {
              $module
                .addClass(className.fixed)
              ;
            }
            if(settings.sticky) {
              // retrigger on scroll for absolute
              if(settings.position == 'absolute') {
                $context
                  .on('scroll resize', module.event.scroll)
                ;
              }
              // fixed is always relative to window
              else {
                $(window)
                  .on('scroll resize', module.event.scroll)
                ;
              }
              // fire once to position on init
              $.proxy(module.event.scroll, this)();
            }
            if(settings.followLink) {
              $module
                .on('mouseenter mouseleave', module.event.hover)
                .on('click', module.followLink)
              ;
            }

            if(settings.displayTime > 0) {
              setTimeout(module.hide, settings.displayTime);
            }
            if(module.should.show()) {
              if( !$module.is(':visible') ) {
                module.show();
              }
            }
            else {
              module.hide();
            }
          },

          refresh: function() {
            moduleOffset  = $module.offset();
            moduleHeight  = $module.outerHeight();
            contextWidth  = $context.outerWidth();
            contextHeight = $context.outerHeight();
            contextOffset = $context.offset();
          },

          show: function() {
            if($.fn.popIn !== undefined) {
              $module
                .popIn(settings.duration)
              ;
            }
            else {
              $module
                .fadeIn(settings.duration, settings.easing)
              ;
            }
          },
          hide: function() {
            $module
              .fadeOut(settings.duration, settings.easing)
            ;
          },

          stick: function() {
            module.refresh();

            if(settings.position == 'fixed') {
              var
                windowScroll = $(window).prop('pageYOffset') || $(window).scrollTop(),
                fixedOffset = ( $module.hasClass(className.bottom) )
                  ? contextOffset.top + (contextHeight - moduleHeight) - windowScroll
                  : contextOffset.top - windowScroll
              ;
              $module
                .css({
                  position : 'fixed',
                  top      : fixedOffset,
                  left     : contextOffset.left,
                  width    : contextWidth - settings.scrollBarWidth
                })
              ;
            }
            else {
              $module
                .css({
                  top      : yPosition
                })
              ;
            }
          },
          unStick: function() {
            $module
              .css({
                top      : ''
              })
            ;
          },
          dismiss: function() {
            if(settings.storageMethod) {
              module.storage.set(settings.storedKey, settings.storedValue);
            }
            module.hide();
          },

          should: {
            show: function() {
              if( module.storage.get(settings.storedKey) == settings.storedValue) {
                return false;
              }
              return true;
            },
            stick: function() {
              yOffset   = $context.prop('pageYOffset') || $context.scrollTop();
              yPosition = ( $module.hasClass(className.bottom) )
                ? (contextHeight - $module.outerHeight() ) + yOffset
                : yOffset
              ;
              // absolute position calculated when y offset met
              if(yPosition > moduleOffset.top) {
                return true;
              }
              else if(settings.position == 'fixed') {
                return true;
              }
              return false;
            }
          },

          followLink: function() {
            if($.fn.followLink !== undefined) {
              $module
                .followLink()
              ;
            }
          },

          storage: {

            set: function(key, value) {
              if(settings.storageMethod == 'local' && store !== undefined) {
                store.set(key, value);
              }
              // store by cookie
              else if($.cookie !== undefined) {
                $.cookie(key, value);
              }
              else {
                module.error(settings.errors.noStorage);
              }
            },
            get: function(key) {
              if(settings.storageMethod == 'local' && store !== undefined) {
                return store.get(key);
              }
              // get by cookie
              else if($.cookie !== undefined) {
                return $.cookie(key);
              }
              else {
                module.error(settings.errors.noStorage);
              }
            }

          },

          event: {
            hover: function() {
              $(this)
                .toggleClass(className.hover)
              ;
            },
            scroll: function() {
              if(timer !== undefined) {
                clearTimeout(timer);
              }
              timer = setTimeout(function() {
                if(module.should.stick() ) {
                  requestAnimationFrame(module.stick);
                }
                else {
                  module.unStick();
                }
              }, settings.lag);
            }
          },

          error: function(error) {
            console.log('Nag Module:' + error);
          },

          // allows for dot notation method calls
          invoke: function(methodName, context, methodArguments) {
            var
              method
            ;
            methodArguments = methodArguments || Array.prototype.slice.call( arguments, 2 );
            if(typeof methodName == 'string' && instance !== undefined) {
              methodName = methodName.split('.');
              $.each(methodName, function(index, name) {
                if( $.isPlainObject( instance[name] ) ) {
                  instance = instance[name];
                  return true;
                }
                else if( $.isFunction( instance[name] ) ) {
                  method = instance[name];
                  return true;
                }
                module.error(settings.errors.method);
                return false;
              });
            }
            if ( $.isFunction( method ) ) {
              return method.apply(context, methodArguments);
            }
            // return retrieved variable or chain
            return method;
          }

        };

        if(instance !== undefined && moduleArguments) {
          if(moduleArguments[0] == 'invoke') {
            moduleArguments = Array.prototype.slice.call( moduleArguments, 1 );
          }
          return module.invoke(moduleArguments[0], this, Array.prototype.slice.call( moduleArguments, 1 ) );
        }
        module.initialize();

      })
    ;
    return this;
  };

  $.fn.nag.settings = {

    // set to zero to manually dismiss, otherwise hides on its own
    displayTime    : 0,

    // if there is a link to follow
    followLink     : true,

    // method of stickyness
    position       : 'fixed',
    scrollBarWidth : 18,

    // type of storage to use
    storageMethod  : 'cookie',

    // value to store in dismissed localstorage/cookie
    storedKey      : 'nag',
    storedValue    : 'dismiss',

    // need to calculate stickyness on scroll
    sticky         : true,

    // how often to check scroll event
    lag            : 0,

    // context for scroll event
    context        : window,

    errors: {
      noStorage  : 'Neither $.cookie or store is defined. A storage solution is required for storing state',
      followLink : 'Follow link is set but the plugin is not included'
    },

    className     : {
      bottom      : 'bottom',
      hover       : 'hover',
      fixed       : 'fixed'
    },

    selector      : {
      close: '.icon.close'
    },

    speed         : 500,
    easing        : 'easeOutQuad'

  };

})( jQuery, window , document );