/*

* # Semantic - Modal
* 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.modal = function(parameters) {

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

  moduleSelector = $allModules.selector || '',

  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
      settings    = ( $.isPlainObject(parameters) )
        ? $.extend(true, {}, $.fn.modal.settings, parameters)
        : $.extend({}, $.fn.modal.settings),

      selector        = settings.selector,
      className       = settings.className,
      namespace       = settings.namespace,
      error           = settings.error,

      eventNamespace  = '.' + namespace,
      moduleNamespace = 'module-' + namespace,

      $module         = $(this),
      $context        = $(settings.context),
      $close          = $module.find(selector.close),

      $allModals,
      $otherModals,
      $focusedElement,
      $dimmable,
      $dimmer,

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

      elementNamespace,
      id,
      observer,
      module
    ;
    module  = {

      initialize: function() {
        module.verbose('Initializing dimmer', $context);

        module.create.id();
        module.create.dimmer();
        module.refreshModals();

        module.verbose('Attaching close events', $close);
        module.bind.events();
        module.observeChanges();
        module.instantiate();
      },

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

      create: {
        dimmer: function() {
          var
            defaultSettings = {
              debug      : settings.debug,
              dimmerName : 'modals',
              duration   : {
                show     : settings.duration,
                hide     : settings.duration
              }
            },
            dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings)
          ;
          if($.fn.dimmer === undefined) {
            module.error(error.dimmer);
            return;
          }
          module.debug('Creating dimmer with settings', dimmerSettings);
          $dimmable = $context.dimmer(dimmerSettings);
          if(settings.detachable) {
            module.verbose('Modal is detachable, moving content into dimmer');
            $dimmable.dimmer('add content', $module);
          }
          $dimmer = $dimmable.dimmer('get dimmer');
        },
        id: function() {
          module.verbose('Creating unique id for element');
          id = module.get.uniqueID();
          elementNamespace = '.' + id;
        }
      },

      destroy: function() {
        module.verbose('Destroying previous modal');
        $module
          .removeData(moduleNamespace)
          .off(eventNamespace)
        ;
        $window.off(elementNamespace);
        $close.off(eventNamespace);
        $context.dimmer('destroy');
      },

      observeChanges: function() {
        if('MutationObserver' in window) {
          observer = new MutationObserver(function(mutations) {
            module.debug('DOM tree modified, refreshing');
            module.refresh();
          });
          observer.observe(element, {
            childList : true,
            subtree   : true
          });
          module.debug('Setting up mutation observer', observer);
        }
      },

      refresh: function() {
        module.remove.scrolling();
        module.cacheSizes();
        module.set.screenHeight();
        module.set.type();
        module.set.position();
      },

      refreshModals: function() {
        $otherModals = $module.siblings(selector.modal);
        $allModals   = $otherModals.add($module);
      },

      attachEvents: function(selector, event) {
        var
          $toggle = $(selector)
        ;
        event = $.isFunction(module[event])
          ? module[event]
          : module.toggle
        ;
        if($toggle.size() > 0) {
          module.debug('Attaching modal events to element', selector, event);
          $toggle
            .off(eventNamespace)
            .on('click' + eventNamespace, event)
          ;
        }
        else {
          module.error(error.notFound, selector);
        }
      },

      bind: {
        events: function() {
          $close
            .on('click' + eventNamespace, module.event.close)
          ;
          $window
            .on('resize' + elementNamespace, module.event.resize)
          ;
        }
      },

      get: {
        uniqueID: function() {
          return (Math.random().toString(16) + '000000000').substr(2,8);
        }
      },

      event: {
        close: function() {
          module.verbose('Closing element pressed');
          if( $(this).is(selector.approve) ) {
            if($.proxy(settings.onApprove, element)() !== false) {
              module.hide();
            }
            else {
              module.verbose('Approve callback returned false cancelling hide');
            }
          }
          else if( $(this).is(selector.deny) ) {
            if($.proxy(settings.onDeny, element)() !== false) {
              module.hide();
            }
            else {
              module.verbose('Deny callback returned false cancelling hide');
            }
          }
          else {
            module.hide();
          }
        },
        click: function(event) {
          if( $(event.target).closest($module).size() === 0 ) {
            module.debug('Dimmer clicked, hiding all modals');
            if( module.is.active() ) {
              module.remove.clickaway();
              if(settings.allowMultiple) {
                module.hide();
              }
              else {
                module.hideAll();
              }
            }
          }
        },
        debounce: function(method, delay) {
          clearTimeout(module.timer);
          module.timer = setTimeout(method, delay);
        },
        keyboard: function(event) {
          var
            keyCode   = event.which,
            escapeKey = 27
          ;
          if(keyCode == escapeKey) {
            if(settings.closable) {
              module.debug('Escape key pressed hiding modal');
              module.hide();
            }
            else {
              module.debug('Escape key pressed, but closable is set to false');
            }
            event.preventDefault();
          }
        },
        resize: function() {
          if( $dimmable.dimmer('is active') ) {
            requestAnimationFrame(module.refresh);
          }
        }
      },

      toggle: function() {
        if( module.is.active() || module.is.animating() ) {
          module.hide();
        }
        else {
          module.show();
        }
      },

      show: function(callback) {
        callback = $.isFunction(callback)
          ? callback
          : function(){}
        ;
        module.refreshModals();
        module.showModal(callback);
      },

      hide: function(callback) {
        callback = $.isFunction(callback)
          ? callback
          : function(){}
        ;
        module.refreshModals();
        module.hideModal(callback);
      },

      showModal: function(callback) {
        callback = $.isFunction(callback)
          ? callback
          : function(){}
        ;
        if( module.is.animating() || !module.is.active() ) {

          module.showDimmer();
          module.cacheSizes();
          module.set.position();
          module.set.screenHeight();
          module.set.type();
          module.set.clickaway();

          if( !settings.allowMultiple && $otherModals.filter(':visible').size() > 0) {
            module.debug('Other modals visible, queueing show animation');
            module.hideOthers(module.showModal);
          }
          else {
            $.proxy(settings.onShow, element)();
            if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
              module.debug('Showing modal with css animations');
              $module
                .transition({
                  debug       : settings.debug,
                  animation   : settings.transition + ' in',
                  queue       : settings.queue,
                  duration    : settings.duration,
                  useFailSafe : true,
                  onComplete : function() {
                    $.proxy(settings.onVisible, element)();
                    module.add.keyboardShortcuts();
                    module.save.focus();
                    module.set.active();
                    module.set.autofocus();
                    callback();
                  }
                })
              ;
            }
            else {
              module.debug('Showing modal with javascript');
              $module
                .fadeIn(settings.duration, settings.easing, function() {
                  $.proxy(settings.onVisible, element)();
                  module.add.keyboardShortcuts();
                  module.save.focus();
                  module.set.active();
                  callback();
                })
              ;
            }
          }
        }
        else {
          module.debug('Modal is already visible');
        }
      },

      hideModal: function(callback) {
        callback = $.isFunction(callback)
          ? callback
          : function(){}
        ;
        module.debug('Hiding modal');
        $.proxy(settings.onHide, element)();

        if( module.is.animating() || module.is.active() ) {
          if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
            module.remove.active();
            $module
              .transition({
                debug       : settings.debug,
                animation   : settings.transition + ' out',
                queue       : settings.queue,
                duration    : settings.duration,
                useFailSafe : true,
                onStart     : function() {
                  if( !module.othersActive() ) {
                    module.hideDimmer();
                  }
                  module.remove.keyboardShortcuts();
                },
                onComplete : function() {
                  $.proxy(settings.onHidden, element)();
                  module.restore.focus();
                  callback();
                }
              })
            ;
          }
          else {
            module.remove.active();
            if( !module.othersActive() ) {
              module.hideDimmer();
            }
            module.remove.keyboardShortcuts();
            $module
              .fadeOut(settings.duration, settings.easing, function() {
                $.proxy(settings.onHidden, element)();
                module.restore.focus();
                callback();
              })
            ;
          }
        }
      },

      showDimmer: function() {
        if($dimmable.dimmer('is animating') || !$dimmable.dimmer('is active') ) {
          module.debug('Showing dimmer');
          $dimmable.dimmer('show');
        }
        else {
          module.debug('Dimmer already visible');
        }
      },

      hideDimmer: function() {
        if( $dimmable.dimmer('is animating') || ($dimmable.dimmer('is active')) ) {
          $dimmable.dimmer('hide', function() {
            if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
              module.remove.clickaway();
              module.remove.screenHeight();
            }
          });
        }
        else {
          module.debug('Dimmer is not visible cannot hide');
          return;
        }
      },

      hideAll: function(callback) {
        callback = $.isFunction(callback)
          ? callback
          : function(){}
        ;
        if( $allModals.is(':visible') ) {
          module.debug('Hiding all visible modals');
          module.hideDimmer();
          $allModals
            .filter(':visible')
              .modal('hide modal', callback)
          ;
        }
      },

      hideOthers: function(callback) {
        callback = $.isFunction(callback)
          ? callback
          : function(){}
        ;
        if( $otherModals.is(':visible') ) {
          module.debug('Hiding other modals', $otherModals);
          $otherModals
            .filter(':visible')
              .modal('hide modal', callback)
          ;
        }
      },

      othersActive: function() {
        return ($otherModals.filter('.' + className.active).size() > 0);
      },

      add: {
        keyboardShortcuts: function() {
          module.verbose('Adding keyboard shortcuts');
          $document
            .on('keyup' + eventNamespace, module.event.keyboard)
          ;
        }
      },

      save: {
        focus: function() {
          $focusedElement = $(document.activeElement).blur();
        }
      },

      restore: {
        focus: function() {
          if($focusedElement && $focusedElement.size() > 0) {
            $focusedElement.focus();
          }
        }
      },

      remove: {
        active: function() {
          $module.removeClass(className.active);
        },
        clickaway: function() {
          if(settings.closable) {
            $dimmer
              .off('click' + elementNamespace)
            ;
          }
        },
        screenHeight: function() {
          if(module.cache.height > module.cache.pageHeight) {
            module.debug('Removing page height');
            $body
              .css('height', '')
            ;
          }
        },
        keyboardShortcuts: function() {
          module.verbose('Removing keyboard shortcuts');
          $document
            .off('keyup' + eventNamespace)
          ;
        },
        scrolling: function() {
          $dimmable.removeClass(className.scrolling);
          $module.removeClass(className.scrolling);
        }
      },

      cacheSizes: function() {
        var
          modalHeight = $module.outerHeight()
        ;
        if(module.cache === undefined || modalHeight !== 0) {
          module.cache = {
            pageHeight    : $(document).outerHeight(),
            height        : modalHeight + settings.offset,
            contextHeight : (settings.context == 'body')
              ? $(window).height()
              : $dimmable.height()
          };
        }
        module.debug('Caching modal and container sizes', module.cache);
      },

      can: {
        fit: function() {
          return (module.cache.height < module.cache.contextHeight);
        }
      },

      is: {
        active: function() {
          return $module.hasClass(className.active);
        },
        animating: function() {
          return $module.transition('is supported')
            ? $module.transition('is animating')
            : $module.is(':visible')
          ;
        },
        scrolling: function() {
          return $dimmable.hasClass(className.scrolling);
        },
        modernBrowser: function() {
          // appName for IE11 reports 'Netscape' can no longer use
          return !(window.ActiveXObject || "ActiveXObject" in window);
        }
      },

      set: {
        autofocus: function() {
          if(settings.autofocus) {
            var
              $inputs    = $module.find(':input:visible'),
              $autofocus = $inputs.filter('[autofocus]'),
              $input     = ($autofocus.size() > 0)
                ? $autofocus
                : $inputs
            ;
            $input.first().focus();
          }
        },
        clickaway: function() {
          if(settings.closable) {
            $dimmer
              .on('click' + elementNamespace, module.event.click)
            ;
          }
        },
        screenHeight: function() {
          if(module.cache.height > module.cache.pageHeight) {
            module.debug('Modal is taller than page content, resizing page height');
            $body
              .css('height', module.cache.height + settings.padding)
            ;
          }
          else {
            $body.css('height', '');
          }
        },
        active: function() {
          $module.addClass(className.active);
        },
        scrolling: function() {
          $dimmable.addClass(className.scrolling);
          $module.addClass(className.scrolling);
        },
        type: function() {
          if(module.can.fit()) {
            module.verbose('Modal fits on screen');
            if(!module.othersActive) {
              module.remove.scrolling();
            }
          }
          else {
            module.verbose('Modal cannot fit on screen setting to scrolling');
            module.set.scrolling();
          }
        },
        position: function() {
          module.verbose('Centering modal on page', module.cache);
          if(module.can.fit()) {
            $module
              .css({
                top: '',
                marginTop: -(module.cache.height / 2)
              })
            ;
          }
          else {
            $module
              .css({
                marginTop : '',
                top       : $document.scrollTop()
              })
            ;
          }
        }
      },

      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( (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.modal.settings = {

name           : 'Modal',
namespace      : 'modal',

debug          : false,
verbose        : true,
performance    : true,

allowMultiple  : false,
detachable     : true,
closable       : true,
autofocus      : true,

dimmerSettings : {
  closable : false,
  useCSS   : true
},

context        : 'body',

queue          : false,
duration       : 500,
easing         : 'easeOutExpo',
offset         : 0,
transition     : 'scale',

padding        : 30,

onShow         : function(){},
onHide         : function(){},

onVisible      : function(){},
onHidden       : function(){},

onApprove      : function(){ return true; },
onDeny         : function(){ return true; },

selector    : {
  close    : '.close, .actions .button',
  approve  : '.actions .positive, .actions .approve, .actions .ok',
  deny     : '.actions .negative, .actions .deny, .actions .cancel',
  modal    : '.ui.modal'
},
error : {
  dimmer    : 'UI Dimmer, a required component is not included in this page',
  method    : 'The method you called is not defined.',
  notFound  : 'The element you specified could not be found'
},
className : {
  active    : 'active',
  animating : 'animating',
  scrolling : 'scrolling'
}

};

})( jQuery, window , document );