/*
  Copyright (c) LinkedIn Corporation. All rights reserved. Licensed under the BSD-2 Clause license.
  See LICENSE in the project root for license information.
*/

window.iris = window.iris || {}; //define iris app obj

iris = {
  data: {tableEntryLimit: 500},
  init: function(){
    $.fn.dataTableExt.classes.sLengthSelect = "form-control border-bottom";
    window.addEventListener('popstate', this.route.bind(this));
    this.registerHandlebarHelpers();
    $('.main').tooltip({
      selector: '[data-toggle="tooltip"]'
    });
    this.route();
  },
  route: function() {
    // break path into segments and route
    var path = window.location.pathname;
    var start = 0;
    var end = path.length - 1;
    if (path.indexOf('/') == start) {
      start++;
    }
    if (path.lastIndexOf('/') == end) {
      end--;
    }
    path = path.substr(start, end);
    var segments = path.split('/');
    var base_route = segments[0];
    if (!(base_route in this)) {
      return;
    }
    switch (segments.length) {
      case 1:
          this[base_route].init();
        break;
      case 2:
          // Add exception for unsubscribe, which has no plural/singular but requires an app
          if (base_route === 'unsubscribe') {
            this.unsubscribe.init();
          } else {
            this[base_route.slice(0, -1)].init();
          }
        break;
    }
  },
  changeTitle: function(title) {
    window.document.title = (title ? title + ' - ' : '') + 'Iris';
  },
  plans: {
    initialized: false,
    data: {
      url: '/v0/plans',
      $page: $('.main'),
      $table: $('#plans-table'),
      $filterForm: $('#filter-form'),
      tableTemplate: $('#plans-table-template').html(),
      DataTable: null,
      dataTableOpts: {
        orderClasses: false,
        order: [[2, 'desc']],
        columns: [
          { width: '40%' },
          null,
          null,
          null
        ]
      }
    },
    init: function(){
      iris.changeTitle('Plans');
      if (this.initialized == false) {
        this.initFilters();
        this.events();
        this.initialized = true;
      }
      iris.tables.filterTable.call(this);
    },
    events: function(){
      var data = this.data,
          $table = data.$table;

      $table.on('click', 'tbody tr[data-route]', function(e){
        if (e.target.tagName !== 'A') {
          if ((e.ctrlKey || e.metaKey)) {
            window.open('/plans/' + $(this).attr('data-route'));
          } else {
            window.location = '/plans/' + $(this).attr('data-route');
          }
        }
      });
      data.$filterForm.on('submit', iris.tables.filterTable.bind(this));

      //javascript live filtering if needed
      // $page.find('#filter-name').on('keyup', function(){
      //   data.DataTable.column('.name').search($(this).val()).draw();
      // });
      // $page.find('#filter-creator').on('keyup', function(){
      //   data.DataTable.column('.creator').search($(this).val()).draw();
      // });
      // $page.find('#filter-active').on('change', function(){
      //   var state = $(this).prop('checked');
      //   if (state) {
      //     data.DataTable.column('.active').search(state).draw();
      //   } else {
      //     data.DataTable.column('.active').search(state).draw();
      //   }
      // });
    },
    getData: function(params){
      params['limit'] = iris.data.tableEntryLimit;
      return $.getJSON(this.data.url, params).fail(function(){
        iris.createAlert('No plans found');
      });
    },
    initFilters: function(){
      iris.typeahead.init('user');
      $('.datetimepicker').datetimepicker({
        showClose: true,
        keyBinds: {'delete': null}
      });
    }
  }, //end iris.plans
  plan: {
    data: {
      url: '/v0/plans/',
      modesUrl: '/v0/modes',
      $page: $('.plan-details'),
      pageTemplateSource: $('#plan-module-template').html(),
      notificationTemplateSource: $('#plan-notification-template').html(),
      stepTemplateSource: $('#plan-step-template').html(),
      trackingTemplateSource: $('#plan-tracking-notification-template').html(),
      testPlanTemplateSource: $('#test-plan-template').html(),
      addNotificationBtn: '.plan-notification-add',
      removeNotificationBtn: '.plan-notification-remove',
      planNotificationInputs: '.plan-notification input, .plan-notification select',
      addStepBtn: '#add-step',
      addTrackingTemplateBtn: '#add-tracking-template',
      removeStepBtn: '.remove-step',
      removeTrackingTemplateBtn: '.remove-tracking-template',
      createPlanBtn: '#publish-plan',
      clonePlanBtn: '#clone-plan',
      showTestPlanModalBtn: '#test-plan-modal-btn',
      showDeletePlanModalBtn: '#delete-plan-modal-btn',
      toggleDynamic: '.toggle-dynamic',
      testPlanBtn: '#test-plan',
      testPlanInputs: '.test-plan-dynamic-target select',
      deletePlanBtn: '#delete-plan',
      versionSelect: '.version-select',
      activatePlan: '.badge[data-active="0"]',
      viewRelated: '.view-related',
      relatedPlans: [],
      aggregationBtn: '#aggregation h4',
      trackingType: '#tracking-type',
      trackingKey: '#tracking-key',
      trackingTemplateBtn: '#tracking-notification[data-view="false"] .tracking-inner h4',
      variablesTemplateSource: $('#variables-template').html(),
      appSelect: '.template-application',
      countInput: '.notification-count',
      waitInput: '.notification-wait',
      template: null,
      ajaxResponse: null,
      submitModel: {},
      blankModel: {
        modes: null,
        priorities: window.appData.priorities,
        target_roles: window.appData.target_roles,
        availableTemplates: [],
        aggregation_reset: 300,
        aggregation_window: 300,
        threshold_count: 10,
        threshold_window: 900,
        dynamic_index: 0,
        dynamic: 0,
        count: 1,
        optional: 0,
        steps: [[{count: 1, id: 'n0', dynamic_index: 0}]]
      },
      blankTrackingTemplateModel: {
        applications: window.appData.applications,
        modes: null,
        viewMode: false,
        tracking_template: {
          application: {
            email_subject: "",
            email_text: "",
            email_html: "",
            body: ""
          }
        }
      }
    },
    init: function(){
      var location = window.location.pathname.split('/'),
          path = this.data.path = location[location.length - 1],
          self = this;

      if (this.data.blankModel.target_roles.length >= 1) {
        var default_role = this.data.blankModel.target_roles[0].name,
            default_role_type = this.data.blankModel.target_roles[0].type;
        this.data.blankModel.role = default_role;
        this.data.blankModel.target_role_type = default_role_type;
        this.data.blankModel.steps[0][0].role = default_role;
        this.data.blankModel.steps[0][0].target_role_type = default_role_type;
      }

      Handlebars.registerPartial('plan-step', $('#plan-step-template').html());
      Handlebars.registerPartial('plan-notification', $('#plan-notification-template').html());
      Handlebars.registerPartial('plan-tracking-notification', this.data.trackingTemplateSource);
      Handlebars.registerPartial('variables', this.data.variablesTemplateSource);
      this.getModes().done(function(){
        self.getPlan(path).done(function(){
          self.events();
          iris.versionTour.init();
          iris.typeahead.init();
        })
      });
      this.drag.init();
    },
    getModes: function(){
      var self = this;
      return $.getJSON(this.data.modesUrl, function(response){
        self.data.blankTrackingTemplateModel.modes = response;
        self.data.blankModel.modes = response;
      });
    },
    events: function(){
      var data = this.data;
      data.$page.on('click', data.addNotificationBtn, this.addNotification.bind(this));
      data.$page.on('click', data.removeNotificationBtn, this.removeNotification);
      data.$page.on('click', data.removeStepBtn, this.removeStep);
      data.$page.on('click', data.removeTrackingTemplateBtn, this.removeTrackingTemplate);
      data.$page.on('click', data.addStepBtn, this.addStep.bind(this));
      data.$page.on('click', data.addTrackingTemplateBtn, this.addTrackingTemplate.bind(this));
      data.$page.on('click', data.addTrackingTemplateBtn, this.updateNotificationFields.bind(this));
      $(data.createPlanBtn).on('click', this.createPlan.bind(this));
      data.$page.on('click', data.clonePlanBtn, this.clonePlan.bind(this));
      data.$page.on('click', data.clonePlanBtn, this.updateNotificationFields.bind(this));
      $(data.testPlanBtn).on('click', this.testPlan.bind(this));
      $(data.deletePlanBtn).on('click', this.deletePlan.bind(this));
      data.$page.on('click', data.showTestPlanModalBtn, this.testPlanModal.bind(this));
      data.$page.on('change', data.planNotificationInputs, this.updateValues);
      data.$page.on('click', data.aggregationBtn, this.toggleAggregation);
      data.$page.on('click', data.trackingTemplateBtn, this.toggleTrackingTemplate);
      data.$page.on('click', data.activatePlan, this.activatePlan.bind(this));
      data.$page.on('change', data.versionSelect, this.loadVersion.bind(this));
      data.$page.on('change', data.trackingType, this.updateTrackingPlaceholder.bind(this));
      data.$page.on('change', data.trackingType, this.updateNotificationFields.bind(this));
      data.$page.on('change', data.appSelect, this.renderVariables.bind(this));
      data.$page.on('change', data.testPlanInputs, this.updateTestPlanValues);
      data.$page.on('change', data.toggleDynamic, this.toggleDynamicTargets);
      data.$page.on('change', data.waitInput, this.updateStepTimes.bind(this))
      data.$page.on('change', data.countInput, this.updateStepTimes.bind(this))
      window.onbeforeunload = iris.unloadDialog.bind(this);
    },
    updateStepTimes: function() {
      var self = this;
      $('.step-time').each(function(_, element) {
        self.updateStepTime(element)
      });
    },
    updateStepTime: function(element) {
      var $element = $(element),
          waits = [],
          stepTime;

      $element.parent().siblings('.plan-notification').each(function(_, n) {
        var $n = $(n);
        if ($n.attr('data-mode') === 'view') {
          waits.push($n.children('.notification-wait')[0].title * $n.children('.notification-count')[0].title);
        } else {
          waits.push($n.find('.notification-wait')[0].value * $n.find('.notification-count')[0].value);
        }
      })
      stepTime = Math.min.apply(null, waits);
      if (stepTime === 0) {
        stepTime = 1;
      }
      $element.text(stepTime);
    },
    getPlan: function(plan){
      var self = this,
          template = this.data.template = Handlebars.compile(this.data.pageTemplateSource);

        //create list of templates available for plans.
        for (var i = 0, item; i < window.appData.templates.length; i++) {
          item = window.appData.templates[i];
          if (item.active){
            self.data.blankModel.availableTemplates.push(item.name);
          }
        }
        self.data.blankModel.availableTemplates.sort(function(a,b){
          a = a.toLowerCase();
          b = b.toLowerCase();

          if (a < b) {
            return -1;
          } else if (a > b) {
            return 1;
          } else {
            return 0;
          }
        });

        // if path is new, render in edit mode.
        if (plan === 'new') {
          self.data.$page.html(template(self.data.blankModel));
          iris.changeTitle('New Plan');
          return $.Deferred().resolve();
        } else {
          self.data.viewMode = true;
          return $.getJSON(this.data.url + plan).done(function(response){
            var max_idx = -1;
            self.data.id = response.id;
            self.data.name = response.name;
            self.data.ajaxResponse = response; //set response in root object for clone plan functionality.
            response.viewMode = self.data.viewMode;
            response.availableTemplates = self.data.blankModel.availableTemplates;
            // convert 'repeat' field to 'count' for frontend
            for (var i = 0; i < response.steps.length; i++) {
              for (var j = 0; j < response.steps[i].length; j++) {
                response.steps[i][j].count = response.steps[i][j].repeat + 1;
                if (response.steps[i][j].dynamic_index !== null) {
                  max_idx = Math.max(max_idx, response.steps[i][j].dynamic_index);
                }
                response.steps[i][j].dynamic = response.steps[i][j].dynamic_index !== null;
              }
            }
            response.max_dynamic_index = max_idx + 1;
            response.dynamic = max_idx !== -1;
            response.target_roles = window.appData.target_roles;
            response.target_role_type =  window.appData.target_roles[0].type;
            response.modes =  self.data.blankTrackingTemplateModel.modes;
            self.data.$page.html(template(response));
            self.loadVersionSelect();
            iris.changeTitle('Plan ' + response.name);
            self.updateStepTimes()
          }).fail(function(){
            iris.createAlert('"' + plan +'" plan not found. <a href="/plans/new">Create a new plan.</a>');
          });
        }
    },
    toggleAggregation: function(){
      $(this).parents('#aggregation').toggleClass('active');
    },
    toggleTrackingTemplate: function(){
      $('#tracking-notification').toggleClass('active');
    },
    toggleDynamicTargets: function(){
      var $toggle = $(this),
          dynamic = $toggle.prop('checked') ? '1' : '0';

      $('.plan-notification').each(function(){
        $(this).attr('data-dynamic', dynamic);
      });
    },
    clonePlan: function(){
      var response = this.data.ajaxResponse;
      response.viewMode = false;
      response.templates = window.appData.templates;
      response.priorities = window.appData.priorities;
      response.target_roles = window.appData.target_roles;
      response.applications = window.appData.applications;
      this.data.$page.html(this.data.template(response));
      this.updateStepTimes();
      iris.typeahead.init();
    },
    testPlanModal: function() {
      var
          $modal = $('#test-plan-modal'),
          $applicationSelect = $modal.find('#test-plan-modal-application-select'),
          frag = '';
      window.appData.applications.forEach(function(app) {
        frag += ['<option>', app['name'], '</option>'].join('');
      });
      $applicationSelect.html(frag);
      $modal.modal();
    },
    testPlan: function() {
      var
          $modal = $('#test-plan-modal'),
          $application = $modal.find('#test-plan-modal-application-select').val(),
          $context = JSON.parse(window.appData.applications.filter(function(l) {return l.name === $application})[0]['sample_context']) || {},
          targets = $modal.find('.test-plan-dynamic-target'),
          dynamic_targets = [],
          role,
          target;
      for (var i = 0; i < targets.length; i++) {
        role = $(targets[i]).find('select').val();
        target = $(targets[i]).find('input.tt-input').val();
        dynamic_targets.push({'role': role, 'target': target});
      }
      $.ajax({
          url: '/v0/incidents',
          data: JSON.stringify({
            application: $application,
            context: $context,
            dynamic_targets: dynamic_targets,
            plan: this.data.name
          }),
          method: 'POST',
          contentType: 'application/json'
      }).done(function(r) {
        window.location = '/incidents/' + r
      }).fail(function(r) {
        iris.createAlert('Failed creating test incident: ' + r.responseJSON['title'])
      }).always(function() {
        $modal.modal('hide');
      })
    },
    updateTestPlanValues: function(){
      var $this = $(this);

      if ($this.is('select[data-type="role"]')) {
        var $target = $this.parents('.test-plan-dynamic-target').find('input[data-typeaheadtype="target"]');
        $target.val('').attr(
            'placeholder',
            $this.find('option:selected').attr('data-url-type') + ' name');
        $target.attr('data-targettype', $this.find('option:selected').attr('data-url-type'));
        iris.typeahead.init();
      }

      $this.attr('value', $this.val());
    },
    deletePlan: function() {
      var $modal = $('#delete-plan-modal');
      $.ajax({
          url: '/v0/plans/' + this.data.id,
          method: 'DELETE'
      }).done(function() {
        window.onbeforeunload = null;
        window.location = '/plans/';
      }).fail(function(r) {
        iris.createAlert('Failed deleting plan: ' + r.responseJSON['title'])
      }).always(function() {
        $modal.modal('hide');
      })
    },
    loadVersionSelect: function(){
      var $select = $(this.data.versionSelect),
          self = this,
          frag = '';

      $.ajax({
        url: this.data.url,
        data: {
          name: this.data.name,
          fields: ['id', 'created', 'creator']
        },
        traditional: true
      }).done(function(r){
        r = r.reverse();
        for (var i = 0; i < r.length; i++) {
          var entry = r[i];
          frag += [
            '<option ', ((entry.id === self.data.id) ? 'selected' : ''),
            ' value="', entry.id,
            '" data-id="', entry.id + '">',
            'ID ', entry.id, ' - ',
            moment.unix(entry.created).local().format('YYYY/MM/DD HH:mm:ss [GMT]ZZ'),
            ' by ', entry.creator,
            ' </option>'
          ].join('');
        }
        $select.append(frag);
      });
    },
    loadVersion: function(e){
      var versionId = $(e.target).find('option:selected').attr('data-id'),
          self = this;
      this.getModes().done(function(){
        self.getPlan(versionId);
      });
      window.history.pushState(null, null, versionId); //update url bar for refreshes
    },
    activatePlan: function(e){
      var $el = $(e.target);
      $el.addClass('disabled');
      $.ajax({
          url: this.data.url + this.data.id,
          data: JSON.stringify({active: 1}),
          method: 'POST',
          contentType: 'application/json'
      }).done(function(){
        $el.attr('data-active', 1);
      }).fail(function(){
        iris.createAlert('Failed to activate.');
      }).always(function(){
        $el.removeClass('disabled');
      });
    },
    updateValues: function(){
      var $this = $(this);
      // Update selected attribute in the DOM on change. This is needed for
      // drag & drop to copy over attribute states.
      if ( $this.is('select') ) {
        $('option:selected', this).attr('selected',true).siblings().removeAttr('selected');
      }

      if ($this.is('select[data-type="role"]')) {
        $this.parents('.plan-notification').find('input[data-type="target"]').val('').attr(
            'placeholder',
            $this.find('option:selected').attr('data-url-type') + ' name');
        iris.typeahead.init();
      }

      $this.attr('value', $this.val());
    },
    addNotification: function(event){
      var $step = $(event.target).parents('.plan-step'),
          notifications = $('.plan-notification'),
          template = Handlebars.compile(this.data.notificationTemplateSource),
          max_id = 0,
          id,
          newModel;

      for (var i = 0; i < notifications.length; i++){
        id = notifications[i].id;
        // New notifications have ids like 'notification-n{{id}}'
        // Existing ones have ids like 'notification-{{id}}'
        if (id.startsWith('notification-n')) {
            max_id = Math.max(parseInt(id.slice(14)), max_id);
        }
      }
      // Create a new notification with id notification-n{{max_id + 1}}
      newModel = $.extend({},this.data.blankModel);
      newModel.id = 'n' + (max_id + 1).toString();
      newModel.dynamic = $('.toggle-dynamic').prop('checked');
      $step.find('.plan-notification-add').before(template(newModel));
      // re-init typeahead
      iris.typeahead.init();
    },
    removeNotification: function(){
      $(this).parents('.plan-notification').remove();
    },
    addStep: function(){
      var template = Handlebars.compile(this.data.stepTemplateSource);
      $(this.data.addStepBtn).before(template());
    },
    addTrackingTemplate: function(){
      var template = Handlebars.compile(this.data.trackingTemplateSource);
      $(this.data.addTrackingTemplateBtn).before(template(this.data.blankTrackingTemplateModel));
    },
    removeStep: function(){
      $(this).parents('.plan-step').remove();
    },
    removeTrackingTemplate: function(){
      $(this).parents('.tracking-template-step').remove();
    },
    updateSubmitModel: function(){
      // gets & checks data from plan inputs and prepares model for post.
      var model = this.data.submitModel,
          missingFields = [],
          $trackingEl = $('#tracking-notification.active'),
          planLength = 0;

      model.creator = window.appData.user;
      model.name = $('#plan-name').val();
      model.description = $('#plan-desc').val();
      model.threshold_window = parseFloat($('#threshold-window').val()) * 60;
      model.threshold_count = parseFloat($('#threshold-count').val());
      model.aggregation_window = parseFloat($('#aggregation-window').val()) * 60;
      model.aggregation_reset = parseFloat($('#aggregation-reset').val()) * 60;
      model.steps = [];
      model.isValid = true;

      // reset invalid inputs from previous check
      this.data.$page.find('.invalid-input').removeClass('invalid-input');
      this.data.$page.find('.alert-danger').remove();

      // validate model name
      if (!model.name) {
        $('#plan-name').addClass('invalid-input');
        //iris.createAlert('Missing fields: Plan name', 'danger', $('.plan-details') );
        missingFields.push('Plan name');
        model.isValid = false;
      }

      // validate plan notifications exist.
      if ($('.plan-notification').length === 0) {
        missingFields.push('steps/notifications');
        model.isValid = false;
      }

      // validate maximum plan duration
      $('.step-time').each(function(_, element) {
        planLength += parseInt($(element).html());
      });
      if ( planLength > 1440 ){
        iris.createAlert('Maximum plan duration exceeded: plan steps must add up to < 24 hours', 'danger', $('.plan-details') );
        model.isValid = false;
      }

      // collect data for notifications
      $('.plan-step:has(.plan-notification)').each(function(){
        var step = [],
            $this = $(this);

        $this.find('.plan-notification').each(function(){
          var notification = {};

          $(this).find('input:not(".tt-hint"):visible:not(".toggle-dynamic"),select:visible,textarea').each(function(){
            var $current = $(this),
                type = $current.attr('data-type'),
                val = $current.val();

            if (!val || !$current.get(0).checkValidity()) {
              $current.addClass('invalid-input');
              //iris.createAlert('Missing fields: ' + type, 'danger', $('.plan-details') );
              missingFields.push(type);
              model.isValid = false;
            }
            val = isNaN(val) ? val : parseFloat(val);
            // convert minutes to seconds for API
            if (type === 'wait') {
              val = Math.round(val * 60);
            }
            // convert count to repeat for API
            else if (type === 'count') {
              notification['repeat'] = val - 1;
            }
            notification[type] = val;
          });
          step.unshift(notification);
        });
        model.steps.push(step);
      });

      // collect data for tracking templates

      if ($trackingEl.length) {
        if ($trackingEl.find('.template-notification').length == 0) {
            return true;
        }

        model.tracking_type = $trackingEl.find('#tracking-type').val();
        model.tracking_key = $trackingEl.find('#tracking-key').val();

        if (!model.tracking_key) {
          $trackingEl.find('#tracking-key').addClass('invalid-input');
          missingFields.push('tracking target');
          model.isValid = false;
        }

        if (!model.tracking_type) {
          $trackingEl.find('#tracking-type').addClass('invalid-input');
          missingFields.push('tracking type');
          model.isValid = false;
        }

        if (model.tracking_type === 'slack' && (!model.tracking_key.startsWith('#') && !model.tracking_key.startsWith('@'))) {
          $trackingEl.find('#tracking-key').addClass('invalid-input');
          iris.createAlert('Invalid field for slack target: target must start with # for channels or @ for users', 'danger', $('.plan-details'));
          model.isValid = false;
        }

        if ($('.tracking-template-step').length === 0) {
          missingFields.push('tracking template');
          model.isValid = false;
        }

        model.tracking_template = {};
        $trackingEl.find('.tracking-template-step').each(function(){
          var $this = $(this),
              app = $this.find('.template-application').val(),
              template = {};

          if (!app) {
            $this.find('.template-application').addClass('invalid-input');
            missingFields.push();
            model.isValid = false;
          }

          $this.find('.template-notification:visible').each(function(){
            var $notification = $(this),
                type = $notification.attr('data-mode'),
                content = $notification.find('.notification-body').val();

            if (content) {
              template[type] = content;
            } else if ($notification.attr('data-required')) {
              $notification.find('input, textarea').addClass('invalid-input');
              missingFields.push(type);
              model.isValid = false;
            }
          });

          if (Object.keys(template).length === 0) {
            iris.createAlert('Empty tracking template for application: ' + app);
            model.isValid = false;
            $this.addClass('invalid-input');
          } else {
            model.tracking_template[app] = template;
          }

        });
      }

      if (missingFields.length > 0) {
        iris.createAlert('Missing fields: ' + missingFields.join(', '), 'danger', $('.plan-details'))
      }
    },
    createPlan: function(e){
      var model = this.data.submitModel,
          self = this,
          $button = $(e.target);
      $button.parents('.modal').modal('hide');
      this.updateSubmitModel();

      if (model && model.isValid) {
        $button.prop('disabled', true);
        $.ajax({
          url: self.data.url,
          data: JSON.stringify(model),
          method: 'POST',
          contentType: 'application/json'
        }).done(function(response){
          if (response) {
            window.onbeforeunload = null; //detach confirm dialog if plan is published.
            window.location = response;
          }
        }).fail(function(response){
          var errorTxt = (response && response.responseJSON && response.responseJSON.description) ? response.responseJSON.title + ' - ' + response.responseJSON.description : 'Plan creation failed.';
          $button.prop('disabled', false);
          iris.createAlert('Error: ' + errorTxt);
        });
      }
    },
    updateTrackingPlaceholder: function(e){
      var $type = $(e.target),
          $key = $(this.data.trackingKey);

      if ($type.val() === 'email') {
        $key.attr('placeholder', 'example@example.com');
      } else {
        $key.attr('placeholder', 'example');
      }
    },
    updateNotificationFields: function(e){
      var $type = $(this.data.trackingType).find(":selected").text();

      if ($type === 'email') {
        $(".template-notification[data-mode='email_subject']").show();
        $(".template-notification[data-mode='email_text']").show();
        $(".template-notification[data-mode='email_html']").show();
        $(".template-notification[data-mode='body']").hide().val("");
      } else {
        $(".template-notification[data-mode='email_subject']").hide().val("");
        $(".template-notification[data-mode='email_text']").hide().val("");
        $(".template-notification[data-mode='email_html']").hide().val("");
        $(".template-notification[data-mode='body']").show();
      };
    },
    drag: {
      data: {
        $draggedEl: null
      },
      init: function(){
        var self = this,
            $planDetails = $('.plan-details');

        $planDetails.on('dragstart','.plan-notification[data-mode="edit"]', self.handleDragStart.bind(this));
        $planDetails.on('dragend','.plan-notification[data-mode="edit"]', self.handleDragEnd.bind(this));
        $planDetails.on('dragover','.plan-step[data-mode="edit"]', self.handleDragOver);
        $planDetails.on('dragenter','.plan-step[data-mode="edit"]', self.handleDragEnter);
        $planDetails.on('dragleave','.plan-step[data-mode="edit"]', self.handleDragLeave);
        $planDetails.on('drop', '.plan-step[data-mode="edit"]', self.handleDrop);
        $planDetails.on('dragstart', '.plan-step[data-mode="edit"]', self.handleStepStart.bind(this));

        //Allow text highlighting
        //Fix for HTML5 drag and drop - removes drag attribute on input focus and re-adds it on blur

        $planDetails.on('focus','.plan-notification[data-mode="edit"] input', function(){
          $(this).parents('.plan-notification, .plan-step').attr('draggable', false);
        });

        $planDetails.on('blur','.plan-notification[data-mode="edit"] input', function(){
          $(this).parents('.plan-notification, .plan-step').attr('draggable', true);
        });
      },
      handleStepStart: function(e){
        e = e.originalEvent;
        if (!$(e.target).hasClass('plan-notification')) {
          iris.typeahead.destroy();
          this.data.$draggedEl = $(e.target);
          e.dataTransfer.effectAllowed = "move";
          e.dataTransfer.dropEffect = "move";
          e.dataTransfer.setData('text/html', e.target.innerHTML);
        }
      },
      handleDragStart: function(e){
        e = e.originalEvent;
        e.stopPropagation();
        this.data.$draggedEl = $(e.target);
        iris.typeahead.destroy();
        $(e.target).addClass('dragging').parents('.plan-step').addClass('drag-source');
        e.dataTransfer.effectAllowed = "move";
        e.dataTransfer.setData('text/html', e.target.outerHTML);
      },
      handleDragEnd: function(e){
        typeahead.init();
        $(e.target).removeClass('dragging');
        $('.drag-source').removeClass('drag-source');
      },
      handleDragOver: function(e){
        $(this).addClass('drag-over');
        e.preventDefault();
        return false;
      },
      handleDragEnter: function(e){
        e.preventDefault();
        $(this).addClass('drag-over');
      },
      handleDragLeave: function(e){
        e.preventDefault();
        $(this).removeClass('drag-over');
      },
      handleDrop: function(e){
          var parent = iris.plan,
            self = parent.drag,
            $this = $(this);
        e.preventDefault();
        e = e.originalEvent;

        if(!$this.hasClass('drag-source')) {

          //if dropped item is a step, switch steps, else move notification
          if(self.data.$draggedEl && self.data.$draggedEl.hasClass('plan-step')){
            var temp = $this.html();
            $this.html(e.dataTransfer.getData('text/html'));
            self.data.$draggedEl.html(temp);
            iris.typeahead.init();
          } else {
            $this.prepend(e.dataTransfer.getData('text/html'));
            self.data.$draggedEl.remove();
            iris.typeahead.init();
          }
        }
        $('.drag-over').removeClass('drag-over');
        $('.dragging').removeClass('dragging');
        $('.drag-source').removeClass('drag-source');
      }
    },
    getVariablesForApp: function(app){
      var apps = window.appData.applications;
      // get variables for apps in current template application
      for (var i = 0; i < apps.length; i++){
        if (apps[i].name === app) {
          var variables = {};
          for (var j = 0; j < apps[i].variables.length; j++){
            variables[apps[i].variables[j]] = {
              'required': apps[i].required_variables.indexOf(apps[i].variables[j]) !== -1
            };
          }
          return variables;
        }
      }
    },
    renderVariables: function(e){
      var $el = $(e.target),
          app = $el.val(),
          template = Handlebars.compile(this.data.variablesTemplateSource);
      $el.parents().find('.variables').html(template({variables: this.getVariablesForApp(app)}));
    }
  }, //end iris.plan
  templates: {
    initialized: false,
    data: {
      url: '/v0/templates',
      $page: $('.main'),
      $table: $('#templates-table'),
      $filterForm: $('#filter-form'),
      tableTemplate: $('#templates-table-template').html(),
      DataTable: null,
      dataTableOpts: {
        orderClasses: false,
        order: [[2, 'desc']]
      }
    },
    init: function(){
      iris.changeTitle('Templates');
      if (this.initialized == false) {
        this.initFilters();
        this.events();
        this.initialized = true;
      }
      iris.tables.filterTable.call(this);
    },
    events: function(){
      var data = this.data,
          $table = data.$table;

      $table.on('click', 'tbody tr[data-route]', function(e){
        if (e.target.tagName !== 'A') {
          if ((e.ctrlKey || e.metaKey)) {
            window.open('/templates/' + $(this).attr('data-route'));
          } else {
            window.location = '/templates/' + $(this).attr('data-route');
          }
        }
      });
      data.$filterForm.on('submit', iris.tables.filterTable.bind(this));
    },
    getData: function(params){
      return $.getJSON(this.data.url, params).fail(function(){
        iris.createAlert('No templates found');
      });
    },
    initFilters: function(){
      iris.typeahead.init('user');
      $('.datetimepicker').datetimepicker({
        showClose: true,
        keyBinds: {'delete': null}
      });
    }
  }, //end iris.templates
  template: {
    data: {
      url: '/v0/templates/',
      name: '',
      id: null,
      path: null,
      $page: $('.main'),
      createTemplateBtn: '#publish-template',
      cloneTemplateBtn: '#clone-template',
      addStepBtn: '#add-step',
      removeStepBtn: '.remove-step',
      templateSteps: '.template-steps',
      appSelect: '.template-application',
      versionSelect: '.version-select',
      activateTemplate: '.badge[data-active="0"]',
      previewTemplate: '.preview-template',
      viewRelated: '.view-related',
      relatedPlans: [],
      stepTemplateSource: $('#template-step-template').html(),
      variablesTemplateSource: $('#variables-template').html(),
      templateSource: $('#template-template').html(),
      template: null,
      submitModel: {},
      modes: window.appData.modes,
      user: window.appData.user,
      blankModel: {
        applications: window.appData.applications,
        modes: window.appData.modes,
        content: {
          application: {}
        },
        viewMode: false,
        creator: window.appData.user
      }
    },
    init: function(){
      var location = window.location.pathname.split('/'),
          path = this.data.path = location[location.length - 1],
          self = this;

      for (var i = 0; i < window.appData.modes.length; i++) {
          this.data.blankModel.content.application[window.appData.modes[i]] = {
              body: "",
              subject: ""
          };
      }

      Handlebars.registerPartial('template-step', $('#template-step-template').html());
      Handlebars.registerPartial('template-notification', $('#template-notification-template').html());
      Handlebars.registerPartial('variables', this.data.variablesTemplateSource);
      this.getTemplate(path).done(function(){
        self.events();
        iris.versionTour.init();
      });
    },
    events: function(){
      var data = this.data;
      $(data.createTemplateBtn).on('click', this.createTemplate.bind(this));
      data.$page.on('click', data.cloneTemplateBtn, this.cloneTemplate.bind(this));
      data.$page.on('click', data.addStepBtn, this.addStep.bind(this));
      data.$page.on('click', data.removeStepBtn, this.removeStep);
      data.$page.on('click', data.previewTemplate, this.previewTemplate);
      data.$page.on('click', data.activateTemplate, this.activateTemplate.bind(this));
      data.$page.on('click', data.viewRelated, this.viewRelated.bind(this));
      data.$page.on('change', data.appSelect, this.renderVariables.bind(this));
      data.$page.on('change', data.versionSelect, this.loadVersion.bind(this));
      window.onbeforeunload = iris.unloadDialog.bind(this);
    },
    addStep: function(){
      var template = Handlebars.compile(this.data.stepTemplateSource);
      $(this.data.addStepBtn).before(template(this.data.blankModel));
    },
    removeStep: function(){
      $(this).parents('.step').remove();
    },
    getTemplate: function(path){
      var self = this,
          template = this.data.template = Handlebars.compile(this.data.templateSource);

        //if path is new, render in edit mode.
        if (path === 'new') {
          var response = self.data.blankModel;
          response.viewMode = false;
          for (var i = 0, keys = Object.keys(response.content); i < keys.length; i++) {
            response.content[keys[i]].variables = self.getVariablesForApp(keys[i]);
          }
          self.data.$page.html(template(self.data.blankModel));
          iris.changeTitle('Edit Template');
          return $.Deferred().resolve();
        } else {
          self.data.viewMode = true;
          return $.getJSON(this.data.url + path).done(function(response){
            //shim response data for handlebars
            response.viewMode = true;
            response.applications = window.appData.applications;
            response.modes = window.appData.modes;
            for (var i = 0, keys = Object.keys(response.content); i < keys.length; i++) {
              response.content[keys[i]].application = keys[i];
              response.content[keys[i]].variables = self.getVariablesForApp(keys[i]);
              for (var j = 0; j < window.appData.modes.length; j++) {
                if (!(window.appData.modes[j] in response.content[keys[i]])) {
                  response.content[keys[i]][window.appData.modes[j]] = {
                    body: "",
                    subject: ""
                  };
                }
              }
            }
            self.data.ajaxResponse = response; //set response in root object for clone path functionality.
            self.data.id = response.id;
            self.data.name = response.name;
            self.data.relatedPlans = response.plans;
            self.data.$page.html(template(response));
            self.loadVersionSelect();
            iris.changeTitle('Template ' + self.data.name);
          }).fail(function(){
            iris.createAlert('"' + path +'" template not found. <a href="/templates/new">Create a new template.</a>');
          });
        }
    },
    getVariablesForApp: function(app){
      var apps = window.appData.applications;
      // get variables for apps in current template application
      for (var i = 0; i < apps.length; i++){
        if (apps[i].name === app) {
          var variables = {};
          for (var j = 0; j < apps[i].variables.length; j++){
            variables[apps[i].variables[j]] = {
              'required': apps[i].required_variables.indexOf(apps[i].variables[j]) !== -1
            };
          }
          return variables;
        }
      }
    },
    renderVariables: function(e){
      var $el = $(e.target),
          app = $el.val(),
          self = this,
          template = Handlebars.compile(self.data.variablesTemplateSource);

      self.data.$page.find(self.data.previewTemplate).css('display', !app ? 'none' : '');
      $el.parents('.step').find('.variables').html(template({variables: this.getVariablesForApp(app)}));
    },
    cloneTemplate: function(){
      var response = this.data.ajaxResponse;
      response.viewMode = false;
      this.data.$page.html(this.data.template(response));
    },
    loadVersionSelect: function(){
      var $select = $(this.data.versionSelect),
          self = this,
          frag = '';

      $.ajax({
        url: this.data.url,
        data: {
          name:this.data.name,
          fields: ['id', 'created', 'creator']
        },
        traditional: true
      }).done(function(r){
        r = r.reverse();
        for (var i = 0; i < r.length; i++) {
          var entry = r[i];
          frag += [
            '<option ', ((entry.id === self.data.id) ? 'selected' : ''),
            ' value="', entry.id,
            '" data-id="', entry.id + '">',
            'ID ', entry.id, ' - ',
            moment.unix(entry.created).local().format('YYYY/MM/DD HH:mm:ss [GMT]ZZ'),
            ' by ', entry.creator,
            ' </option>'
          ].join('');
        }
        $select.append(frag);
      });
    },
    loadVersion: function(e){
      var versionId = $(e.target).find('option:selected').attr('data-id');
      this.getTemplate(versionId);
      window.history.pushState(null, null, versionId); //update url bar for refreshes
    },
    activateTemplate: function(e){
      var $el = $(e.target);

      $.ajax({
          url: this.data.url + this.data.id,
          data: JSON.stringify({active: 1}),
          method: 'POST',
          contentType: 'application/json'
      }).done(function(){
        $el.attr('data-active', 1);
      }).fail(function(){
        iris.createAlert('Failed to activate.');
      });
    },
    viewRelated: function(e){
      var $modal = $($(e.target).attr('data-target')),
          frag = '',
          data = this.data.relatedPlans;
      if (data.length) {
        for (var i = 0; i < data.length; i++) {
          frag += '<li class="border-bottom"><a href="/plans/' + data[i]["id"] + '">' + data[i]["name"] + ' - ID ' + data[i]['id']+ '</a></li>';
        }
        $modal.find('.modal-body .modal-list').html(frag);
      } else {
        $modal.find('.modal-body .modal-list').text('No plans are currently using this template.');
      }
    },
    updateSubmitModel: function(){
      // gets & checks data from plan inputs and prepares model for post.
      var model = this.data.submitModel,
          $steps = $(this.data.templateSteps),
          self = this;

      model.creator = this.data.user;
      model.name = $('#template-name').val();
      model.content = {};
      $steps.find('.step').each(function(){
        var $this = $(this),
            application = $this.find('.template-application').val();

        model.content[application] = {};

        $this.find('.template-notification').each(function(){
          var mode = $(this).attr('data-mode');

          if (mode === 'email') {
            model.content[application][mode] = {
              subject: $(this).find('.template-subject').val(),
              body: $(this).find('.template-body').val()
            }
          } else {
            model.content[application][mode] = {
              subject: '',
              body: $(this).find('.template-body').val()
            }
          }
        });
      });

      // reset invalid inputs from previous check
      this.data.$page.find('.invalid-input').removeClass('invalid-input');
      if (
        $('.view-template select, .view-template input, .view-template textarea')
        .filter(function(){
          return !$(this).val();
        })
        .addClass('invalid-input')
        .length > 0
      ){
        model.isValid = false;
        iris.createAlert('Missing fields', 'danger', this.data.$page);
      } else if($steps.find('.step').length > 1){
        var apps = [];
        model.isValid = true;
        $steps.find('.template-application').each(function(){
          var val = $(this).val();
          if (apps.indexOf(val) !== -1) {
            $(this).addClass('invalid-input');
            model.isValid = false;
            iris.createAlert('Please pick a unique application for each template.', 'danger', self.data.$page);
          } else {
            apps.push(val);
          }
        });
      } else {
        model.isValid = true;
      }
    },
    previewTemplate: function(){
      var $this = $(this),
          mode = $this.attr('data-mode'),
          $notification = $this.parents('.template-notification'),
          $step = $this.parents('.step'),
          submitModel = {},
          $modal = $('#preview-template-modal'),
          $modalBody = $modal.find('.modal-body');

      submitModel.application = $step.find('.template-application').val();
      if (!submitModel.application) {
          iris.createAlert('Please select an application.', 'danger', $modalBody);
          return;
      }
      submitModel.templateSubject = $notification.find('.template-subject').val() || ' ';
      submitModel.templateBody = $notification.find('.template-body').val();

      $modalBody.html('<i class="loader"></i>');
      $.ajax({
        type: 'post',
        url: '/validate/jinja',
        dataType: 'json',
        data: submitModel
      }).done(function(r){
        if (r.template_subject || r.template_body) {
          $modalBody.html( ( (mode === 'email') ? '<div class="preview-subject">' + Handlebars.helpers.breakLines(r.template_subject) + '</div>' : '') + '<div class="preview-body">' + Handlebars.helpers.breakLines(r.template_body) + '</div>');
        } else {
          $modalBody.empty();
          iris.createAlert('Invalid template', 'danger', $modalBody);
        }
      }).fail(function(r){
        $modalBody.empty();
        if (r.responseJSON && r.responseJSON.lineno && r.responseJSON.error) {
          iris.createAlert('Line: ' + r.responseJSON.lineno + '<br />Error: ' + r.responseJSON.error, 'danger', $modalBody);
        } else if (r.responseJSON && r.responseJSON.error) {
          iris.createAlert('Error: ' + r.responseJSON.error, 'danger', $modalBody);
        } else {
          iris.createAlert('Invalid template', 'danger', $modalBody);
        }
      });
    },
    createTemplate: function(e){
      var model = this.data.submitModel,
          self = this,
          $button = $(e.target);

      this.updateSubmitModel();

      if (model && model.isValid) {
        $button.prop('disabled', true);
        $.ajax({
          url: self.data.url,
          data: JSON.stringify(model),
          method: 'POST',
          contentType: 'application/json'
        }).done(function(response){
          if (response) {
            window.onbeforeunload = null; //detach confirm dialog if plan is published.
            window.location = response;
          }
        }).fail(function(response){
          var errorTxt = (response && response.responseJSON && response.responseJSON.error) ? response.responseJSON.error : 'Template creation failed.';
          $button.prop('disabled', false);
          iris.createAlert('Error: ' + errorTxt);
        });
      }
    }
  }, //end iris.template
  incidents: {
    initialized: false,
    data: {
      url: '/v0/incidents/',
      fields: ['id', 'owner', 'application', 'plan', 'plan_id', 'created', 'updated', 'active', 'current_step'],
      $page: $('.main'),
      $table: $('#incidents-table'),
      $filterApp: $('#filter-application'),
      $filterForm: $('#filter-form'),
      $claimModal: $('#claim-modal'),
      claimModalBtn: '#show-claim',
      claimBtn: '#claim-active-btn',
      tableTemplate: $('#incidents-table-template').html(),
      summaryCtxHash: {},
      processedIds: {},
      incidentData: null,
      DataTable: null,
      dataTableOpts: {
        dom: '<"claim-container"><"pull-right"l>ftip',
        order: [[0, 'desc']],
        columns: [
          null,
          null,
          {width: '25%'},
          null,
          { width: '10%' },
          { width: '10%' },
          null,
          null
        ]
      }
    },
    init: function(){
      iris.changeTitle('Incidents');
      var self = this;
      if (this.initialized == false) {
        this.initFilters();
        this.events();
        this.initialized = true;
      }
      this.data.dataTableOpts.fnInitComplete = this.tableDrawComplete.bind(self);
      iris.tables.filterTable.call(this);
      this.data.$table.on('draw.dt', self.getSummary.bind(self));
    },
    tableDrawComplete: function() {
      var self = this,
          $empty = $('.dataTables_empty');
      if ($empty && $('#filter-active').prop('checked')) {
        $empty.append($('<br>'))
              .append($('<a>Try viewing all incidents instead of just active incidents</a>')
                        .click(function() {
                           $('#filter-all').prop('checked', 'true');
                           self.data.$filterForm.submit();
                        }));
      }
      $('div.claim-container').html('<button type="button" class="btn btn-default blue btn-sm" id="show-claim">Claim Active Incidents</button>');
    },
    events: function(){
      var data = this.data,
          $table = data.$table,
          self = this;

      $table.on('click', 'tbody tr[data-route]', function(e){
        if (e.target.tagName !== 'A') {
          if ((e.ctrlKey || e.metaKey)) {
            window.open('/incidents/' + $(this).attr('data-route'));
          } else {
            window.location = '/incidents/' + $(this).attr('data-route');
          }
        }
      });

      $table.on('click', '.claim-incident', function(e){
        e.stopPropagation();
        self.claimIncident(e);
      });
      data.$page.on('click', self.data.claimModalBtn, self.claimModal.bind(this));
      data.$claimModal.on('click', self.data.claimBtn, self.claimActive.bind(this));
      data.$filterForm.on('submit', iris.tables.filterTable.bind(this));
    },
    claimModal: function(){
      var actives = this.data.DataTable.rows(function(_, _, x) { return x.getAttribute('data-active') === '1' }),
          activeCount = actives.flatten().length;
      if (activeCount > 0) {
        $('#active-count').html(actives.flatten().length);
        this.data.$claimModal.modal();
      } else {
        iris.createAlert('No active incidents to claim');
      }
    },
    claimActive: function(){
      var self = this,
          actives = this.data.DataTable.rows(function(_, _, x) { return x.getAttribute('data-active') === '1' }),
          activeIds = [];
      activeIds = Array.from(actives.nodes().map(function(x) { return x.getAttribute('data-route') }));
       $.ajax({
        url: self.data.url + 'claim',
        data: JSON.stringify({
          owner: window.appData.user,
          incident_ids: activeIds
        }),
      method: 'POST',
      contentType: 'application/json'
      }).done(function(data) {
        iris.createAlert('Successfully claimed ' + data.claimed.length + ' incidents: ' + data.claimed.join(', '), 'success');
        self.init();
      }).fail(function() {
        iris.createAlert('Error: failed to claim incidents');
      }
      )
    },
    getData: function(params){
      //merge params and fields
      params['fields'] = this.data.fields;
      params['limit'] = iris.data.tableEntryLimit;

      return $.ajax({
        url: this.data.url,
        data: params,
        traditional: true
      }).fail(function(){
        iris.createAlert('No incidents found');
      });
    },
    getSummary: function(){
      var self = this,
          params = {
            'fields': ['id', 'context', 'application'],
            'id__in': null
          },
          templateSource = $('#summary-template').html(),
          getCtxRequest = new $.Deferred(),
          idList = [],
          $summaryTh = this.data.$table.find('td.summary');

      this.data.$table.find('tbody tr').each(function(){
        var id = $(this).data('route');
        if (id) {
          idList.push(id);
        }
      });

      if (idList.length) {
        var filteredIdList = idList.filter(function(i){ return self.data.summaryCtxHash[i] === undefined});

        if (filteredIdList.length) {
          params.id__in = filteredIdList.toString();
          $summaryTh.attr('data-loading', true);
          getCtxRequest = $.ajax({
            url: self.data.url,
            data: params,
            traditional: true
          }).done(function(data){
            for (var i = 0; i < data.length; i++) {
              var item = data[i];
              self.data.summaryCtxHash[item.id] = item;
            }
          });
        } else {
          var newIds = false;
          // invalidate datatable so search will work on the dynamic loaded context
          self.data.DataTable.rows().every( function ( rowIdx, tableLoop, rowLoop ) {
              if(!(self.data.DataTable.row( this ).data()[0] in self.data.processedIds)){
                newIds = true;
                self.data.processedIds[self.data.DataTable.row( this ).data()[0]] = 1;
                this.invalidate();
              }
          } );
          if(newIds){
            // redraw datatable only if there is any new data
            self.data.DataTable.draw();
          }
          getCtxRequest.resolve(); // resolve promise if all data exists in hash
        }

        getCtxRequest.done(function(){
          $summaryTh.attr('data-loading', '');
          for (var i = 0; i < idList.length; i++) {
            var item = self.data.summaryCtxHash[idList[i]],
                app = window.appData.applications.filter(function(l){ return l.name === item.application })[0],
                currTmplSrc,
                template;

            currTmplSrc = app && app.summary_template || templateSource;
            template = Handlebars.compile(currTmplSrc);
            self.data.$table.find('tr[data-route=' + item.id + '] .incident-summary').html(template(item));
          }
        });
      }

    },
    initFilters: function(){
      for (var i = 0; i < window.appData.applications.length; i++) {
        this.data.$filterApp.append($(document.createElement('option')).attr({ value: window.appData.applications[i].name }).text(window.appData.applications[i].name));
      }
      iris.typeahead.init('user');
      $('.datetimepicker').datetimepicker({
        showClose: true,
        keyBinds: {'delete': null}
      });
    },
    claimIncident: function(e){
      var $this = $(e.target),
          owner = $this.attr('data-action') === 'claim' ? window.appData.user : null,
          self = this,
          incidentId = $this.attr('data-id');
      $this.prop('disabled', true);
      $.ajax({
        url: self.data.url + incidentId,
        data: JSON.stringify({
          owner: owner
        }),
        method: 'POST',
        contentType: 'application/json'
      }).done(function(data){
        var active = data.active;
        incidentId = data.incident_id;
        var message = active ? 'Incident ' + incidentId + ' unclaimed.' : 'Incident ' + incidentId + ' claimed.';
        iris.createAlert(message, 'success', null, null, 'fixed');
        $this.attr('data-action', active ? 'claim' : 'unclaim')
          .text(active ? 'Claim Incident' : 'Unclaim Incident')
          .parents('tr[data-route=' + incidentId + ']')
          .attr('data-active', active ? 1 : 0)
          .find('.owner').text(active ? 'Unclaimed' : owner);
      }).fail(function(){
        iris.createAlert('Failed to modify incident', 'danger');
      }).always(function(){
        $this.prop('disabled', false);
      });
    }
  },
  incident: {
    data: {
      url: '/v0/incidents/',
      $page: $('.main'),
      id: null,
      incident: null,
      appPlanUrl: '/v0/applications/',
      planTypeahead: 'input.typeahead',
      showReescalateBtn: '#re-escalate-modal-btn',
      reescalateModal: '#re-escalate-modal',
      selectPlanBtn: '#re-escalate-plan-btn',
      reescalateBtn: '#re-escalate-btn',
      reescalatePlanContainer: '.re-escalate-plan-container',
      claimIncidentBtn: '#claim-incident',
      showAddCommentBtn: '#show-comment',
      hideAddCommentBtn: '#cancel-comment',
      addCommentBtn: '#comment-incident',
      addCommentContainer: '#add-comment',
      commentContainer: '.comment-list',
      commentSource: $('#comment-template').html(),
      incidentSource: $('#incident-template').html(),
      messageTable: '#incident-messages-table',
      DataTable: null,
      dataTableOpts: {
        orderClasses: false,
        order: [[0, 'desc']],
        oLanguage: {
          sEmptyTable: "No messages have been sent for this incident.",
          sZeroRecords: "No messages have been sent for this incident."
        }
      }
    },
    init: function(){
      var location = window.location.pathname.split('/'),
          path = this.data.id = location[location.length - 1],
          self = this;
      Handlebars.registerPartial('comment', this.data.commentSource);
      this.getIncident(path).done(function() {
        self.events();
      })
    },
    events: function(){
      var data = this.data;
      data.$page.on('click', data.claimIncidentBtn, this.claimIncident.bind(this));
      data.$page.on('click', data.showAddCommentBtn, this.showComment.bind(this));
      data.$page.on('click', data.hideAddCommentBtn, this.hideComment.bind(this));
      data.$page.on('click', data.addCommentBtn, this.addComment.bind(this));
      data.$page.on('click', data.showReescalateBtn, this.reescalateModal.bind(this));
      data.$page.on('click', data.selectPlanBtn, this.selectPlan.bind(this));
      data.$page.on('click', data.reescalateBtn, this.reescalateIncident.bind(this));
    },
    showComment: function() {
      $(this.data.addCommentContainer).show();
      $(this.data.showAddCommentBtn).hide();
      $('#comment-body').focus();
    },
    hideComment: function() {
      $('#comment-body').val('');
      $(this.data.addCommentContainer).hide();
      $(this.data.showAddCommentBtn).show();
    },
    addComment: function() {
      var self = this,
          commentContainer = $(self.data.addCommentContainer),
          comment = {
            author: window.appData.user,
            content: $('#comment-body').val()
          };
      if (comment.content === '') {
        iris.createAlert('Error: Empty comment body', 'danger', commentContainer)
      } else {
        $.ajax({
          url: self.data.url + self.data.id + '/comments',
          data: JSON.stringify(comment),
          method: 'POST',
          contentType: 'application/json'
        }).done(function() {
          var template = Handlebars.compile(self.data.commentSource);
          comment['created'] = Date.now() / 1000;
          $(self.data.commentContainer).append(template(comment));
          $('.no-comments').hide();
          self.hideComment();
        }).fail(function(){
          iris.createAlert('Failed to post comment', 'danger', commentContainer)
        })
      }
    },
    getIncident: function(path){
      var self = this;

      return $.getJSON(self.data.url + path).done(function(response){
        self.data.incident = response;
        iris.changeTitle('Incident #' + response.id);
        $.getJSON('/v0/applications/' + response.application).done(function(application){
          if (application.context_template) {
            Handlebars.registerPartial('context_template', application.context_template);
          }
          var template = self.data.template = Handlebars.compile(self.data.incidentSource);
          response.user = window.appData.user;
          self.data.$page.html(template(response));
          self.data.DataTable = $(self.data.messageTable).DataTable(self.data.dataTableOpts);
          iris.tables.bindArrowKeys(self.data.DataTable);
        });
      }).fail(function(){
        iris.createAlert('Incident not found');
      });
    },
    claimIncident: function(e){
      var $this = $(e.target),
          owner = $this.attr('data-action') === 'claim' ? window.appData.user : null,
          self = this,
          incidentId = $this.attr('data-id');
      $this.prop('disabled', true);
      $.ajax({
        url: self.data.url + incidentId,
        data: JSON.stringify({
          owner: owner
        }),
        method: 'POST',
        contentType: 'application/json'
      }).done(function(){
        self.getIncident(incidentId).done(function(){
          var message = owner ? 'Incident ' + incidentId + ' claimed.' : 'Incident ' + incidentId + ' unclaimed.';
          iris.createAlert(message, 'success');
        });
      }).fail(function(){
        iris.createAlert('Failed to modify incident', 'danger');
      }).always(function(){
        $this.prop('disabled', false);
      });
    },
    reescalateModal: function() {
      var $modal = $(this.data.reescalateModal),
          self = this;
          $field = $(self.data.planTypeahead);
      $field.typeahead('destroy').each(function(){
        var $this = $(this),
            remoteUrl = self.data.appPlanUrl + self.data.incident.application + '/plans?name__contains=%QUERY';
            results = new Bloodhound({
              datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
              queryTokenizer: Bloodhound.tokenizers.whitespace,
              remote: {
                url: remoteUrl,
                wildcard: '%QUERY',
              }
            });
        $this.typeahead(null, {
          hint: true,
          async: true,
          highlight: true,
          display: 'name',
          source: results,
        }).on('typeahead:select', function(e, plan){
          $this.attr('value', plan.name);
          self.previewPlan(plan);
        }).on('input', function(e) {
          $this.attr('value', e.target.value);
        });
      });
      $modal.modal();
    },
    // Extra button to select re-escalation plan if plan name is typed fully
    selectPlan: function() {
      var planName = $('#re-escalate-typeahead').attr('value'),
          self = this;

      self.resetPlanPreview();
      // Get plan details from name
      $.ajax({
        url: '/v0/plans',
        data: {name: planName, active: 1},
        method: 'GET',
        contentType: 'application/json'
      }).done(function(r) {
        if (!r.length) {
          iris.createAlert('No plan found for query: ' + planName, 'danger', $('.modal-body'));
        } else {
          self.previewPlan(r[0]);
        }
      }).fail(function() {
        iris.createAlert('No plan found for query: ' + planName, 'danger', $('.modal-body'));
      })
    },
    resetPlanPreview: function() {
      $(this.data.reescalateBtn).prop('disabled', true);
      $(this.data.reescalatePlanContainer).html('');
    },
    previewPlan: function(plan) {
      var planContainer = $(this.data.reescalatePlanContainer),
          planTemplate = Handlebars.compile($('#re-escalation-plan-template').html())
          self = this;
      // Get plan steps to preview re-escalation plan
      $.ajax({
        url: '/v0/plans/' + plan.id,
        method: 'GET',
      }).done(function(r) {
        // Dynamic plans will have steps that define dynamic_index
        // These can't be used for re-escalation
        dynamic = r.steps.some(function(step){
          return step.some(function(notification){
            return notification.dynamic_index != undefined })
          });
        if (dynamic) {
          self.resetPlanPreview();
          iris.createAlert('Cannot re-escalate with dynamic plan: ' + plan.name, 'danger', $('.modal-body'))
        } else {
          iris.dismissAlerts();
          planContainer.html(planTemplate(r));
          $(self.data.reescalateBtn).prop('disabled', false);
        }
      }).fail(function(r) {
        self.resetPlanPreview();
        iris.createAlert('Failed to fetch plan details', 'danger', $('.modal-body'))
      })
    },
    /* Re-ecalate an incident. This triggers the following actions:
       1. Claim the incident if it is unclaimed
       2. Re-raise the incident with the specified escalation plan
       3. Comment on the original incident with a link to the new incident
       4. Comment on the new incident with a link to the original
       5. Navigate to the new incident page
    */
    reescalateIncident: function() {
      var self = this,
          $modal = $('.modal-body'),
          plan = $('#re-escalate-typeahead').attr('value'),
          claim;
      // Claim incident if necessary
      if (self.data.incident.active) {
        claim = $.ajax({
          url: self.data.url + self.data.incident.id,
          data: JSON.stringify({
            owner: window.appData.user
          }),
          method: 'POST',
          contentType: 'application/json'
        }).fail(function() {
          iris.createAlert('Failed to claim original incident', 'danger', $modal);
        })
      } else {
        claim = $.Deferred().resolve();
      }
      // Then, create a new incident
      claim.then(function() {
        return $.ajax({
          url: '/v0/incidents',
          data: JSON.stringify({
            application: self.data.incident.application,
            context: self.data.incident.context,
            plan: plan
          }),
          method: 'POST',
          contentType: 'application/json'
        }).fail(function(){
          iris.createAlert('Failed creating re-escalation incident', 'danger', $modal);
        })
      // Finally, post comments to new and original incidents
      }).then(function(reescalateId){
        var author = window.appData.user;
        var comment = {
          author: window.appData.user,
          content: 'Incident re-escalated as [incident ' + reescalateId + '](/incidents/'
            + reescalateId + ') by ' + author
        }
        var reescalationComment = {
          author: window.appData.user,
          content: 'Incident re-escalated from [incident ' + self.data.id + '](/incidents/'
          + self.data.id + ') by ' + author
        }
        $.when(
          $.ajax({
            url: self.data.url + self.data.id + '/comments',
            data: JSON.stringify(comment),
            method: 'POST',
            contentType: 'application/json'
          }),
          $.ajax({
            url: self.data.url + reescalateId + '/comments',
            data: JSON.stringify(reescalationComment),
            method: 'POST',
            contentType: 'application/json'
          })
        ).done(function() {
          window.location = '/incidents/' + reescalateId;
        }).fail(function(){
          iris.createAlert('Failed to post re-escalation comments for re-escalation incident ' + reescalateId,
            'danger', $modal);
        })
      })
    }
  },
  messages: {
    initialized: false,
    data: {
      url: '/v0/messages',
      fields: ['id', 'batch', 'target', 'subject', 'incident_id', 'priority', 'application', 'mode', 'sent', 'mode_changed', 'target_changed'],
      $page: $('.main'),
      $table: $('#messages-table'),
      $filterApp: $('#filter-application'),
      $filterPriority: $('#filter-priority'),
      $filterForm: $('#filter-form'),
      tableTemplate: $('#messages-table-template').html(),
      DataTable: null,
      dataTableOpts: {
        orderClasses: false,
        order: [[0, 'desc']],
        oLanguage: {
          sEmptyTable: "Use the filters above to find messages",
          sZeroRecords: "Use the filters above to find messages"
        },
        columns: [
          null,
          null,
          null,
          { width: '30%' },
          null,
          null,
          null,
          null,
          null
        ]
      }
    },
    init: function(){
      iris.changeTitle('Messages');
      if (this.initialized == false) {
        this.initFilters();
        this.events();
        this.initialized = true;
      }
      iris.tables.filterTable.call(this);
    },

    events: function(){
      var data = this.data,
          $table = data.$table;

      $table.on('click', 'tbody tr[data-route]', function(e){
        if (e.target.tagName !== 'A') {
          if ((e.ctrlKey || e.metaKey)) {
            window.open('/messages/' + $(this).attr('data-route'));
          } else {
            window.location = '/messages/' + $(this).attr('data-route');
          }
        }
      });
      data.$filterForm.on('submit', iris.tables.filterTable.bind(this));
    },
    getData: function(params){
      //merge params and fields
      params['fields'] = this.data.fields;
      params['limit'] = iris.data.tableEntryLimit;

      return $.ajax({
        url: this.data.url,
        data: params,
        traditional: true
      }).fail(function(){
        iris.createAlert('No messages found');
      });
    },
    initFilters: function(){
      for (var i = 0; i < window.appData.applications.length; i++) {
        this.data.$filterApp.append($(document.createElement('option')).attr({ value: window.appData.applications[i].name }).text(window.appData.applications[i].name));
      }
      for (i = 0; i < window.appData.priorities.length; i++) {
        this.data.$filterPriority.append($(document.createElement('option')).attr({ value: window.appData.priorities[i].name }).text(window.appData.priorities[i].name));
      }
      iris.typeahead.init('user');
      $('.datetimepicker').datetimepicker({
        showClose: true,
        keyBinds: {'delete': null}
      });
      // $('#filter-start').data('DateTimePicker').date(moment().subtract(1, 'hours'));
    }
  },
  message: {
    data: {
      url: '/v0/messages/',
      $page: $('.main'),
      incidentSource: $('#message-template').html()
    },
    init: function(){
      var location = window.location.pathname.split('/'),
          path = this.data.id = location[location.length - 1];
      this.getMessage(path);
    },
    getMessage: function(path){
      var self = this,
          template = this.data.template = Handlebars.compile(this.data.incidentSource),
          template_vars = {changes: []};
      $.getJSON(self.data.url + path).done(function(data) {
          if (data.generic_message_sent_status != null) {
              data.generic_message_sent_status = data.generic_message_sent_status ? 'sent' : 'failed to send';
          }
          $.extend(template_vars, data);
          $.getJSON(self.data.url + path + '/auditlog').done(function(data) {
            data.forEach(function(change) {
              var old_halves = change.old.split('|');
              if (old_halves.length == 2) {
                change.old = old_halves[1];
                change.old_role = old_halves[0];
              }
              change.change_type = change.change_type.split('-')[0];
            });
            template_vars.changes = data;
          }).always(function() {
            self.data.$page.html(template(template_vars));
            iris.changeTitle('Message #' + template_vars.id);
          });
      }).fail(function() {
          iris.createAlert('Message not found');
      });
    }
  },
    user: {
        data: {
            url: '/v0/users/',
            modesUrl: '/v0/modes',
            apiBase: '/v0/',
            postModesUrl: '/v0/users/modes/',
            reprioritizationUrl: '/v0/users/reprioritization/',
            settingsUrl: '/v0/users/settings/',
            overridesUrl: '/v0/users/overrides/',
            timezonesUrl: '/v0/timezones',
            user: window.appData.user,
            settings: null,
            reprioritizationSettings: [],
            supportedTimezones: [],
            supportedOverrideModes: [],
            $page: $('.main'),
            $priority: $('#priority-table'),
            $customOverrideTable: $('#custom-override-table'),
            $customOverrideModal: $('#custom-override-modal-content'),
            $batching: $('#batching-table'),
            $contact: $('.user-contact-module'),
            $saveBtn: $('#save-settings'),
            $saveModalBtn: $('#save-custom-override-modal'),
            $delOverrideBtn: $('#delete-override'),
            $reprioritizationToggleBtn: $('#reprioritization h4'),
            $reprioritizationAddBtn: $('#reprio-add-btn'),
            $reprioritizationTable: $('#reprioritization-table'),
            $addAppSelect: $('#add-application-select'),
            $addAppOverrideSelect: $('#add-application-override-select'),
            $addAppBtn: $('#add-application-button'),
            $addAppOverrideBtn: $('#add-application-override-button'),
            appsToDelete: {},
            priorityTemplate: $('#priority-template').html(),
            customOverrideTemplate: $('#custom-override-template').html(),
            createCustomOverrideModalTemplate: $('#custom-override-modal-template').html(),
            batchingTemplate: $('#batching-template').html(),
            subheaderTemplate: $('#user-contact-template').html(),
            reprioritizationTemplate: $('#reprioritization-table-template').html(),
            postModel: {},
            $timezoneSelect: $('#timezone-select'),
            $timezoneSave: $('#timezone-save'),
            $smsOverrideSelect: $('#sms-override-select'),
            $smsOverrideSave: $('#sms-override-save')
        },
        init: function(){
            iris.changeTitle('Settings');
            var self = this;
            this.supportedOverrideModes = window.appData.modes;
            // drop is ommitted add it for override options
            this.supportedOverrideModes.push('drop');

            this.getUserSettings().done(function(){
                self.getCustomOverrideSettings().done(function(){
                    self.createCustomOverrideTable();
                });
                self.createContactModule();
                self.createPriorityTable();
                self.populateSmsOverride();
                self.events();
            });
            this.getReprioritizationSettings().done(function(){
                self.populateReprioritization();
            });
            this.loadSupportedTimezones().done(function(){
                self.populateTimezone();
            });
        },
        events: function(){
            var self = this;
            this.data.$saveBtn.on('click', function(){
                self.saveSetting();
            });
            this.data.$saveModalBtn.on('click', function(){
                self.saveModal();
            });
            this.data.$delOverrideBtn.on('click', function() {
                self.deletePerCustomApp($(this).attr('data-app'));
            });
            this.data.$reprioritizationAddBtn.on('click', function(){
                $(this).prop('disabled', true);
                self.saveReprioritizationSettings();
            });
            this.data.$reprioritizationToggleBtn.on('click', function(){
                self.toggleReprioritization();
            });
            this.data.$addAppBtn.on('click', function(){
                self.addApplication();
            });
            this.data.$addAppOverrideBtn.on('click', function(){
                self.addApplicationOverride();
                self.data.$addAppOverrideBtn.prop('disabled', true);
            });
            this.data.$addAppSelect.change(function() {
                self.data.$addAppBtn.prop('disabled', $(this).val() == '');
            });
            this.data.$addAppOverrideSelect.change(function() {
                self.data.$addAppOverrideBtn.prop('disabled', $(this).val() == '');
            });
            this.data.$timezoneSelect.change(function() {
                self.data.$timezoneSave.prop('disabled', $(this).val() == '');
            });
            this.data.$timezoneSave.on('click', function(){
                self.saveTimezone();
            });
            this.data.$smsOverrideSelect.change(function() {
              self.data.$smsOverrideSave.prop('disabled', $(this).val() == '');
            });
            this.data.$smsOverrideSave.on('click', function(){
                self.saveSmsOverride();
                self.data.$smsOverrideSave.prop('disabled', true);
            });
            self.data.$saveBtn.prop('disabled', true);
            self.data.$addAppOverrideBtn.prop('disabled', true);
            self.data.$addAppBtn.prop('disabled', true);
        },
        getUserSettings: function() {
            var self = this;
            return $.getJSON(this.data.url + this.data.user).done(function(response) {
                self.data.settings = response;
                //add app data to settings.
                self.data.settings.priorities = window.appData.priorities;
                self.data.settings.modeSet = window.appData.modes;
                self.data.settings.applications = window.appData.applications;
            }).fail(function(response) {
                iris.createAlert('Error: Failed to load data -' + response.text);
            });
        },
        toggleReprioritization: function() {
            $('#reprioritization').toggleClass('active')
        },
        getReprioritizationSettings: function() {
            var self = this;
            return $.getJSON(this.data.reprioritizationUrl + this.data.user).done(function(response){
                self.data.reprioritizationSettings = response;
            }).fail(function(response){
                iris.createAlert('Error: Failed to load reprioritization data -' + response.text);
            });
        },
        getCustomOverrideSettings: function() {
            var self = this;
            return $.getJSON(this.data.url + this.data.user + '/categories').done(function(response){
                self.data.settings.categories = response;
                self.data.settings['customApp'] = new Set();
                self.data.settings.categories.forEach(function (item, index) {
                    self.data.settings.customApp.add(item['application']);
                });

            }).fail(function(response){
                iris.createAlert('Error: Failed to load notification category data -' + response.text);
            });
        },
        saveReprioritizationSettings: function() {
            var data = {},
                self = this;
            $('#reprioritization input, #reprioritization select').each(function() {
                data[$(this).data('type')] = $(this).val();
            });
            data['duration'] *= 60;
            $.ajax({
                url: this.data.reprioritizationUrl + this.data.user,
                data: JSON.stringify(data),
                method: 'POST',
                contentType: 'application/json'
            }).done(function(){
                iris.createAlert('Reprioritization added.', 'success');
                self.getReprioritizationSettings().done(function(){
                    self.populateReprioritization();
                });
            }).fail(function(response){
                iris.createAlert('Failed to add reprioritization: ' + response.responseJSON.description, 'danger');
            }).always(function(){
                self.data.$reprioritizationAddBtn.prop('disabled', false);
            });
        },
        deleteReprioritizationSetting: function(src_mode) {
            var self = this;
            $.ajax({
                url: this.data.reprioritizationUrl + this.data.user + '/' + src_mode,
                method: 'DELETE',
                contentType: 'application/json',
                data: '{}'
            }).done(function(){
                iris.createAlert('Reprioritization rule deleted.', 'success');
                self.getReprioritizationSettings().done(function(){
                    self.populateReprioritization();
                });
            }).fail(function(){
                iris.createAlert('Failed deleting', 'danger');
            });
        },
        populateReprioritization: function() {
            $('#reprioritization select').each(function(){
                var frag = '';
                window.appData.modes.forEach(function(mode) {
                    frag += ['<option>', mode, '</option>'].join('');
                });
                $(this).html(frag)
            });
            $('#reprio-dest-mode').val('sms');

            if (!this.data.reprioritizationSettings.length) {
                this.data.$reprioritizationTable.hide();
                return;
            }

            var existingReprioTemplate = Handlebars.compile(this.data.reprioritizationTemplate);
            this.data.$reprioritizationTable.html(existingReprioTemplate(this.data.reprioritizationSettings));
            this.data.$reprioritizationTable.show();

            var self = this;

            $('#reprioritization-table button').click(function(){
                self.deleteReprioritizationSetting($(this).data('src-mode'));
            });
        },
        createContactModule: function(){
            var template = Handlebars.compile(this.data.subheaderTemplate),
                settings = this.data.settings;
            this.data.$contact.html(template(settings));
        },
        createPriorityTable: function(){
            var template = Handlebars.compile(this.data.priorityTemplate),
                $priority = this.data.$priority,
                settings = this.data.settings,
                self = this;
            var global_default_values = {};
            var app_default_values = {};
            var app_supported_modes = {};
            settings.per_app_defaults = {};
            window.appData.priorities.forEach(function(priority) {
                global_default_values[priority.name] = priority.default_mode;
            });
            window.appData.applications.forEach(function(app) {
                app_default_values[app.name] = app.default_modes;
                app_supported_modes[app.name] = app.supported_modes;
            });
            settings.per_app_defaults_obj = {};
            for (var app in settings.per_app_modes) {
                settings.per_app_defaults[app] = {priorities: [], supported_modes: app_supported_modes[app]};     // Needed for handlebars
                settings.per_app_defaults_obj[app] = {}; // Needed for JS elsewhere
                window.appData.priorities.forEach(function(p) {
                    var priority = p.name;
                    var asm_obj = app_supported_modes[app];
                    var default_mode = app_default_values[app][priority] ? app_default_values[app][priority] : (
                        global_default_values[priority] ? global_default_values[priority] : ''
                    );
                    // if the default mode is not supported by the app change it to one that is
                    if(Object.values(asm_obj).indexOf(default_mode) < 0){
                      console.log(settings.per_app_modes[app]);
                      default_mode = asm_obj[Object.keys(asm_obj)[0]];
                    }
                    settings.per_app_defaults[app].priorities.push({
                        priority_name: priority,
                        default_mode: default_mode
                    });
                    settings.per_app_defaults_obj[app][priority] = default_mode;
                });
            }
            this.data.$priority.empty();
            this.data.$priority.append(template(settings));
            //load saved user settings to ui, and make changing any of them enable the save button
            $priority.find('select').each(function(){
                var priority = $(this).data('type'),
                    app = $(this).data('app'),
                    setting;
                if (app) {
                    setting = settings.per_app_modes[app][priority] ? settings.per_app_modes[app][priority] : 'default';
                } else {
                    setting = settings.modes[priority] ? settings.modes[priority] : 'default';
                }
                $(this).val(setting);
                // On dropdown change state, modify our settings dictionaries so the next table redraw shows
                // the changed settings
                $(this).change(function() {
                    self.data.$saveBtn.prop('disabled', false);
                    var val = $(this).val();
                    if (app) {
                        settings.per_app_modes[app][priority] = val;
                    }
                    else {
                        // Our per-app dropdowns do not have default, so don't bother
                        if (val == 'default') {
                            delete settings.modes[priority];
                        } else {
                            settings.modes[priority] = val;
                        }
                    }
                });
            });
            $priority.find('button.delete-app-button').each(function() {
                $(this).click(function() {
                    self.deletePerApp($(this));
                });
            });
            this.redrawApplicationDropdown();
        },
        createCustomOverrideTable: function(){
            var template = Handlebars.compile(this.data.customOverrideTemplate),
                settings = this.data.settings,
                self = this;
            this.data.$customOverrideTable.empty();
            this.data.$customOverrideTable.append(template(settings));
            this.data.$customOverrideTable.find('button.delete-custom-app-button').each(function() {
                $(this).click(function() {
                    var appToDelete = $(this).data('app');
                    $('#app-overrides-to-delete').text(appToDelete);
                    $('#delete-override').attr('data-app', appToDelete);
                    $('#confirm-delete-override').modal('show');
                });
            });

            this.data.$customOverrideTable.find('button.edit-custom-app-button').each(function() {
                $(this).click(function() {
                    self.editPerCustomApp($(this));
                });
            });

            this.redrawApplicationOverrideDropdown();
        },
        createCustomOverrideModal: function(app){

            var template = Handlebars.compile(this.data.createCustomOverrideModalTemplate),
                self = this,
                settings = this.data.settings,
                customOverrideModal = this.data.$customOverrideModal;

            // call out to the back end to get all categories for this app
            var appOverrideCategories = [];
            $.ajax({
                type: "GET",
                url: '/v0/categories/' + app
            }).done(function (resp) {
                  appOverrideCategories = resp;
                  appOverrideCategories.forEach(function (appSetting, i) {
                    settings.categories.forEach(function (userSetting) {
                      if(appSetting['application'] === userSetting['application'] && appSetting['name'] === userSetting['category']){
                        //apply user overrides
                        appOverrideCategories[i]['mode'] = userSetting['mode'];
                      }
                    });
                  });
                  settings['modalAppName'] = app;
                  settings['appCategoryData'] = appOverrideCategories;
                  settings['supported_modes'] = self.supportedOverrideModes;
                  customOverrideModal.empty();
                  customOverrideModal.append(template(settings));
                  $('#custom-override-modal').modal('show');
            }).fail(function(){
                iris.createAlert('Failed to fetch category data for this app', 'danger');
            });

        },
        createBatchingTable: function(){
            var template = Handlebars.compile(this.data.batchingTemplate),
                settings = this.data.settings;
            this.data.$batching.append(template(settings));
        },
        deletePerApp: function(elem) {
            var self = this,
                app = elem.data('app');
            self.data.$saveBtn.prop('disabled', false);
            delete self.data.settings.per_app_modes[app];
            self.data.appsToDelete[app] = true;
            self.createPriorityTable();
        },
        deletePerCustomApp: function(app) {
            var self = this;

            if(self.data.settings.customApp.has(app)){
                // delete user overrides for application
                $.ajax({
                    url: self.data.url + self.data.user + '/categories/' + app,
                    method: 'DELETE',
                    contentType: 'text'
                }).done(function(){
                    // delete user mode overrides locally
                    self.data.settings.categories = self.data.settings.categories.filter(function( obj ) {
                        return obj.application !== app;
                    });
                    self.data.settings.customApp.delete(app);
                    iris.createAlert('Deleted application succesfully', 'success');
                    self.createCustomOverrideTable();
                }).fail(function(){
                    iris.createAlert('Failed to delete application', 'danger');
                });
            }
        },
        editPerCustomApp: function(elem) {
            var app = elem.data('app');
            this.createCustomOverrideModal(app);
        },
        saveSetting: function(){
            var globalOptions = this.data.postModel,
                self = this;
            self.data.$saveBtn.prop('disabled', true);
            //get form data
            $('#priority-table select.global-priority').each(function(){
                var $this = $(this);
                globalOptions[$this.attr('data-type')] = $this.val();
            });

            globalOptions.per_app_modes = {};

            $('#priority-table tr.app-row').each(function() {
                var app = $(this).data('app'),
                    all_default = true;

                globalOptions.per_app_modes[app] = {};

                $(this).find('select.app-priority').each(function() {
                    var $this = $(this),
                        val = $this.val();
                    globalOptions.per_app_modes[app][$this.attr('data-type')] = val;
                    if (val != 'default') {
                        all_default = false;
                    }
                });

                // If the user chooses 'default' for all values, set each one to the default value for that app or column to avoid the row
                // disappearing on reload (as all 'default' deletes the custom setting)
                if (all_default) {
                    for (var key in globalOptions.per_app_modes[app]) {
                        globalOptions.per_app_modes[app][key] = self.data.settings.per_app_defaults_obj[app][key];
                    }
                }
            });

            for (var app in self.data.appsToDelete) {
                globalOptions.per_app_modes[app] = {};
                self.data.settings.priorities.forEach(function(p) {
                    globalOptions.per_app_modes[app][p.name] = 'default';
                });
            }

            self.data.appsToDelete = {};

            $.ajax({
                url: this.data.postModesUrl + this.data.user,
                data: JSON.stringify(globalOptions),
                method: 'POST',
                contentType: 'application/json'
            }).done(function(){
                iris.createAlert('Settings saved.', 'success');
            }).fail(function(){
                iris.createAlert('Failed to save settings', 'danger');
            });
        },
        saveModal: function(){
            var modalOptions = {},
                self = this,
                settings = this.data.settings,
                app = $('#custom-override-modal-table').data('app');

            $('select.custom-modal-priority').each(function() {
                var $this = $(this),
                    val = $this.val();
                modalOptions[$this.data('category')] = val;
            });

            $.ajax({
                url: this.data.url + this.data.user + '/categories/' + app,
                data: JSON.stringify(modalOptions),
                method: 'POST',
                contentType: 'text'
            }).done(function(){
                iris.createAlert('Modal settings saved succesfully', 'success');
                self.data.settings.customApp.add(app);

                // update user overrides
                settings.categories.forEach(function(cat, i){
                    if(cat['application'] == app){
                        self.data.settings.categories.splice(i,1);
                    }
                });
                Object.keys(modalOptions).forEach(function(key) {
                    self.data.settings.categories.push({ 'application': app, 'category': key, 'mode': modalOptions[key]});
                });

                self.createCustomOverrideTable();

            }).fail(function(){
                iris.createAlert('Failed to save modal settings', 'danger');
            });
        },
        redrawApplicationDropdown: function() {
            var self = this;
            self.data.$addAppSelect.empty();
            self.data.$addAppSelect.append($('<option value="">').text('Add Application'));
            var myApps = Object.keys(self.data.settings.per_app_modes);
            this.data.settings.applications.sort(function(app1, app2) {
                var app1_name = app1.name.toLowerCase(),
                    app2_name = app2.name.toLowerCase();
                if (app1_name > app2_name) {
                    return 1;
                } else if (app2_name > app1_name) {
                    return -1;
                } else {
                    return 0;
                }
            }).forEach(function(app) {
                if (myApps.indexOf(app.name) == -1) {
                    self.data.$addAppSelect.append($('<option>').text(app.name));
                }
            });
        },
        addApplication: function() {
            var app = this.data.$addAppSelect.val();
            this.data.$addAppSelect.val('');
            this.data.$addAppBtn.prop('disabled', true);
            if (app == '') {
                return;
            }
            this.data.$saveBtn.prop('disabled', false);
            this.data.settings.per_app_modes[app] = {};
            if (app in this.data.appsToDelete) {
                delete this.data.appsToDelete[app];
            }
            this.createPriorityTable();
        },
        addApplicationOverride: function() {
            var app = this.data.$addAppOverrideSelect.val();
            this.data.$addAppOverrideSelect.val('');
            this.createCustomOverrideModal(app);

        },
        redrawApplicationOverrideDropdown: function() {
            var self = this,
                customOverrideApps = new Set;

            //fetch apps that have custom categories defined
            $.ajax({
                type: "GET",
                url: '/v0/categories/'
            }).done(function (resp) {
                  self.data.$addAppOverrideSelect.empty();
                  self.data.$addAppOverrideSelect.append($('<option value="">').text('Add Application'));
                  var appOverrideCategories = resp;
                  appOverrideCategories.forEach(function (appSetting, i) {
                    customOverrideApps.add(appSetting['application']);
                  });
                  Array.from(customOverrideApps).sort().forEach(function (app) {
                    self.data.$addAppOverrideSelect.append($('<option>').text(app));
                  });

              }).fail(function () {
                iris.createAlert('Failed to fetch applications for dropdown', 'danger');
              });

        },
        loadSupportedTimezones: function() {
            var self = this;
            return $.getJSON(this.data.timezonesUrl).done(function(response){
                self.data.supportedTimezones = response;
            }).fail(function(response){
                iris.createAlert('Error: Failed to load supported timezones -' + response.text);
            });
        },
        saveTimezone: function() {
            $.ajax({
                url: this.data.settingsUrl + this.data.user,
                data: JSON.stringify({'timezone': this.data.$timezoneSelect.val()}),
                method: 'PUT',
                contentType: 'application/json'
            }).done(function(){
                iris.createAlert('Settings saved.', 'success');
            }).fail(function(){
                iris.createAlert('Failed to save settings', 'danger');
            });
        },
        populateTimezone: function() {
            var self = this,
                configured_timezone = window.appData.user_settings.timezone;
            self.data.$timezoneSelect.empty();
            if (!configured_timezone) {
                self.data.$timezoneSelect.append($('<option value="">').text('Browser Default'));
                self.data.$timezoneSave.prop('disabled', true);
            }
            self.data.supportedTimezones.forEach(function(name) {
                self.data.$timezoneSelect.append($('<option>').text(name));
            });
            if (configured_timezone) {
                self.data.$timezoneSelect.val(configured_timezone);
            }
        },
        saveSmsOverride: function() {
            $.ajax({
                url: this.data.overridesUrl + this.data.user,
                data: JSON.stringify({'template_overrides': {'sms':this.data.$smsOverrideSelect.val()}}),
                method: 'POST',
                contentType: 'application/json'
            }).done(function(){
                iris.createAlert('Settings saved.', 'success');
            }).fail(function(){
                iris.createAlert('Failed to save settings', 'danger');
            });
        },
        populateSmsOverride: function() {
            var self = this;
            var overrideBool = false;

            if ('template_overrides' in self.data.settings){
              overrideBool =  self.data.settings.template_overrides.includes('sms');
            }
            self.data.$smsOverrideSelect.empty();
            if (!overrideBool) {
              self.data.$smsOverrideSelect.append($('<option value="disabled">').text('Disabled (Default)'));
              self.data.$smsOverrideSelect.append($('<option value="enabled">').text('Enabled'));
              self.data.$smsOverrideSave.prop('disabled', true);
            }
            else {
              self.data.$smsOverrideSelect.append($('<option value="enabled">').text('Enabled'));
              self.data.$smsOverrideSelect.append($('<option value="disabled">').text('Disabled (Default)'));
              self.data.$smsOverrideSave.prop('disabled', true);
            }
        }
    }, //end iris.user
    applications: {
        data: {
            $page: $('.main'),
            $table: $('#applications-table'),
            $pageHeader: $('.main h3'),
            $createAppModal: $('#create-app-modal'),
            showAppModalButton: '#show-app-modal-button',
            applicationSubmitButton: '#create-app-submit',
            tableRows: 'tbody tr',
            tableTemplate: $('#applications-table-template').html(),
            headerTemplate: $('#applications-header-template').html(),
            DataTable: null,
            dataTableOpts: {
                orderClasses: false,
                order: [[0, 'asc']]
            }
        },
        init: function() {
            iris.changeTitle('Applications');

            this.data.$pageHeader.html(Handlebars.compile(this.data.headerTemplate)({
                showCreateButton: window.appData.user_admin
            }));

            iris.tables.createTable.call(this, window.appData.applications);
            this.events();
        },
        events: function() {
            this.data.$page.on('click', this.data.tableRows, function() {
                window.location = '/applications/' + $(this).data('application');
            });
            this.data.$page.on('click', this.data.showAppModalButton, this.showCreateAppModal.bind(this));
            this.data.$page.on('click', this.data.applicationSubmitButton, this.createApplication.bind(this));
        },
        showCreateAppModal: function() {
            this.data.$createAppModal.modal();
        },
        createApplication: function() {
            var self = this,
                $appNameBox = $('#create-app-name'),
                appName = $.trim($appNameBox.val()),
                appNameLower = appName.toLowerCase();

            if (appName == '') {
                $appNameBox.val('');
                $appNameBox.focus();
                return;
            }

            // If this application already exists, just go to its page
            for (var i = 0; i < window.appData.applications.length; i++) {
                if (window.appData.applications[i].name.toLowerCase() == appNameLower) {
                    window.location = '/applications/' + window.appData.applications[i].name;
                    return;
                }
            }

            $.ajax({
                url: '/v0/applications/',
                data: JSON.stringify({
                    name: appName
                }),
                method: 'POST',
                contentType: 'application/json'
            }).done(function() {
                window.location = '/applications/' + appName
            }).fail(function(r) {
                iris.createAlert('Failed creating application: ' + r.responseJSON['title'])
            }).always(function() {
                self.data.$createAppModal.modal('hide');
            });
        }
    }, // End iris.applications
    application: {
        data: {
            url: '/v0/applications/',
            apiBase: '/v0/',
            $page: $('.main'),
            $editButton: $('#application-edit-button'),
            applicationTemplate: $('#application-template').html(),
            loaderTemplate: $('#loader-template').html(),
            applicationEditbutton: '#application-edit-button',
            applicationSavebutton: '#application-save-button',
            applicationRenameButton: '#application-rename-button',
            applicationDeleteButton: '#application-delete-button',
            applicationRekeyButton: '#application-rekey-button',
            applicationSecondaryKeyButton: '#application-secondary-key-button',
            showRenameModalButton: '#show-rename-modal-button',
            showDeleteModalButton: '#show-delete-modal-button',
            showRekeyModalButton: '#show-rekey-modal-button',
            showSecondaryKeyModalButton: '#show-secondary-key-modal-button',
            removeVariableButton: '.remove-variable',
            removeOwnerButton: '.remove-owner',
            removeAddressButton: '.remove-address',
            showApiKeyButton: '#show-api-key-button',
            showSecondaryKeyButton: '#show-secondary-key-button',
            addVariableForm: '#add-variable-form',
            addOwnerForm: '#add-owner-form',
            addAddressForm: '#add-address-form',
            addEmailIncidentForm: '#add-email-incident-form',
            addCustomCategoryForm: '#add-custom-category-form',
            addDefaultModeForm: '#add-default-mode-form',
            removeEmailIncidentButton: '.delete-email-incident-button',
            removeCustomCategoryButton: '.delete-custom-category-button',
            removeDefaultModeButton: '.delete-default-mode-button',
            dangerousActionsToggle: '.application-dangerous-actions h4',
            application: null,
            model: {}
        },
        init: function() {
            var location = window.location.pathname.split('/'),
                application = decodeURIComponent(location[location.length - 1]);

            // Register this only for this page because it isn't used anywhere else. Only admins and users
            // can delete owners, but users who are not admins cannot delete themselves.
            Handlebars.registerHelper('ifCanShowDeleteUserButton', function(username, opts) {
                return window.appData.user_admin || username != window.appData.user ? opts.fn(this) : opts.inverse(this);
            });

            this.data.application = application;
            this.getApplication(application);
        },
        events: function() {
            var self = this,
                data = this.data;
            data.$page.on('click', data.applicationEditbutton, this.editApplication.bind(this));
            data.$page.on('click', data.applicationSavebutton, this.saveApplication.bind(this));
            data.$page.on('click', data.showRenameModalButton, this.showRenameModal.bind(this));
            data.$page.on('click', data.showDeleteModalButton, this.showDeleteModal.bind(this));
            data.$page.on('click', data.showRekeyModalButton, this.showRekeyModal.bind(this));
            data.$page.on('click', data.showSecondaryKeyModalButton, this.showSecondaryKeyModal.bind(this));
            data.$page.on('click', data.applicationRenameButton, this.renameApplication.bind(this));
            data.$page.on('click', data.applicationDeleteButton, this.deleteApplication.bind(this));
            data.$page.on('click', data.applicationRekeyButton, this.rekeyApplication.bind(this));
            data.$page.on('click', data.applicationSecondaryKeyButton, this.genSecondaryKey.bind(this));
            data.$page.on('click', data.showApiKeyButton, this.showApiKey.bind(this));
            data.$page.on('click', data.showSecondaryKeyButton, this.showSecondaryKey.bind(this));
            data.$page.on('click', data.dangerousActionsToggle, this.toggleDangerousActions.bind(this));
            data.$page.on('click', data.removeVariableButton, function() {
                var variable = $(this).data('variable');
                var pos = self.data.model.variables.indexOf(variable);
                if (pos !== -1) {
                    self.data.model.variables.splice(pos, 1)
                }
                $(this).parent().remove();
            });
            data.$page.on('click', data.removeOwnerButton, function() {
                var owner = $(this).data('owner');
                var pos = self.data.model.owners.indexOf(owner);
                if (pos !== -1) {
                    self.data.model.owners.splice(pos, 1)
                }
                $(this).parent().remove();
            });
            data.$page.on('click', data.removeAddressButton, function(e) {
                e.preventDefault();
                self.data.model.custom_sender_addresses.email = null;
                $("#custom-sender-email-value").text('None (using default sender address)');
            });
            data.$page.on('submit', data.addVariableForm, function(e) {
                e.preventDefault();
                var variable = $('#add-variable-box').val();
                if (variable == '') {
                    iris.createAlert('Cannot add empty variable');
                    return;
                }
                if (self.data.model.variables.indexOf(variable) !== -1) {
                    iris.createAlert('That variable "' + variable + '" already exists');
                    return;
                }
                $('#add-variable-box').val('');
                self.data.model.variables.push(variable);
                self.modelPersist();
                self.render();
            });
            data.$page.on('submit', data.addOwnerForm, function(e) {
                e.preventDefault();
                var owner = $('#add-owner-box').val();
                if (owner == '') {
                    iris.createAlert('Cannot add empty owner');
                    return;
                }
                if (self.data.model.owners.indexOf(owner) !== -1) {
                    iris.createAlert('That owner "' + owner + '" already exists');
                    return;
                }
                $('#add-owner-box').val('');
                self.data.model.owners.push(owner);
                self.modelPersist();
                self.render();
            });
            data.$page.on('submit', data.addAddressForm, function(e) {
                e.preventDefault();
                var address = $('#add-address-box').val();
                if (address === '') {
                    iris.createAlert('Cannot add empty custom address');
                    return;
                }
                if(self.validateEmail(address) === false){
                    iris.createAlert('Email address is invalid. Please make sure it is formatted correctly.');
                    return;
                }

                $('#add-address-box').val('');
                self.data.model.custom_sender_addresses.email = address;
                self.modelPersist();
                self.render();
            });
            data.$page.on('submit', data.addEmailIncidentForm, function(e) {
                e.preventDefault();
                var $email = $('#add-email-incident-email');
                var $plan = $('#add-email-incident-plan');
                self.data.model.emailIncidents[$email.val()] = $plan.val();
                $email.val('');
                $plan.val('');
                self.modelPersist();
                self.render();
            });
            data.$page.on('click', data.removeEmailIncidentButton, function() {
                delete self.data.model.emailIncidents[$(this).data('email')];
                self.modelPersist();
                self.render();
            });
            data.$page.on('submit', data.addCustomCategoryForm, function(e) {
                e.preventDefault();
                var $category = $('#add-custom-category');
                var $description = $('#add-custom-category-description');
                var $mode = $('#category-default-mode');
                self.data.model.customCategories.forEach(function(element, i){
                    // if it already exists, replace with new values
                    if (element["name"] == $category.val()){
                        self.data.model.customCategories.splice(i, 1);
                    }
                });
                self.data.model.customCategories.push({"name":$category.val(), "description": $description.val(), "mode": $mode.val()});
                $category.val('');
                $description.val('');
                self.modelPersist();
                self.render();
            });
            data.$page.on('click', data.removeCustomCategoryButton, function() {
                var nameToDelete = $(this).data('category');
                self.data.model.customCategories.forEach(function(element, i){
                    if(element["name"] == nameToDelete){
                        self.data.model.customCategories.splice(i, 1);
                    }
                });
                self.modelPersist();
                self.render();
            });
            data.$page.on('submit', data.addDefaultModeForm, function(e) {
                e.preventDefault();
                var $priority = $('#default-mode-form-priority'),
                    priority_val = $priority.val();
                var $mode = $('#default-mode-form-mode'),
                    mode_val = $mode.val();
                if (priority_val == '') {
                    $priority.addClass('invalid-input');
                    return;
                } else {
                    $priority.removeClass('invalid-input');
                }
                if (mode_val == '') {
                    $mode.addClass('invalid-input');
                    return;
                } else {
                    $mode.removeClass('invalid-input');
                }
                self.data.model.default_modes[priority_val] = mode_val;
                $priority.val('');
                $mode.val('');
                self.modelPersist();
                self.render();
            });
            data.$page.on('click', data.removeDefaultModeButton, function() {
                delete self.data.model.default_modes[$(this).data('priority')];
                self.modelPersist();
                self.render();
            });
            window.onbeforeunload = iris.unloadDialog.bind(this);
        },
        validateEmail: function(email) {
            var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
            return re.test(String(email).toLowerCase());
        },
        modelPersist: function() {
            var self = this;
            self.data.$page.find('textarea').each(function() {
                var key = $(this).data('field');
                if (key && key in self.data.model) {
                    self.data.model[key] = $(this).val();
                }
            });
        },
        getApplication: function(application) {
            var app = null,
                self = this,
                applicationLower = application.toLowerCase();
            for (var i = 0, item; i < window.appData.applications.length; i++) {
                item = window.appData.applications[i];
                if (item.name.toLowerCase() == applicationLower) {
                    app = item;
                    break;
                }
            }
            if (!app) {
                iris.createAlert('Application not found');
                return;
            }
            iris.changeTitle('Application ' + app.name);

            self.showLoader();

            $.when($.get(self.data.url + application + '/quota'),
                $.get(self.data.url + application + '/incident_emails'),
                $.get(self.data.apiBase + 'categories/' + self.data.application)
            ).always(function(quota, incident_emails, customCategories) {
                app.quota = Object.keys(quota[0]).length ? quota[0] : null;
                app.emailIncidents = incident_emails[0];

                app.customCategories = [];
                customCategories[0].forEach(function(element){
                    app.customCategories.push({'name':element['name'], 'description':element['description'], 'mode':element['mode']})
                });

                // For convenience in setting the below boolean flags, localize
                // whether the user is an owner of this app or is an admin here.
                var isOwner = app.owners.indexOf(window.appData.user) !== -1;
                var isAdmin = window.appData.user_admin;

                // Bunch of boolean flags passed to the template which control what is
                // seen and how the UI behaves. The entire template is scattered with if
                // blocks based on these variables.
                app.isInViewMode = true;
                app.isEditable = isAdmin || isOwner;
                app.allowViewingKey = isAdmin || isOwner;
                app.allowEditingEmailIncidents = isAdmin || isOwner;
                app.allowEditingCustomCategories = isAdmin || isOwner;
                app.showEditOwners = isAdmin || isOwner;
                app.allowDangerousActions = isAdmin || isOwner;
                app.allowEditingSupportedModes = isAdmin;
                app.allowEditingTitle = isAdmin || isOwner;

                // This gets turned to true when the edit button is clicked if the user
                // is an admin
                app.showEditQuotas = false;

                app.apiKey = false;
                app.priorities = window.appData.priorities.map(function(priority) {
                    return priority.name;
                });
                app.modes = window.appData.modes;
                self.data.model = app;
                self.render();
                self.events();
            }).fail(function() {
                iris.createAlert('Failed loading app settings');
            });
        },
        editApplication: function() {
            this.data.model.isInViewMode = false;
            this.data.model.showEditQuotas = window.appData.user_admin;
            this.render();
        },
        showApiKey: function() {
            var self = this;
            $(self.data.showApiKeyButton).prop('disabled', true);
            $.get('/v0/applications/' + self.data.application + '/key').done(function(result) {
                self.data.model.apiKey = result.key;
                self.modelPersist();
                self.render();
            }).fail(function(result) {
                iris.createAlert('Unable to get key: ' + result.responseJSON.title);
            });
        },
        showSecondaryKey: function() {
            var self = this;
            $(self.data.showSecondaryKeyButton).prop('disabled', true);
            $.get('/v0/applications/' + self.data.application + '/secondary').done(function(result) {
                self.data.model.secondaryKey = result.key;
                if (result.key === null) {
                    self.data.model.secondaryKey = 'No secondary key exists'
                }
                self.modelPersist();
                self.render();
            }).fail(function(result) {
                iris.createAlert('Unable to get key: ' + result.responseJSON.title);
            });
        },
        saveApplication: function() {
            var self = this,
                failedCheck = false;
            $('.application-settings').find('textarea').each(function(k, elem) {
                var $elem = $(elem),
                    field = $elem.data('field'),
                    val = $elem.val();
                self.data.model[field] = val;
                $elem.removeClass('invalid-input');
                if (field == 'context_template' || field == 'summary_template' || field == 'mobile_template') {
                    try {
                        Handlebars.compile(val)({})
                    } catch (e) {
                        iris.createAlert('Invalid HandleBars syntax for ' + field + '.\n' + e.message);
                        $elem.addClass('invalid-input');
                        failedCheck = true;
                    }
                } else if (field == 'sample_context' && val != '') {
                    try {
                        JSON.parse(val);
                    } catch (e) {
                        iris.createAlert('Invalid JSON syntax for sample context.\n' + e.message);
                        $elem.addClass('invalid-input');
                        failedCheck = true;
                    }
                }
            });
            checked = $("input[name=supported_modes]:checked").length;
            if(!checked) {
              iris.createAlert('Application must have at least one supported mode');
              failedCheck = true;
            }
            if (failedCheck) {
                return;
            }
            if (self.data.model.sample_context == '') {
                self.data.model.sample_context = '{}';
            }
            var ajaxCalls = [];
            if (self.data.model.showEditQuotas) {
                var quotaSettings = {},
                    deleteQuota = true;
                $('.application-quota').find('input').each(function(k, elem) {
                    var $elem = $(elem),
                        field = $elem.attr('name'),
                        val = $elem.val();
                    if (field && val != '' && val != '0') {
                        if (field != 'target_name' && field != 'plan_name') {
                            val = parseInt(val);
                        }
                        quotaSettings[field] = val;
                        deleteQuota = false;
                    }
                });
                if (deleteQuota) {
                    self.data.model.quota = null;
                    ajaxCalls.push($.ajax({
                        url: self.data.url + self.data.application + '/quota',
                        method: 'DELETE',
                        data: '{}',
                        contentType: 'application/json'
                    }));
                } else {
                    self.data.model.quota = quotaSettings;
                    quotaSettings.wait_time = 3600;
                    // Abstract away the fact that quota durations are really seconds underneath
                    quotaSettings.hard_quota_duration *= 60;
                    quotaSettings.soft_quota_duration *= 60;
                    ajaxCalls.push($.ajax({
                        url: self.data.url + self.data.application + '/quota',
                        data: JSON.stringify(quotaSettings),
                        method: 'POST',
                        contentType: 'application/json'
                    }));
                }
            }
            if (self.data.model.allowEditingEmailIncidents) {
                ajaxCalls.push($.ajax({
                    url: self.data.url + self.data.application + '/incident_emails',
                    data: JSON.stringify(self.data.model.emailIncidents),
                    method: 'PUT',
                    contentType: 'application/json'
                }));
            }
            if (self.data.model.allowEditingCustomCategories) {
                ajaxCalls.push($.ajax({
                    url: self.data.apiBase + 'categories/' + self.data.application,
                    data: JSON.stringify(self.data.model.customCategories),
                    method: 'POST',
                    contentType: 'application/json'
                }));
            }
            if (self.data.model.allowEditingSupportedModes) {
                self.data.model.supported_modes = [];
                $('input[name=supported_modes]').each(function(k, elem) {
                    var $elem = $(elem);
                    if ($elem.prop('checked')) {
                        self.data.model.supported_modes.push($elem.val());
                    }
                });
                // Remove any default modes using modes we no longer support
                for (var priority in self.data.model.default_modes) {
                    if (self.data.model.supported_modes.indexOf(self.data.model.default_modes[priority]) == -1) {
                        delete self.data.model.default_modes[priority];
                    }
                }
            }

            if (self.data.model.allowEditingTitle){
                self.data.model.title_variable = $('#title-variable-select').val();
                if(self.data.model.title_variable == 'null'){
                    self.data.model.title_variable = null;
                }
            }

            ajaxCalls.push($.ajax({
                url: self.data.url + self.data.application,
                data: JSON.stringify(self.data.model),
                method: 'PUT',
                contentType: 'application/json'
            }));
            $.when.apply(undefined, ajaxCalls).done(function(){
                self.data.model.isInViewMode = true;
                self.data.model.showEditQuotas = false;
                self.data.model.apiKey = false;
                self.render();
                iris.createAlert('Settings saved', 'success');
            }).fail(function(data) {
                iris.createAlert('Settings failed to save: ' + data.responseJSON.title);
            });
        },
        toggleDangerousActions: function() {
            $('.application-dangerous-actions').toggleClass('active')
        },
        showRenameModal: function() {
            $('#app-new-name-box').val(this.data.application);
            $('#rename-app-modal').modal();
        },
        showDeleteModal: function() {
            $('#delete-app-modal').modal();
        },
        showRekeyModal: function() {
            $('#rekey-app-modal').modal();
        },
        showSecondaryKeyModal: function() {
            $('#secondary-key-app-modal').modal();
        },
        renameApplication: function() {
            var $nameBox = $('#app-new-name-box'),
                newName = $.trim($nameBox.val());
            if (newName == '' || newName == this.data.application) {
                $nameBox.focus();
                return;
            }
            var $renameBtn = $(this.data.applicationRenameButton);
            $renameBtn.prop('disabled', true);
            $.ajax({
                url: '/v0/applications/' + this.data.application + '/rename',
                data: JSON.stringify({
                    new_name: newName
                }),
                method: 'PUT',
                contentType: 'application/json'
            }).done(function() {
                window.onbeforeunload = null;
                window.location = '/applications/' + newName;
            }).fail(function(r) {
                iris.createAlert('Failed renaming application: ' + r.responseJSON['title']);
                $('body').scrollTop(0);
                $renameBtn.prop('disabled', false);
            }).always(function() {
                $('#rename-app-modal').modal('hide');
            });
        },
        deleteApplication: function() {
            var $deleteBtn = $(this.data.applicationDeleteButton);
            $deleteBtn.prop('disabled', true);
            $.ajax({
                url: '/v0/applications/' + this.data.application,
                method: 'DELETE',
                contentType: 'application/json'
            }).done(function() {
                window.onbeforeunload = null;
                window.location = '/applications/';
            }).fail(function(r) {
                iris.createAlert('Failed deleting application: ' + r.responseJSON['title']);
                $('body').scrollTop(0);
                $deleteBtn.prop('disabled', false);
            }).always(function() {
                $('#delete-app-modal').modal('hide');
            });
        },
        rekeyApplication: function() {
            var $rekeyBtn = $(this.data.applicationRekeyButton);
            $rekeyBtn.prop('disabled', true);
            $.ajax({
                url: '/v0/applications/' + this.data.application + '/rekey',
                method: 'POST',
                contentType: 'application/json'
            }).done(function() {
                iris.createAlert('Successfully rekey\'d application', 'success')
            }).fail(function(r) {
                iris.createAlert('Failed re-keying application: ' + r.responseJSON['title'])
            }).always(function() {
                $('#rekey-app-modal').modal('hide');
                $('body').scrollTop(0);
                $rekeyBtn.prop('disabled', false);
            });
        },
        genSecondaryKey: function() {
            var $keyBtn = $(this.data.applicationRekeyButton);
            $keyBtn.prop('disabled', true);
            $.ajax({
                url: '/v0/applications/' + this.data.application + '/secondary',
                method: 'POST',
                contentType: 'application/json'
            }).done(function() {
                iris.createAlert('Successfully generated secondary key', 'success')
            }).fail(function(r) {
                iris.createAlert('Failed to generate secondary key: ' + r.responseJSON['title'])
            }).always(function() {
                $('#secondary-key-app-modal').modal('hide');
                $('body').scrollTop(0);
                $keyBtn.prop('disabled', false);
            });
        },
        showLoader: function() {
            var template = Handlebars.compile(this.data.loaderTemplate);
            this.data.$page.html(template(this.data.model));
        },
        render: function() {
            var template = Handlebars.compile(this.data.applicationTemplate);
            this.data.$page.html(template(this.data.model));
            if (this.data.model.showEditQuotas || this.data.model.showEditOwners) {
                iris.typeahead.init('');
            } else {
                iris.typeahead.destroy();
            }
        }
    }, // End iris.application
    stats: {
        data: {
            url: '/v0/stats',
            $page: $('.stats'),
            $table: $('#stats-table'),
            tableTemplate: $('#stats-table-template').html(),
            DataTable: null,
            dataTableOpts: {
                orderClasses: false,
                order: [[0, 'asc']],
                columns: [
                    null,
                    null
                ]
            }
        },
        init: function() {
            iris.changeTitle('Stats');
            iris.tables.filterTable.call(this);
        },
        events: function() {},
        getData: function(params) {
            return $.getJSON(this.data.url, params).fail(function(){
                iris.createAlert('No stats found');
            });
        }
    }, // End iris.stats
    stat: {
        data: {
            url: '/v0/applications/',
            $page: $('.stats'),
            $table: $('#stats-table'),
            application: null,
            tableTemplate: $('#stats-table-template').html(),
            DataTable: null,
            dataTableOpts: {
                orderClasses: false,
                order: [[0, 'asc']],
                columns: [
                    null,
                    null
                ]
            }
        },
        init: function() {
            var location = window.location.pathname.split('/'),
                application = decodeURIComponent(location[location.length - 1]);

            iris.changeTitle('App Stats');
            $('#stats-header').text("App Stats: " + application);
            this.data.application = application;
            iris.tables.filterTable.call(this);
        },
        events: function() {},
        getData: function(params) {
            apiUrl = this.data.url + this.data.application + "/stats";
            return $.getJSON(apiUrl).fail(function(){
                iris.createAlert('No stats found');
            });
        }
    }, // End iris.appStats
    unsubscribe: {
        data: {
          url: '/v0/applications/',
          $page: $('.unsub'),
          application: null,
          user: null,
          unsubscribeButton: $('#unsub-button'),
          unsubTemplate: $('#unsub-template').html(),
        },
        init: function() {
          var location = window.location.pathname.split('/'),
              application = decodeURIComponent(location[location.length - 1])
              self = this;

          iris.changeTitle('Unsubscribe');
          this.events()
          var template = Handlebars.compile(this.data.unsubTemplate);
          $.when($.get(self.data.url + application), $.get('/v0/users/' + window.appData.user)).then(function(app, user){
            app = app[0];
            self.data.application = app;
            self.data.user = user[0];
            self.data.$page.html(template({'name': app['name'], 'owners': app['owners'], 'eligible': app.supported_modes.includes('drop')}));
          }).fail(function(){
            iris.createAlert('Failed to fetch application data');
          })
        },
        events: function() {
          this.data.$page.on('click', this.data.unsubscribeButton, this.unsubscribe.bind(this));
        },
        unsubscribe: function() {
          var self = this,
              modes = self.data.user['modes']
          // Construct new user modes
          modes['per_app_modes'] = self.data.user['per_app_modes']
          modes['per_app_modes'][self.data.application['name']] = {};
          window.appData.priorities.forEach(function(priority){
            modes['per_app_modes'][self.data.application['name']][priority['name']] = 'drop';
          })
          self.data.unsubscribeButton.prop('disabled', true);
          $.ajax({
              url: '/v0/users/modes/' + window.appData.user,
              data: JSON.stringify(modes),
              method: 'POST',
              contentType: 'application/json'
          }).done(function(r) {
            iris.createAlert('Successfully unsubscribed from all messages sent by this app!', 'success')
          }).fail(function(r) {
            iris.createAlert('Failed to unsubscribe: ' + r.responseJSON['title'])
          }).always(function(){
            self.data.unsubscribeButton.prop('disabled', false);
          })
        }
      },
      // Needed for single stats routing
      singlestats: {},
      singlestat: {
        data: {
          url: '/v0/singlestats/',
          $page: $('.stats'),
          $table: $('#stats-table'),
          tableTemplate: $('#stats-table-template').html(),
          DataTable: null,
          statName: null,
          dataTableOpts: {
            orderClasses: false,
            order: [[0, 'asc']],
            columns: [
              null,
              null
            ]
          }
        },
        init: function() {
          var location = window.location.pathname.split('/'),
              statName = decodeURIComponent(location[location.length - 1]);
          this.data.statName = statName;

          $('#stats-header').text("Stats: " + statName);
          iris.tables.filterTable.call(this);
        },
        events: function() {},
        getData: function(params) {
          dataUrl = this.data.url + this.data.statName;
          return $.getJSON(dataUrl).fail(function(){
            iris.createAlert('No stats found');
          });
        }
      }, // End iris.hpistats
      tables: {
        filterTable: function(e){
          if (e) {
            // form submitted - serialize and shove into qs
            var $form = $(e.target);
            var $btn;
            $btn = $form.find('button[type="submit"]');
            $btn.prop('disabled', true);
            e.preventDefault();

            var url = window.location.protocol + "//" + window.location.host + window.location.pathname + '?';
            var qs_form = $form.serializeArray();
            for (var i = 0; i < qs_form.length; i++) {
              var k = qs_form[i].name;
              var v = qs_form[i].value;
              if (v) {
                url += k.slice(7) +'=' + encodeURI(v) + '&';
              }
            }
            url = url.slice(0, -1);
            history.pushState(url, null, url);
          }
          var self = this;

          if (window.location.search) {
            var qs = {};
            var qs_parts = decodeURI(window.location.search.substr(1)).split('&');
            for (i = qs_parts.length - 1; i >= 0; i--) {
              var qs_pair = qs_parts[i].split('=');
              qs[qs_pair[0]] = qs_pair[1];
            }
          } else {
            qs = {
              'active': 'active',
              'target': appData.user,
              // Default incident start time to now() - 1 day
              'incidentStart': moment(Date.now() - 86400000).format('MM/DD/YYYY h:mm A')
            };
          }
          var params = {};

          // add loading icon
          this.data.$table.html('<tr><td><i class="loader"></i></td></tr>');
          $('#filter-form input, #filter-form select').each(function(){

            var $this = $(this);
            var name = $this.attr('name');
            if (typeof(name) != 'undefined') {
              var qs_name = name.split('-')[1];
              var qs_value = qs[qs_name];
            }

            switch (name) {
              case 'filter-active':
                var v = $this.prop('id').slice(7);
                if ((v == qs_value) || (v=='all' && qs_value === undefined)) {
                  $this.prop('checked', true);
                } else {
                  $this.prop('checked', false);
                }
                break;
              case 'filter-incidentStart':
              case 'filter-start':
              case 'filter-end':
                $this.val(qs_value);
                if ($this.val().length) {
                  params[$this.attr('data-param')] = moment(qs_value).unix();
                }
                break;
              case 'filter-target':
                $this.typeahead('val', qs_value);
                params[$this.attr('data-param')] = qs_value;
                break;
              default:
                $this.val(qs_value);
                params[$this.attr('data-param')] = qs_value;
                break;
            }

          });

          switch (qs['active']) {
            case 'active':
              params['active'] = 1;
              break;
            case 'inactive':
              params['active'] = 0;
              break;
          }

          self.getData(params).done(function(data){
            if (self.data.DataTable) {
              self.data.DataTable.destroy();
            }
            self.data.$table.empty();
            iris.tables.createTable.call(self, data);
          }).always(function(){
            if ($btn && $btn.prop('disabled')) { $btn.prop('disabled', false) }
          });
        },
        createTable: function(data){
          var template = Handlebars.compile(this.data.tableTemplate),
              options = this.data.dataTableOpts || { orderClasses: false };
          if (data.length == iris.data.tableEntryLimit){
            data.limit = iris.data.tableEntryLimit;
          }
          this.data.$table.html(template(data));
          this.data.DataTable = this.data.$table.DataTable(options);
          iris.tables.bindArrowKeys(this.data.DataTable);
          iris.tables.bindUrlPagination(this.data.DataTable);
        },
        bindArrowKeys: function(dataTable) {
          $(document).keydown(function(e) {

            if (document.activeElement && (document.activeElement.nodeName === 'INPUT'
                                          || document.activeElement.nodeName === 'TEXTAREA')) {
              return;
            }

            if (e.keyCode == 37) {
              dataTable.page('previous').draw(false);
            } else if (e.keyCode == 39) {
              dataTable.page('next').draw(false);
            }
          });
        },
        bindUrlPagination: function(dataTable) {
          // If we currently have a page number in URL, use it
          var parts = window.location.hash.split('-');
          if (parts.length == 2 && (parts[1] - 1) < dataTable.page.info().pages) {
              dataTable.page(parts[1] - 1).draw(false);
          }

          // Store page number in URL for future refreshes / back buttons
          dataTable.on('page.dt', function() {
            var current_page = dataTable.page.info().page + 1;
            window.history.replaceState(undefined, undefined, '#page-' + current_page)
          });
        }
      },
      createAlert: function(alertText, type, $el, action, fixed){
        //params:
        //-- alertText: content of alert *REQUIRED* --string--
        //-- type: type of alert (coincides to color) --string--
          //---- 'danger' - red *default*
          //---- 'warning' - yellow
          //---- 'info' - blue
          //---- 'success' - green
        //-- $el: DOM element which the alert will be added to. Defaults to body --jQuery element--
        //-- action: jQuery action on the $el.
          //---- 'prepend' - inserts alert as first element inside $el *default*
          //---- 'append' - inserts alert at the end of the $el
          //---- 'before' - inserts alert as a sibling node before $el
          //---- 'after' - inserts alert as a sibling node after $el
        //-- fixed: alternate alert which is absolutely positioned at top center of the screen
        var alert;
        type = type || 'danger';
        $el = $el || $('.main');
        action = action || 'prepend';
        $('#iris-alert').remove();
        alert = '<div id="iris-alert" class="alert alert-'+ type +' alert-dismissible ' + fixed + '" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button><span class="alert-content"></span></div>';
        $el[action](alert);
        $('#iris-alert .alert-content').html(alertText);
      }, //end createAlert
      dismissAlerts: function() {
        $('#iris-alert').remove();
      },
      typeahead: {
        data: {
          targetUrl: '/v0/targets/',
          planUrl: '/v0/plans?name__startswith=%QUERY&active=1',
          field: 'input.typeahead'
        },
        init: function(urlType){
          var $field = $(this.data.field),
              self = this;
          $field.typeahead('destroy').each(function(){
            var $this = $(this),
                type = $this.data('targettype') || (urlType != undefined ? urlType : $this.parents('.plan-notification').find('select[data-type="role"] option:selected').attr('data-url-type')), //get url type from sibling "Role" option
                itemType = $this.data('typeaheadtype'),
                results = new Bloodhound({
                  datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
                  queryTokenizer: Bloodhound.tokenizers.whitespace,
                  remote: {
                    url: itemType == 'plan' ? self.data.planUrl : self.data.targetUrl + type + '?startswith=%QUERY&active=1',
                    wildcard: '%QUERY',
                    transform: function(response) {
                      if (itemType == 'plan') {
                        var names = [];
                        response.forEach(function(plan) {
                          names.push(plan.name);
                        });
                        return names;
                      } else {
                        return response;
                      }
                    }
                  }
                });
            $this.typeahead(null, {
              hint: true,
              async: true,
              highlight: true,
              source: results
            }).on('typeahead:select', function(){
              $(this).attr('value', $(this).val());
            });
          });
        },
        destroy: function() {
          $(this.data.field).typeahead('destroy');
        }
      },
      unloadDialog: function(){
        if ( this.data.$page.find('input, textarea').not('[data-default]').not('[disabled]').not('.unload-exclude').filter(function () { return !!this.value }).length > 0 ) {
          return 'You have unsaved changes';
        }
      },
      versionTour: {
        type: 'templates',
        init: function(type){
          var self = this;
          this.type = type;
          if (!localStorage.getItem('tourCompleted')) {
            hopscotch.startTour(self);
          }
          $('.main').on('click', '.start-tour', function(){
            hopscotch.startTour(self);
          });
        },
        id: "version-tour",
        steps: [
          {
            title: "Changes to versioning",
            content: "Plans or templates with the same name are now combined into one view. You can click this dropdown to select older versions of this plan or template.",
            target: ".version-select",
            placement: "bottom",
            xOffset: -90,
            arrowOffset: 'center'
          },
          {
            title: "Activate old plans or templates",
            content: "If a plan or template is deactivated, you can click here to set it as active. This will deactivate all other plans or templates with this name.",
            target: ".badge",
            placement: "bottom"
          },
          {
            title: "Related plans",
            content: "You can click here to see all plans that are currently using this template. It may be a good idea to review the related plans before activating an older version of a template (it's possible someone else's plan is using your template, and you don't want to ruin their plan!)",
            target: ".view-related",
            placement: "bottom"
          },
          {
            title: "Learn about versioning",
            content: "Click this button to restart this tutorial if you need a reminder.",
            target: ".start-tour",
            placement: "bottom",
            xOffset: -20
          }
        ],
        onEnd: function(){
          iris.versionTour.saveState();
        },
        onClose: function(){
          iris.versionTour.saveState();
        },
        saveState: function(){
          localStorage.setItem('tourCompleted', true);
        },
        showPrevButton: true
      },
      registerHandlebarHelpers: function(){
        // Register handlebars helpers
        Handlebars.registerHelper('ifNot', function(val, opts){
          return val ? opts.inverse(this) : opts.fn(this);
        });
        Handlebars.registerHelper('ifIn', function(needle, haystack, opts){
          if (Array.isArray(haystack)) {
            return haystack.indexOf(needle) != -1 ? opts.fn(this) : opts.inverse(this);
          } else {
            return needle in haystack;
          }
        });
        Handlebars.registerHelper('hasKeys', function(val, opts){
          return Object.keys(val).length ? opts.fn(this) : opts.inverse(this);
        });
        Handlebars.registerHelper('isSelected', function(val, check){
          return val === check ? 'selected': '';
        });
        Handlebars.registerHelper('isActive', function(yes){
          return yes ? 'active': '';
        });
        Handlebars.registerHelper('ifExists', function(val, opts){
          return typeof(val) !== 'undefined' ? opts.fn(this) : opts.inverse(this);
        });
        Handlebars.registerHelper('isEqual', function(val1, val2, opts){
          return val1 === val2 ? opts.fn(this) : opts.inverse(this);
        });
        Handlebars.registerHelper('isUser', function(val1, opts){
          return val1 === window.appData.user ? opts.fn(this) : opts.inverse(this);
        });
        Handlebars.registerHelper('divide', function(val1, val2){
          return val1 / val2;
        });
        Handlebars.registerHelper('add', function(val1, val2){
          return val1 + val2;
        });
        Handlebars.registerHelper('markdown', function(val){
          return new Handlebars.SafeString(marked(val, {sanitize: true}));
        })
        Handlebars.registerHelper('prettyPrint', function(val){
          return typeof(val) === 'string' ? val : "{...}";
        });
        Handlebars.registerHelper('eachInSet', function(context, options) {
            // handle sets and return them in sorted order
            var ret = "";
            values = Array.from(context).sort();
            values.forEach(function(val) {
                ret = ret + options.fn({value:val});
            });
            return ret;
        });
        Handlebars.registerHelper('secondsToMinutes', function(val){
          if (val) {
            return parseFloat((val / 60).toFixed(2));
          } else {
            return 0;
          }
        });
        Handlebars.registerHelper('convertToLocal', function(time){
          if (time) {
            if (window.appData.user_settings.timezone) {
              return moment.unix(time).tz(window.appData.user_settings.timezone).toString();
            } else {
              return moment.unix(time).local().toString();
            }
          }
        });
        Handlebars.registerHelper('breakLines', function(text, type){
          if (text) {
            text = Handlebars.Utils.escapeExpression(text);
            if (type && type === 'input') {
              text = text.replace(/(\r\n|\n|\r)/gm, '&#13;&#10;');
            } else {
              text = text.replace(/(\r\n|\n|\r)/gm, '<br />');
            }

            return new Handlebars.SafeString(text);
          } else {
            return '';
          }
        });
        Handlebars.registerHelper('getValForKey', function (obj, val1, val2, breakLines) {
          //gets nested value from an object with a dynamic key.
          //obj = object to get val from
          //val1 = key to get val from
          //val2 = key within val1 to get the value from
          //breakLines = replaces \n with <br /> tag for view mode of templates
          var text = obj[val1][val2];
          if (breakLines === true) {
            text = Handlebars.helpers.breakLines(text);
          } else {
            text = Handlebars.Utils.escapeExpression(text);
            text = text.replace(/(\r\n|\n|\r)/gm, '&#13;&#10;');
          }
          return new Handlebars.SafeString(text);
        });
        Handlebars.registerHelper('trim', function(string, start, end) {
          if (string) {
            return string.slice(start, end);
          } else {
            return string
          }
        });
        Handlebars.registerHelper('unixToDate', function(val){
          if (val) {
            var a = new Date(val * 1000);
            var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
            var year = a.getFullYear();
            var month = months[a.getMonth()];
            var date = a.getDate();
            var hour = a.getHours();
            if(hour < 10){hour = '0' + hour}
            var min = a.getMinutes();
            if(min < 10){min = '0' + min}
            var sec = a.getSeconds();
            if(sec < 10){sec = '0' + sec}
            var time = date + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec ;
            return time;
          } else {
            return "N/A";
          }
        });
        Handlebars.registerHelper('range', function(n, block) {
          var accum = '';
          for (var i = 0; i < n; i++) {
            accum += block.fn(i);
          }
          return accum;
        });
        Handlebars.registerHelper('regex', function (val, regex) {
          return (new RegExp(regex).test(val))
        })
      } //end registerHandlebarHelpers
    }; //end iris

    iris.init();
