/*

* # Semantic - Shape
* http://github.com/semantic-org/semantic-ui/
*
*
* Copyright 2014 Contributor
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
*/

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

“use strict”;

$.fn.shape = function(parameters) {

var
  $allModules     = $(this),
  $body           = $('body'),

  time            = new Date().getTime(),
  performance     = [],

  query           = arguments[0],
  methodInvoked   = (typeof query == 'string'),
  queryArguments  = [].slice.call(arguments, 1),

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

  returnedValue
;

$allModules
  .each(function() {
    var
      moduleSelector  = $allModules.selector || '',
      settings        = $.extend(true, {}, $.fn.shape.settings, parameters),

      // internal aliases
      namespace     = settings.namespace,
      selector      = settings.selector,
      error         = settings.error,
      className     = settings.className,

      // define namespaces for modules
      eventNamespace  = '.' + namespace,
      moduleNamespace = 'module-' + namespace,

      // selector cache
      $module       = $(this),
      $sides        = $module.find(selector.sides),
      $side         = $module.find(selector.side),

      // private variables
      nextIndex = false,
      $activeSide,
      $nextSide,

      // standard module
      element       = this,
      instance      = $module.data(moduleNamespace),
      module
    ;

    module = {

      initialize: function() {
        module.verbose('Initializing module for', element);
        module.set.defaultSide();
        module.instantiate();
      },

      instantiate: function() {
        module.verbose('Storing instance of module', module);
        instance = module;
        $module
          .data(moduleNamespace, instance)
        ;
      },

      destroy: function() {
        module.verbose('Destroying previous module for', element);
        $module
          .removeData(moduleNamespace)
          .off(eventNamespace)
        ;
      },

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

      repaint: function() {
        module.verbose('Forcing repaint event');
        var
          shape          = $sides.get(0) || document.createElement('div'),
          fakeAssignment = shape.offsetWidth
        ;
      },

      animate: function(propertyObject, callback) {
        module.verbose('Animating box with properties', propertyObject);
        callback = callback || function(event) {
          module.verbose('Executing animation callback');
          if(event !== undefined) {
            event.stopPropagation();
          }
          module.reset();
          module.set.active();
        };
        $.proxy(settings.beforeChange, $nextSide[0])();
        if(module.get.transitionEvent()) {
          module.verbose('Starting CSS animation');
          $module
            .addClass(className.animating)
          ;
          $sides
            .css(propertyObject)
            .one(module.get.transitionEvent(), callback)
          ;
          module.set.duration(settings.duration);
          requestAnimationFrame(function() {
            $module
              .addClass(className.animating)
            ;
            $activeSide
              .addClass(className.hidden)
            ;
          });
        }
        else {
          callback();
        }
      },

      queue: function(method) {
        module.debug('Queueing animation of', method);
        $sides
          .one(module.get.transitionEvent(), function() {
            module.debug('Executing queued animation');
            setTimeout(function(){
              $module.shape(method);
            }, 0);
          })
        ;
      },

      reset: function() {
        module.verbose('Animating states reset');
        $module
          .removeClass(className.animating)
          .attr('style', '')
          .removeAttr('style')
        ;
        // removeAttr style does not consistently work in safari
        $sides
          .attr('style', '')
          .removeAttr('style')
        ;
        $side
          .attr('style', '')
          .removeAttr('style')
          .removeClass(className.hidden)
        ;
        $nextSide
          .removeClass(className.animating)
          .attr('style', '')
          .removeAttr('style')
        ;
      },

      is: {
        complete: function() {
          return ($side.filter('.' + className.active)[0] == $nextSide[0]);
        },
        animating: function() {
          return $module.hasClass(className.animating);
        }
      },

      set: {

        defaultSide: function() {
          $activeSide = $module.find('.' + settings.className.active);
          $nextSide   = ( $activeSide.next(selector.side).size() > 0 )
            ? $activeSide.next(selector.side)
            : $module.find(selector.side).first()
          ;
          nextIndex = false;
          module.verbose('Active side set to', $activeSide);
          module.verbose('Next side set to', $nextSide);
        },

        duration: function(duration) {
          duration = duration || settings.duration;
          duration = (typeof duration == 'number')
            ? duration + 'ms'
            : duration
          ;
          module.verbose('Setting animation duration', duration);
          $sides.add($side)
            .css({
              '-webkit-transition-duration': duration,
              '-moz-transition-duration': duration,
              '-ms-transition-duration': duration,
              '-o-transition-duration': duration,
              'transition-duration': duration
            })
          ;
        },

        stageSize: function() {
          var
            $clone      = $module.clone().addClass(className.loading),
            $activeSide = $clone.find('.' + settings.className.active),
            $nextSide   = (nextIndex)
              ? $clone.find(selector.side).eq(nextIndex)
              : ( $activeSide.next(selector.side).size() > 0 )
                ? $activeSide.next(selector.side)
                : $clone.find(selector.side).first(),
            newSize = {}
          ;
          $activeSide.removeClass(className.active);
          $nextSide.addClass(className.active);
          $clone.insertAfter($module);
          newSize = {
            width  : $nextSide.outerWidth(),
            height : $nextSide.outerHeight()
          };
          $clone.remove();
          $module
            .css(newSize)
          ;
          module.verbose('Resizing stage to fit new content', newSize);
        },

        nextSide: function(selector) {
          nextIndex = selector;
          $nextSide = $side.filter(selector);
          nextIndex = $side.index($nextSide);
          if($nextSide.size() === 0) {
            module.set.defaultSide();
            module.error(error.side);
          }
          module.verbose('Next side manually set to', $nextSide);
        },

        active: function() {
          module.verbose('Setting new side to active', $nextSide);
          $side
            .removeClass(className.active)
          ;
          $nextSide
            .addClass(className.active)
          ;
          $.proxy(settings.onChange, $nextSide[0])();
          module.set.defaultSide();
        }
      },

      flip: {

        up: function() {
          if(module.is.complete() && !module.is.animating() && !settings.allowRepeats) {
            module.debug('Side already visible', $nextSide);
            return;
          }
          if( !module.is.animating()) {
            module.debug('Flipping up', $nextSide);
            module.set.stageSize();
            module.stage.above();
            module.animate( module.get.transform.up() );
          }
          else {
            module.queue('flip up');
          }
        },

        down: function() {
          if(module.is.complete() && !module.is.animating() && !settings.allowRepeats) {
            module.debug('Side already visible', $nextSide);
            return;
          }
          if( !module.is.animating()) {
            module.debug('Flipping down', $nextSide);
            module.set.stageSize();
            module.stage.below();
            module.animate( module.get.transform.down() );
          }
          else {
            module.queue('flip down');
          }
        },

        left: function() {
          if(module.is.complete() && !module.is.animating() && !settings.allowRepeats) {
            module.debug('Side already visible', $nextSide);
            return;
          }
          if( !module.is.animating()) {
            module.debug('Flipping left', $nextSide);
            module.set.stageSize();
            module.stage.left();
            module.animate(module.get.transform.left() );
          }
          else {
            module.queue('flip left');
          }
        },

        right: function() {
          if(module.is.complete() && !module.is.animating() && !settings.allowRepeats) {
            module.debug('Side already visible', $nextSide);
            return;
          }
          if( !module.is.animating()) {
            module.debug('Flipping right', $nextSide);
            module.set.stageSize();
            module.stage.right();
            module.animate(module.get.transform.right() );
          }
          else {
            module.queue('flip right');
          }
        },

        over: function() {
          if(module.is.complete() && !module.is.animating() && !settings.allowRepeats) {
            module.debug('Side already visible', $nextSide);
            return;
          }
          if( !module.is.animating()) {
            module.debug('Flipping over', $nextSide);
            module.set.stageSize();
            module.stage.behind();
            module.animate(module.get.transform.over() );
          }
          else {
            module.queue('flip over');
          }
        },

        back: function() {
          if(module.is.complete() && !module.is.animating() && !settings.allowRepeats) {
            module.debug('Side already visible', $nextSide);
            return;
          }
          if( !module.is.animating()) {
            module.debug('Flipping back', $nextSide);
            module.set.stageSize();
            module.stage.behind();
            module.animate(module.get.transform.back() );
          }
          else {
            module.queue('flip back');
          }
        }

      },

      get: {

        transform: {
          up: function() {
            var
              translate = {
                y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
                z: -($activeSide.outerHeight() / 2)
              }
            ;
            return {
              transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(-90deg)'
            };
          },

          down: function() {
            var
              translate = {
                y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
                z: -($activeSide.outerHeight() / 2)
              }
            ;
            return {
              transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(90deg)'
            };
          },

          left: function() {
            var
              translate = {
                x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2),
                z : -($activeSide.outerWidth() / 2)
              }
            ;
            return {
              transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(90deg)'
            };
          },

          right: function() {
            var
              translate = {
                x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2),
                z : -($activeSide.outerWidth() / 2)
              }
            ;
            return {
              transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(-90deg)'
            };
          },

          over: function() {
            var
              translate = {
                x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2)
              }
            ;
            return {
              transform: 'translateX(' + translate.x + 'px) rotateY(180deg)'
            };
          },

          back: function() {
            var
              translate = {
                x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2)
              }
            ;
            return {
              transform: 'translateX(' + translate.x + 'px) rotateY(-180deg)'
            };
          }
        },

        transitionEvent: function() {
          var
            element     = document.createElement('element'),
            transitions = {
              'transition'       :'transitionend',
              'OTransition'      :'oTransitionEnd',
              'MozTransition'    :'transitionend',
              'WebkitTransition' :'webkitTransitionEnd'
            },
            transition
          ;
          for(transition in transitions){
            if( element.style[transition] !== undefined ){
              return transitions[transition];
            }
          }
        },

        nextSide: function() {
          return ( $activeSide.next(selector.side).size() > 0 )
            ? $activeSide.next(selector.side)
            : $module.find(selector.side).first()
          ;
        }

      },

      stage: {

        above: function() {
          var
            box = {
              origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
              depth  : {
                active : ($nextSide.outerHeight() / 2),
                next   : ($activeSide.outerHeight() / 2)
              }
            }
          ;
          module.verbose('Setting the initial animation position as above', $nextSide, box);
          $activeSide
            .css({
              'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
            })
          ;
          $nextSide
            .addClass(className.animating)
            .css({
              'display'   : 'block',
              'top'       : box.origin + 'px',
              'transform' : 'rotateX(90deg) translateZ(' + box.depth.next + 'px)'
            })
          ;
        },

        below: function() {
          var
            box = {
              origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
              depth  : {
                active : ($nextSide.outerHeight() / 2),
                next   : ($activeSide.outerHeight() / 2)
              }
            }
          ;
          module.verbose('Setting the initial animation position as below', $nextSide, box);
          $activeSide
            .css({
              'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
            })
          ;
          $nextSide
            .addClass(className.animating)
            .css({
              'display'   : 'block',
              'top'       : box.origin + 'px',
              'transform' : 'rotateX(-90deg) translateZ(' + box.depth.next + 'px)'
            })
          ;
        },

        left: function() {
          var
            box = {
              origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
              depth  : {
                active : ($nextSide.outerWidth() / 2),
                next   : ($activeSide.outerWidth() / 2)
              }
            }
          ;
          module.verbose('Setting the initial animation position as left', $nextSide, box);
          $activeSide
            .css({
              'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
            })
          ;
          $nextSide
            .addClass(className.animating)
            .css({
              'display'   : 'block',
              'left'      : box.origin + 'px',
              'transform' : 'rotateY(-90deg) translateZ(' + box.depth.next + 'px)'
            })
          ;
        },

        right: function() {
          var
            box = {
              origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
              depth  : {
                active : ($nextSide.outerWidth() / 2),
                next   : ($activeSide.outerWidth() / 2)
              }
            }
          ;
          module.verbose('Setting the initial animation position as left', $nextSide, box);
          $activeSide
            .css({
              'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
            })
          ;
          $nextSide
            .addClass(className.animating)
            .css({
              'display'   : 'block',
              'left'      : box.origin + 'px',
              'transform' : 'rotateY(90deg) translateZ(' + box.depth.next + 'px)'
            })
          ;
        },

        behind: function() {
          var
            box = {
              origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
              depth  : {
                active : ($nextSide.outerWidth() / 2),
                next   : ($activeSide.outerWidth() / 2)
              }
            }
          ;
          module.verbose('Setting the initial animation position as behind', $nextSide, box);
          $activeSide
            .css({
              'transform' : 'rotateY(0deg)'
            })
          ;
          $nextSide
            .addClass(className.animating)
            .css({
              'display'   : 'block',
              'left'      : box.origin + 'px',
              'transform' : 'rotateY(-180deg)'
            })
          ;
        }
      },
      setting: function(name, value) {
        module.debug('Changing setting', name, value);
        if( $.isPlainObject(name) ) {
          $.extend(true, settings, name);
        }
        else if(value !== undefined) {
          settings[name] = value;
        }
        else {
          return settings[name];
        }
      },
      internal: function(name, value) {
        if( $.isPlainObject(name) ) {
          $.extend(true, module, name);
        }
        else if(value !== undefined) {
          module[name] = value;
        }
        else {
          return module[name];
        }
      },
      debug: function() {
        if(settings.debug) {
          if(settings.performance) {
            module.performance.log(arguments);
          }
          else {
            module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
            module.debug.apply(console, arguments);
          }
        }
      },
      verbose: function() {
        if(settings.verbose && settings.debug) {
          if(settings.performance) {
            module.performance.log(arguments);
          }
          else {
            module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
            module.verbose.apply(console, arguments);
          }
        }
      },
      error: function() {
        module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
        module.error.apply(console, arguments);
      },
      performance: {
        log: function(message) {
          var
            currentTime,
            executionTime,
            previousTime
          ;
          if(settings.performance) {
            currentTime   = new Date().getTime();
            previousTime  = time || currentTime;
            executionTime = currentTime - previousTime;
            time          = currentTime;
            performance.push({
              'Name'           : message[0],
              'Arguments'      : [].slice.call(message, 1) || '',
              'Element'        : element,
              'Execution Time' : executionTime
            });
          }
          clearTimeout(module.performance.timer);
          module.performance.timer = setTimeout(module.performance.display, 100);
        },
        display: function() {
          var
            title = settings.name + ':',
            totalTime = 0
          ;
          time = false;
          clearTimeout(module.performance.timer);
          $.each(performance, function(index, data) {
            totalTime += data['Execution Time'];
          });
          title += ' ' + totalTime + 'ms';
          if(moduleSelector) {
            title += ' \'' + moduleSelector + '\'';
          }
          if($allModules.size() > 1) {
            title += ' ' + '(' + $allModules.size() + ')';
          }
          if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
            console.groupCollapsed(title);
            if(console.table) {
              console.table(performance);
            }
            else {
              $.each(performance, function(index, data) {
                console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
              });
            }
            console.groupEnd();
          }
          performance = [];
        }
      },
      invoke: function(query, passedArguments, context) {
        var
          object = instance,
          maxDepth,
          found,
          response
        ;
        passedArguments = passedArguments || queryArguments;
        context         = element         || context;
        if(typeof query == 'string' && object !== undefined) {
          query    = query.split(/[\. ]/);
          maxDepth = query.length - 1;
          $.each(query, function(depth, value) {
            var camelCaseValue = (depth != maxDepth)
              ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
              : query
            ;
            if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
              object = object[camelCaseValue];
            }
            else if( object[camelCaseValue] !== undefined ) {
              found = object[camelCaseValue];
              return false;
            }
            else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
              object = object[value];
            }
            else if( object[value] !== undefined ) {
              found = object[value];
              return false;
            }
            else {
              return false;
            }
          });
        }
        if ( $.isFunction( found ) ) {
          response = found.apply(context, passedArguments);
        }
        else if(found !== undefined) {
          response = found;
        }
        if($.isArray(returnedValue)) {
          returnedValue.push(response);
        }
        else if(returnedValue !== undefined) {
          returnedValue = [returnedValue, response];
        }
        else if(response !== undefined) {
          returnedValue = response;
        }
        return found;
      }
    };

    if(methodInvoked) {
      if(instance === undefined) {
        module.initialize();
      }
      module.invoke(query);
    }
    else {
      if(instance !== undefined) {
        module.destroy();
      }
      module.initialize();
    }
  })
;

return (returnedValue !== undefined)
  ? returnedValue
  : this
;

};

$.fn.shape.settings = {

// module info
name : 'Shape',

// debug content outputted to console
debug      : false,

// verbose debug output
verbose    : true,

// performance data output
performance: true,

// event namespace
namespace  : 'shape',

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

// allow animation to same side
allowRepeats: false,

// animation duration
duration   : 700,

// possible errors
error: {
  side   : 'You tried to switch to a side that does not exist.',
  method : 'The method you called is not defined'
},

// classnames used
className   : {
  animating : 'animating',
  hidden    : 'hidden',
  loading   : 'loading',
  active    : 'active'
},

// selectors used
selector    : {
  sides : '.sides',
  side  : '.side'
}

};

})( jQuery, window , document );