/*  ******************************
  Module
  State
  Change text based on state context
  Hover/Pressed/Active/Inactive
  Author: Jack Lukic
  Last revision: May 2012

  State text module is used to apply text to a given node
  depending on the elements "state"

  State is either defined as "active" or "inactive" depending
  on the returned value of a test function

  Usage:

  $button
    .state({
      states: {
        active: true
      },
      text: {
        inactive: 'Follow',
        active  : 'Following',
        enable  : 'Add',
        disable : 'Remove'
      }
    })
  ;

  "Follow", turns to "Add" on hover, then "Following" on active
  and finally "Remove" on active hover

  This plugin works in correlation to API module and will, by default,
  use deffered object accept/reject to determine state.

******************************  */

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

$.fn.state = function(parameters) {
  var

    $allModules     = $(this),

    // make arguments available
    query           = arguments[0],
    passedArguments = [].slice.call(arguments, 1),
    invokedResponse
  ;
  $allModules
    .each(function() {
      var
        $module       = $(this),

        settings      = $.extend(true, {}, $.fn.state.settings, parameters),

        selector      = $module.selector || '',
        element       = this,
        instance      = $module.data('module-' + settings.namespace),
        methodInvoked = (typeof query == 'string'),

        // shortcuts
        namespace     = settings.namespace,
        metadata      = settings.metadata,
        className     = settings.className,
        states        = settings.states,
        text          = settings.text,

        module
      ;
      module = {

        initialize: function() {
          module.verbose('Initializing module for', element);

          // allow module to guess desired state based on element
          if(settings.automatic) {
            module.add.defaults();
          }

          // bind events with delegated events
          if(settings.context && selector !== '') {
            if( module.allows('hover') ) {
              $(element, settings.context)
                .on(selector, 'mouseenter.' + namespace, module.hover.enable)
                .on(selector, 'mouseleave.' + namespace, module.hover.disable)
              ;
            }
            if( module.allows('pressed') ) {
              $(element, settings.context)
                .on(selector, 'mousedown.' + namespace, module.pressed.enable)
                .on(selector, 'mouseup.'   + namespace, module.pressed.disable)
              ;
            }
            if( module.allows('focus') ) {
              $(element, settings.context)
                .on(selector, 'focus.' + namespace, module.focus.enable)
                .on(selector, 'blur.'  + namespace, module.focus.disable)
              ;
            }
            $(settings.context)
              .on(selector, 'mouseover.' + namespace, module.text.change)
              .on(selector, 'mouseout.'  + namespace, module.text.reset)
              .on(selector, 'click.'     + namespace, module.toggle)
            ;

          }
          else {
            if( module.allows('hover') ) {
              $module
                .on('mouseenter.' + namespace, module.hover.enable)
                .on('mouseleave.' + namespace, module.hover.disable)
              ;
            }
            if( module.allows('pressed') ) {
              $module
                .on('mousedown.' + namespace, module.pressed.enable)
                .on('mouseup.'   + namespace, module.pressed.disable)
              ;
            }
            if( module.allows('focus') ) {
              $module
                .on('focus.' + namespace, module.focus.enable)
                .on('blur.'  + namespace, module.focus.disable)
              ;
            }
            $module
              .on('mouseover.' + namespace, module.text.change)
              .on('mouseout.'  + namespace, module.text.reset)
              .on('click.'     + namespace, module.toggle)
            ;
          }
          $module
            .data('module-' + namespace, module)
          ;
        },

        destroy: function() {
          module.verbose('Destroying previous module for', element);
          $module
            .off('.' + namespace)
          ;
        },

        refresh: function() {
          module.verbose('Refreshing selector cache for', element);
          $module = $(element);
        },

        add: {
          defaults: function() {
            var
              userStates = parameters && $.isPlainObject(parameters.states)
                ? parameters.states
                : {}
            ;
            $.each(settings.defaults, function(type, typeStates) {
              if( module.is[type] !== undefined && module.is[type]() ) {
                module.verbose('Adding default states for detected type:', type, element);
                $.extend(settings.states, typeStates, userStates);
              }
            });
          }
        },

        is: {

          active: function() {
            return $module.hasClass(className.active);
          },
          loading: function() {
            return $module.hasClass(className.loading);
          },
          inactive: function() {
            return !( $module.hasClass(className.active) );
          },

          enabled: function() {
            return !( $module.is(settings.filter.active) );
          },
          disabled: function() {
            return ( $module.is(settings.filter.active) );
          },
          textEnabled: function() {
            return !( $module.is(settings.filter.text) );
          },

          // definitions for automatic type detection
          button: function() {
            return $module.is('.button:not(a, .submit)');
          },
          input: function() {
            return $module.is('input');
          }
        },

        allows: function(state) {
          return states[state] || false;
        },
        enable: function(state) {
          if(module.allows(state)) {
            $module.addClass( className[state] );
          }
        },
        disable: function(state) {
          if(module.allows(state)) {
            $module.removeClass( className[state] );
          }
        },
        textFor: function(state) {
          return text[state] || false;
        },

        focus : {
          enable: function() {
            $module.addClass(className.focus);
          },
          disable: function() {
            $module.removeClass(className.focus);
          }
        },

        hover : {
          enable: function() {
            $module.addClass(className.hover);
          },
          disable: function() {
            $module.removeClass(className.hover);
          }
        },

        pressed : {
          enable: function() {
            $module
              .addClass(className.pressed)
              .one('mouseleave', module.pressed.disable)
            ;
          },
          disable: function() {
            $module.removeClass(className.pressed);
          }
        },

        // determines method for state activation
        toggle: function() {
          var
            apiRequest = $module.data(metadata.promise)
          ;
          if( module.allows('active') && module.is.enabled() ) {
            module.refresh();
            if(apiRequest !== undefined) {
              module.listenTo(apiRequest);
            }
            else {
              module.change();
            }
          }
        },

        listenTo: function(apiRequest) {
          module.debug('API request detected, waiting for state signal');
          if(apiRequest) {
            if(text.loading) {
              module.text.update(text.loading);
            }
            $.when(apiRequest)
              .then(function() {
                if(apiRequest.state() == 'resolved') {
                  module.debug('API request succeeded');
                  settings.activateTest   = function(){ return true; };
                  settings.deactivateTest = function(){ return true; };
                }
                else {
                  module.debug('API request failed');
                  settings.activateTest   = function(){ return false; };
                  settings.deactivateTest = function(){ return false; };
                }
                module.change();
              })
            ;
          }
          // xhr exists but set to false, beforeSend killed the xhr
          else {
            settings.activateTest   = function(){ return false; };
            settings.deactivateTest = function(){ return false; };
          }
        },

        // checks whether active/inactive state can be given
        change: function() {
          module.debug('Determining state change direction');
          // inactive to active change
          if( module.is.inactive() ) {
            module.activate();
          }
          else {
            module.deactivate();
          }
          if(settings.sync) {
            module.sync();
          }
          settings.onChange();
        },

        activate: function() {
          if( $.proxy(settings.activateTest, element)() ) {
            module.debug('Setting state to active');
            $module
              .addClass(className.active)
            ;
            module.text.update(text.active);
          }
        },

        deactivate: function() {
          if($.proxy(settings.deactivateTest, element)() ) {
            module.debug('Setting state to inactive');
            $module
              .removeClass(className.active)
            ;
            module.text.update(text.inactive);
          }
        },

        sync: function() {
          module.verbose('Syncing other buttons to current state');
          if( module.is.active() ) {
            $allModules
              .not($module)
                .state('activate');
          }
          else {
            $allModules
              .not($module)
                .state('deactivate')
            ;
          }
        },

        text: {

          // finds text node to update
          get: function() {
            return (settings.textFilter)
              ? $module.find(settings.textFilter).text()
              : $module.html()
            ;
          },

          change: function() {
            if( module.is.textEnabled() ) {
              if( module.is.active() ) {
                if(text.hover) {
                  module.verbose('Changing text to hover text', text.hover);
                  module.text.update(text.hover);
                }
                else if(text.disable) {
                  module.verbose('Changing text to disable text', text.disable);
                  module.text.update(text.disable);
                }
              }
              else {
                if(text.hover) {
                  module.verbose('Changing text to hover text', text.disable);
                  module.text.update(text.hover);
                }
                else if(text.enable){
                  module.verbose('Changing text to enable text', text.disable);
                  module.text.update(text.enable);
                }
              }
            }
          },

          // on mouseout sets text to previous value
          reset : function() {
            var
              activeText   = text.active   || $module.data(metadata.storedText),
              inactiveText = text.inactive || $module.data(metadata.storedText)
            ;
            if( module.is.textEnabled() ) {
              if( module.is.active() && activeText) {
                module.verbose('Resetting active text', activeText);
                module.text.update(activeText);
              }
              else if(inactiveText) {
                module.verbose('Resetting inactive text', activeText);
                module.text.update(inactiveText);
              }
            }
          },

          update: function(text) {
            var
              currentText = module.text.get()
            ;
            if(text && text !== currentText) {
              module.debug('Updating text to', text);
              if(settings.textFilter) {
                $module
                  .data(metadata.storedText, text)
                  .find(settings.textFilter)
                    .text(text)
                ;
              }
              else {
                $module
                  .data(metadata.storedText, text)
                  .html(text)
                ;
              }
            }
          }
        },
        /* standard module */
        setting: function(name, value) {
          if(value === undefined) {
            return settings[name];
          }
          settings[name] = value;
        },
        verbose: function() {
          if(settings.verbose) {
            module.debug.apply(this, arguments);
          }
        },
        debug: function() {
          var
            output    = [],
            message   = settings.moduleName + ': ' + arguments[0],
            variables = [].slice.call( arguments, 1 ),
            log       = console.info || console.log || function(){}
          ;
          log = Function.prototype.bind.call(log, console);
          if(settings.debug) {
            output.push(message);
            log.apply(console, output.concat(variables) );
          }
        },
        error: function() {
          var
            output       = [],
            errorMessage = settings.moduleName + ': ' + arguments[0],
            variables    = [].slice.call( arguments, 1 ),
            log          = console.warn || console.log || function(){}
          ;
          log = Function.prototype.bind.call(log, console);
          if(settings.debug) {
            output.push(errorMessage);
            output.concat(variables);
            log.apply(console, output.concat(variables) );
          }
        },
        invoke: function(query, context, passedArguments) {
          var
            maxDepth,
            found
          ;
          passedArguments = passedArguments || [].slice.call( arguments, 2 );
          if(typeof query == 'string' && instance !== undefined) {
            query    = query.split('.');
            maxDepth = query.length - 1;
            $.each(query, function(depth, value) {
              if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
                instance = instance[value];
                return true;
              }
              else if( instance[value] !== undefined ) {
                found = instance[value];
                return true;
              }
              module.error(settings.errors.method);
              return false;
            });
          }
          if ( $.isFunction( found ) ) {
            return found.apply(context, passedArguments);
          }
          // return retrieved variable or chain
          return found;
        }
      };

      // check for invoking internal method
      if(methodInvoked) {
        invokedResponse = module.invoke(query, this, passedArguments);
      }
      // otherwise initialize
      else {
        if(instance !== undefined) {
          module.destroy();
        }
        module.initialize();
      }
    })
  ;
  // chain or return queried method
  return (invokedResponse !== undefined)
    ? invokedResponse
    : this
  ;
};

$.fn.state.settings = {

  // module info
  moduleName : 'State Module',

  // debug output
  debug      : true,
  // verbose debug output
  verbose    : false,

  namespace  : 'state',

  // callback occurs on state change
  onChange: function() {},

  // state test functions
  activateTest   : function() { return true; },
  deactivateTest : function() { return true; },

  // whether to automatically map default states
  automatic: true,
  // activate / deactivate changes all elements instantiated at same time
  sync: false,

  // selector filter
  filter     : {
    text   : '.loading, .disabled',
    active : '.disabled'
  },

  textFilter : false,
  context    : false,
  // errors
  errors: {
    method : 'The method you called is not defined.'
  },

  // metadata
  metadata: {
    promise    : 'promise',
    storedText : 'stored-text'
  },

  // change class on state
  className: {
    focus   : 'focus',
    hover   : 'hover',
    pressed : 'down',
    active  : 'active',
    loading : 'loading'
  },

  defaults : {
    input: {
      hover   : true,
      focus   : true,
      pressed : true,
      loading : false,
      active  : false
    },
    button: {
      hover   : true,
      focus   : false,
      pressed : true,
      active  : false,
      loading : true
    }
  },

  states     : {
    hover   : true,
    focus   : true,
    pressed : true,
    loading : false,
    active  : false
  },

  text     : {
    hover    : false,
    active   : false,
    inactive : false,
    enable   : false,
    disable  : false
  }

};



})( jQuery, window , document );