(function (context, $) {

  "use strict";

  context.JK = context.JK || {};
  context.JK.VoiceChatHelper = function (app) {
    var logger = context.JK.logger;
    var ALERT_NAMES = context.JK.ALERT_NAMES;
    var ASSIGNMENT = context.JK.ASSIGNMENT;
    var VOICE_CHAT = context.JK.VOICE_CHAT;
    var MAX_TRACKS = context.JK.MAX_TRACKS;
    var MAX_OUTPUTS = context.JK.MAX_OUTPUTS;
    var gearUtils = context.JK.GearUtils;

    var $parent                   = null;

    var $reuseAudioInputRadio = null;
    var $useChatInputRadio = null;
    var $chatInputs = null;
    var $templateChatInput = null;
    var $selectedChatInput = null;// should only be used if isChatEnabled = true
    var $voiceChatVuLeft = null;
    var $voiceChatVuRight = null;
    var $voiceChatFader = null;

    var saveImmediate = null; // if true, then every action by the user results in a save to the backend immediately, false means you have to call trySave to persist
    var uniqueCallbackName = null;
    // needed because iCheck fires iChecked event even when you programmatically change it, unlike when using .val(x)
    var ignoreICheckEvent = false;
    var lastSavedTime = new Date();
    var vuOptions = null;
    var faderHeight = null;
    var startingState = null;
    var resettedOnInvalidDevice = false; // this is set to true when we clear chat, and set to false when the user interacts in anyway

    function defaultReuse() {
      suppressChange(function(){
        $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked');
        $useChatInputRadio.removeAttr('checked');
      })
    }

    function isChatEnabled() {
      return $useChatInputRadio.is(':checked');
    }

    function onInvalidAudioDevice(e, data) {
      logger.debug("voice_chat_helper: onInvalidAudioDevice")

      if(resettedOnInvalidDevice) {
        // we've already tried to clear the audio device, and the user hasn't interacted, but still we are getting this event
        // we can't keep taking action, so stop
        logger.error("voice_chat_helper: onInvalidAudioDevice: ignoring event because we have already tried to handle it");
        return;
      }
      resettedOnInvalidDevice = true;

      $selectedChatInput = null;
      // you can't do this in the event callback; it hangs the app indefinitely, and somehow 'sticks' the mic input into bad state until reboot
      setTimeout(function() {
        context.jamClient.FTUEClearChatInput();
        context.jamClient.TrackSetChatEnable(true);
        var result = context.jamClient.TrackSaveAssignments();

        if(!result || result.length == 0) {
          context.JK.Banner.showAlert('It appears the selected chat input is not functioning.  Please try another chat input.');
        }
        else {
          context.JK.alertSupportedNeeded("Unable to unwind invalid chat input selection.")
        }
      }, 1);
    }

    function beforeShow() {
      userInteracted();
      renderNoVolume();
      context.JK.onBackendEvent(ALERT_NAMES.AUDIO_DEVICE_NOT_PRESENT, 'voice_chat_helper', onInvalidAudioDevice);
      registerVuCallbacks();
    }

    function beforeHide() {
      context.JK.offBackendEvent(ALERT_NAMES.AUDIO_DEVICE_NOT_PRESENT, 'voice_chat_helper',  onInvalidAudioDevice);
      jamClient.FTUERegisterVUCallbacks('', '', '');
    }

    function userInteracted() {
      resettedOnInvalidDevice = false;
    }

    function reset(forceDisabledChat) {

      $selectedChatInput = null;

      if(!forceDisabledChat && context.jamClient.TrackGetChatEnable()) {
        enableChat(false);
      }
      else  {
        disableChat(false);
      }

      $chatInputs.empty();

      var chatInputs = gearUtils.getChatInputs();

      context._.each(chatInputs, function(chatInput) {
        if(chatInput.assignment > 0) {
          return;
        }
        var chatChannelName = chatInput.name;
        var chatChannelId = chatInput.id;
        var isCurrentlyChat = chatInput.assignment == ASSIGNMENT.CHAT;
        var $chat = $(context._.template($templateChatInput.html(), {id: chatChannelId, name: chatChannelName}, { variable: 'data' }));
        var $chatInput = $chat.find('input');
        if(isCurrentlyChat) {
          $selectedChatInput = $chatInput;
          $selectedChatInput.attr('checked', 'checked');
        }
        //$chat.hide(); // we'll show it once it's styled with iCheck
        $chatInputs.append($chat);
      });

      var $radioButtons = $chatInputs.find('input[name="chat-device"]');
      context.JK.checkbox($radioButtons).on('ifChecked', function(e) {
        userInteracted();
        var $input = $(e.currentTarget);
        $selectedChatInput = $input; // for use in handleNext
        if(saveImmediate) {
          var channelId = $input.attr('data-channel-id');
          lastSavedTime = new Date();
          context.jamClient.TrackSetChatInput(channelId);
          lastSavedTime = new Date();
          //context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.CHAT);
          var result = context.jamClient.TrackSaveAssignments();

          if(!result || result.length == 0) {
            // success
          }
          else {
            context.jamClient.FTUEClearChatInput();
            context.jamClient.TrackSetChatEnable(true);
            var result = context.jamClient.TrackSaveAssignments();
            if(!result || result.length == 0) {
              context.JK.Banner.showAlert('Unable to save chat selection. ' + result);
            }
            else {
              context.JK.alertSupportedNeeded("Unable to unwind invalid chat selection.")
            }
            return false;
          }
        }
      });

      if(!isChatEnabled()) {
        disableChatButtonsUI();
      }

      renderVolumes();

      startingState = getCurrentState();
    }

    function disableChatButtonsUI() {
      var $radioButtons = $chatInputs.find('input[name="chat-device"]');
      $radioButtons.iCheck('disable')
      $chatInputs.addClass('disabled');
      $radioButtons.iCheck('uncheck');
      $selectedChatInput = null;
    }

    function enableChatButtonsUI() {
      var $radioButtons = $chatInputs.find('input[name="chat-device"]');
      $radioButtons.iCheck('enable')
      $chatInputs.removeClass('disabled');

    }
    function suppressChange(proc) {

      ignoreICheckEvent = true;

      try {
        proc();
      }
      finally {
        ignoreICheckEvent = false;
      }
    }

    function disableChat(applyToBackend) {
      if(saveImmediate && applyToBackend) {
        logger.debug("voiceChatHelper: disabling chat to backend");
        var state = getCurrentState();
        //context.jamClient.TrackSetChatEnable(false);
        //if(state.chat_channel) {
        //  context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.UNASSIGNED);
        //}
        context.jamClient.FTUEClearChatInput();
        var result = context.jamClient.TrackSaveAssignments();

        if(!result || result.length == 0) {
          renderNoVolume();
          // success
          suppressChange(function() {
            $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked');
            $useChatInputRadio.removeAttr('checked');
          })
        }
        else {
          context.JK.Banner.showAlert('Unable to disable chat. ' + result);
          return false;
        }
      }
      else {
        logger.debug("voiceChatHelper: disabling chat UI only");
        suppressChange(function() {
          $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked');
          $useChatInputRadio.removeAttr('checked');
        })
      }
      disableChatButtonsUI();
    }

    function enableChat(applyToBackend) {
      if(saveImmediate && applyToBackend) {
        logger.debug("voiceChatHelper: enabling chat to backend");
        context.jamClient.TrackSetChatEnable(true);
        var result = context.jamClient.TrackSaveAssignments();

        if(!result || result.length == 0) {
          // success
          suppressChange(function() {
            $useChatInputRadio.iCheck('check').attr('checked', 'checked');
            $reuseAudioInputRadio.removeAttr('checked');
          })
        }
        else {
          context.JK.Banner.showAlert('Unable to enable chat. ' + result);
          return false;
        }
      }
      else {
        logger.debug("voiceChatHelper: enabling chat UI only");
        suppressChange(function() {
          $useChatInputRadio.iCheck('check').attr('checked', 'checked');
          $reuseAudioInputRadio.removeAttr('checked');
        })
      }

      enableChatButtonsUI();
      renderVolumes();
    }

    function handleChatEnabledToggle() {
      context.JK.checkbox($reuseAudioInputRadio);
      context.JK.checkbox($useChatInputRadio);

      // plugin sets to relative on the element; have to do this as an override
      $reuseAudioInputRadio.closest('.iradio_minimal').css('position', 'absolute');
      $useChatInputRadio.closest('.iradio_minimal').css('position', 'absolute');

      $reuseAudioInputRadio.on('ifChecked', function() {
        if(!ignoreICheckEvent) {
          userInteracted();
          disableChat(true);
        }
      });
      $useChatInputRadio.on('ifChecked', function() {
        if(!ignoreICheckEvent) {
          userInteracted();
          enableChat(true)
        }
      });
    }

    // gets the state of the UI
    function getCurrentState() {
      var state = {
        enabled:null,
        chat_channel:null
      };

      state.enabled = $useChatInputRadio.is(':checked');
      state.chat_channel = $selectedChatInput && $selectedChatInput.attr('data-channel-id');
      logger.debug("desired chat state: enabled=" + state.enabled + ", chat_channel=" + state.chat_channel)
      return state;
    }

    function cancel() {
      logger.debug("canceling voice chat state");
      return trySave(startingState);
    }


    function trySave(state) {

      if(!state) {
        state = getCurrentState();
      }

      if(state.enabled && state.chat_channel) {
        logger.debug("enabling chat.  chat_channel=" + state.chat_channel);
        context.jamClient.TrackSetChatEnable(true);
        context.jamClient.FTUESetChatInput(state.chat_channel);
        //context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.CHAT);
      }
      else {
        logger.debug("disabling chat.");
        context.jamClient.FTUEClearChatInput();
        //context.jamClient.TrackSetChatEnable(false);
        //if(state.chat_channel) {
          //context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.UNASSIGNED);
        //}
      }

      var result = context.jamClient.TrackSaveAssignments();

      if(!result || result.length == 0) {
        // success
        if(!state.enabled) {
          renderNoVolume();
        }
        return true;
      }
      else {
        context.JK.Banner.showAlert('Unable to save chat assignments. ' + result);
        return false;
      }
    }

    function initializeVUMeters() {
      context.JK.VuHelpers.renderVU($voiceChatVuLeft, vuOptions);
      context.JK.VuHelpers.renderVU($voiceChatVuRight, vuOptions);

      context.JK.FaderHelpers.renderFader($voiceChatFader, {faderId: '', faderType: "vertical", height: faderHeight});
      $voiceChatFader.on('fader_change', faderChange);
    }

    // renders volumes based on what the backend says
    function renderVolumes() {
      var $fader = $voiceChatFader.find('[data-control="fader"]');
      var db = context.jamClient.FTUEGetChatInputVolume();
      var faderPct = db + 80;
      context.JK.FaderHelpers.setHandlePosition($fader, faderPct);
    }

    function renderNoVolume() {
      var $fader = $voiceChatFader.find('[data-control="fader"]');
      context.JK.FaderHelpers.setHandlePosition($fader, 50);
      context.JK.VuHelpers.updateVU($voiceChatVuLeft, 0);
      context.JK.VuHelpers.updateVU($voiceChatVuRight, 0);

    }

    function faderChange(e, data) {
      // TODO - using hardcoded range of -80 to 20 for output levels.
      var mixerLevel = data.percentage - 80; // Convert our [0-100] to [-80 - +20] range
      context.jamClient.FTUESetChatInputVolume(mixerLevel);
    }

    function registerVuCallbacks() {
      logger.debug("voice-chat-helper: registering vu callbacks");
      jamClient.FTUERegisterVUCallbacks(
        "JK.voiceChatHelperAudioOutputVUCallback",
        "JK.voiceChatHelperAudioInputVUCallback",
        "JK." + uniqueCallbackName
      );
      jamClient.SetVURefreshRate(200);
    }

    function initialize(_$step, caller, _saveImmediate, _vuOptions, _faderHeight) {
      $parent = _$step;
      saveImmediate = _saveImmediate;
      vuOptions = _vuOptions;
      faderHeight = _faderHeight;
      $reuseAudioInputRadio = $parent.find('.reuse-audio-input input');
      $useChatInputRadio = $parent.find('.use-chat-input input');
      $chatInputs = $parent.find('.chat-inputs');
      $templateChatInput = $('#template-chat-input');
      $voiceChatVuLeft = $parent.find('.voice-chat-vu-left');
      $voiceChatVuRight = $parent.find('.voice-chat-vu-right');
      $voiceChatFader = $parent.find('.chat-fader')

      handleChatEnabledToggle();
      initializeVUMeters();
      renderVolumes();

      uniqueCallbackName = 'voiceChatHelperChatInputVUCallback' + caller;
      context.JK[uniqueCallbackName] = function(dbValue, leftClip, rightClip) {
        context.JK.ftueVUCallback(dbValue, $voiceChatVuLeft);
        context.JK.ftueVUCallback(dbValue, $voiceChatVuRight);
      }
    }

    context.JK.voiceChatHelperAudioInputVUCallback = function (dbValue) {};
    context.JK.voiceChatHelperAudioOutputVUCallback = function (dbValue) {};


    this.reset = reset;
    this.trySave = trySave;
    this.cancel = cancel;
    this.initialize = initialize;
    this.beforeShow = beforeShow;
    this.beforeHide = beforeHide;

    return this;
  };

})(window, jQuery);