(function (context, $) {

  "use strict";

  context.JK = context.JK || {};
  context.JK.StepLoopbackTest = function (app, dialog) {

    var ASSIGNMENT = context.JK.ASSIGNMENT;
    var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR;
    var gearUtils = context.JK.GearUtils;

    var logger = context.JK.logger;
    var $step = null;
    var frameBuffers = new context.JK.FrameBuffers(app);
    var gearTest = new context.JK.GearTest(app);
    var deviceInformation = null;
    var operatingSystem = null;
    var selectedDeviceInfo = null;
    var musicPorts = null;

    var $asioInputControlBtn = null;
    var $asioOutputControlBtn = null;
    var $resyncBtn = null;
    var $runTestBtn = null;
    var $audioInputDevice = null;
    var $audioOutputDevice = null;
    var $inputChannels = null;
    var $outputChannels = null;
    var $templateAudioPort = null;
    var $scoreReport = null;
    var $audioInputVuLeft = null;
    var $audioInputVuRight = null;
    var $audioInputFader = null;
    var $audioOutputVuLeft = null;
    var $audioOutputVuRight = null;
    var $audioOutputFader = null;

    var faderMap = {
      'loopback-audio-input-fader': jamClient.FTUESetInputVolume,
      'loopback-audio-output-fader': jamClient.FTUESetOutputVolume
    };

    var faderReadMap = {
      'loopback-audio-input-fader': jamClient.FTUEGetInputVolume,
      'loopback-audio-output-fader': jamClient.FTUEGetOutputVolume
    };



    function attemptScore() {
      gearTest.attemptScore(selectedDeviceInfo);
    }


    function invalidateScore() {
      gearTest.invalidateScore();
      initializeNextButtonState();
    }

    function updateDefaultBuffers() {
      gearUtils.updateDefaultBuffers(selectedDeviceInfo, frameBuffers)
    }

    function onFramesizeChanged() {
      context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
      updateDefaultBuffers();
      jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize());
      invalidateScore();
    }

    function onBufferInChanged() {
      context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
      jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn());
      invalidateScore();
    }

    function onBufferOutChanged() {
      context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
      jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
      invalidateScore();
    }

    function freezeAudioInteraction() {
      logger.debug("loopback: freezing audio interaction");
      frameBuffers.disable();
      $asioInputControlBtn.on("click", false);
      $asioOutputControlBtn.on("click", false);
      $resyncBtn.on('click', false);
      $runTestBtn.on('click', false);
    }

    function unfreezeAudioInteraction() {
      logger.debug("unfreezing audio interaction");
      frameBuffers.enable();
      $asioInputControlBtn.off("click", false);
      $asioOutputControlBtn.off("click", false);
      $resyncBtn.off('click', false);
      $runTestBtn.off('click', false);
    }

    function getGearTest() {
      return gearTest;
    }

    function handleNext() {
      return true;
    }

    function handleBack() {
      return true;
    }


    function render(){

      if(selectedDeviceInfo) {
        var inputBehavior = selectedDeviceInfo.input.behavior;
        var outputBehavior = selectedDeviceInfo.output.behavior;
      }
      else {
        var inputBehavior = null;
        var outputBehavior = null;

      }

      // handle ASIO visibility
      if (inputBehavior) {
        if (inputBehavior.showASIO) {
          $asioInputControlBtn.show();
        }
        else {
          $asioInputControlBtn.hide();
        }
        if(outputBehavior.showASIO && (selectedDeviceInfo.input.id != selectedDeviceInfo.output.id)) {
          $asioOutputControlBtn.show();
        }
        else {
          $asioOutputControlBtn.hide();
        }
      }
      else {
        // show no ASIO buttons
        $asioInputControlBtn.hide();
        $asioOutputControlBtn.hide();
      }

      if(selectedDeviceInfo) {
        $audioInputDevice.text(selectedDeviceInfo.input.info.displayName);
        $audioOutputDevice.text(selectedDeviceInfo.output.info.displayName);
      }
      else {
        $audioInputDevice.text('Unassigned');
        $audioOutputDevice.text('Unassigned');
      }

      $inputChannels.empty();
      context._.each(musicPorts.inputs, function (inputChannel) {
        var $inputChannel = $(context._.template($templateAudioPort.html(), inputChannel, { variable: 'data' }));
        var $checkbox = $inputChannel.find('input');
        if (gearUtils.isChannelAssigned(inputChannel)) {
          $checkbox.attr('checked', 'checked');
        }
        context.JK.checkbox($checkbox).iCheck('disable');
        $inputChannels.append($inputChannel);
      });

      $outputChannels.empty();
      context._.each(musicPorts.outputs, function (outputChannel) {
        var $outputPort = $(context._.template($templateAudioPort.html(), outputChannel, { variable: 'data' }));
        var $checkbox = $outputPort.find('input');
        if (gearUtils.isChannelAssigned(outputChannel)) {
          $checkbox.attr('checked', 'checked');
        }
        context.JK.checkbox($checkbox).iCheck('disable');
        $outputChannels.append($outputPort);
      });

      initializeVUMeters();
      renderVolumes();
      registerVuCallbacks();
    }

    function initializeASIOButtons() {
      $asioInputControlBtn.unbind('click').click(function () {
        context.jamClient.FTUEOpenControlPanel(selectedDeviceInfo.input.id);
      });
      $asioOutputControlBtn.unbind('click').click(function () {
        context.jamClient.FTUEOpenControlPanel(selectedDeviceInfo.output.id);
      });
    }


    function initializeResync() {
      $resyncBtn.unbind('click').click(function () {
        attemptScore();
        return false;
      })
    }

    function initializeVUMeters() {
      var vuOptions = {vuType: "horizontal", lightCount: 12, lightWidth: 15, lightHeight: 3};

      context.JK.VuHelpers.renderVU($audioInputVuLeft, vuOptions);
      context.JK.VuHelpers.renderVU($audioInputVuRight, vuOptions);

      context.JK.VuHelpers.renderVU($audioOutputVuLeft, vuOptions);
      context.JK.VuHelpers.renderVU($audioOutputVuRight, vuOptions);

      var faderOptions = {faderId: '', faderType: "horizontal", width: 163};
      context.JK.FaderHelpers.renderFader($audioInputFader, faderOptions);
      context.JK.FaderHelpers.renderFader($audioOutputFader, faderOptions);
      $audioInputFader.on('fader_change', inputFaderChange);
      $audioOutputFader.on('fader_change', outputFaderChange);
    }

    // renders volumes based on what the backend says
    function renderVolumes() {

      // input
      var $inputFader = $audioInputFader.find('[data-control="fader"]');
      var db = context.jamClient.FTUEGetInputVolume();
      var faderPct = db + 80;
      context.JK.FaderHelpers.setHandlePosition($inputFader, faderPct);

      // output
      var $outputFader = $audioOutputFader.find('[data-control="fader"]');
      var db = context.jamClient.FTUEGetOutputVolume();
      var faderPct = db + 80;
      context.JK.FaderHelpers.setHandlePosition($outputFader, faderPct);
    }

    function inputFaderChange(e, data) {
      var mixerLevel = data.percentage - 80; // Convert our [0-100] to [-80 - +20] range
      context.jamClient.FTUESetInputVolume(mixerLevel);
    }

    function outputFaderChange(e, data) {
      var mixerLevel = data.percentage - 80;
      context.jamClient.FTUESetOutputVolume(mixerLevel);
    }

    function registerVuCallbacks() {
      logger.debug("loopback-wizard: registering vu callbacks");
      jamClient.FTUERegisterVUCallbacks(
        "JK.loopbackAudioOutputVUCallback",
        "JK.loopbackAudioInputVUCallback",
        "JK.loopbackChatInputVUCallback"
      );
      jamClient.SetVURefreshRate(200);
    }

    function initializeNextButtonState() {
      dialog.setNextState(gearTest.isGoodFtue());
    }

    function initializeBackButtonState() {
      dialog.setBackState(!gearTest.isScoring());
    }

    function renderScoringStarted() {
      initializeBackButtonState();
      initializeNextButtonState();
      freezeAudioInteraction();
    }

    function renderScoringStopped() {
      initializeNextButtonState();
      initializeBackButtonState();
      unfreezeAudioInteraction();
    }

    function onGearTestStarted(e, data) {
      renderScoringStarted();
    }

    function onGearTestDone(e, data) {
      renderScoringStopped();
      gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
    }

    function onGearTestFail(e, data) {
      renderScoringStopped();
      gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
    }

    function beforeShow() {
      selectedDeviceInfo = gearUtils.selectedDeviceInfo(context.jamClient.FTUEGetInputMusicDevice(), context.jamClient.FTUEGetOutputMusicDevice());
      deviceInformation = gearUtils.loadDeviceInfo();
      musicPorts = jamClient.FTUEGetChannels();

      render();
    }

    function beforeHide() {
      logger.debug("loopback-wizard: unregistering vu callbacks");
      jamClient.FTUERegisterVUCallbacks('', '', '');
    }

    function initialize(_$step) {
      $step = _$step;

      $asioInputControlBtn = $step.find('.asio-settings-input-btn');
      $asioOutputControlBtn = $step.find('.asio-settings-output-btn');
      $resyncBtn = $step.find('.resync-btn');
      $runTestBtn = $step.find('.run-test-btn');
      $audioInputDevice = $step.find('.audio-device.input')
      $audioOutputDevice = $step.find('.audio-device.output')
      $inputChannels = $step.find('.input-ports')
      $outputChannels = $step.find('.output-ports')
      $templateAudioPort = $('#template-audio-port');
      $scoreReport = $step.find('.results');
      $audioInputVuLeft = $step.find('.audio-input-vu-left');
      $audioInputVuRight = $step.find('.audio-input-vu-right');
      $audioInputFader= $step.find('.audio-input-fader');
      $audioOutputVuLeft = $step.find('.audio-output-vu-left');
      $audioOutputVuRight = $step.find('.audio-output-vu-right');
      $audioOutputFader= $step.find('.audio-output-fader');

      operatingSystem = context.JK.GetOSAsString();

      frameBuffers.initialize($step.find('.frame-and-buffers'));
      $(frameBuffers)
        .on(frameBuffers.FRAMESIZE_CHANGED, onFramesizeChanged)
        .on(frameBuffers.BUFFER_IN_CHANGED, onBufferInChanged)
        .on(frameBuffers.BUFFER_OUT_CHANGED, onBufferOutChanged)


      gearTest.initialize($scoreReport, false)
      $(gearTest)
        .on(gearTest.GEAR_TEST_START, onGearTestStarted)
        .on(gearTest.GEAR_TEST_DONE, onGearTestDone)
        .on(gearTest.GEAR_TEST_FAIL, onGearTestFail)

      $runTestBtn.click(attemptScore);

      initializeASIOButtons();
      initializeResync();


      context.JK.loopbackAudioInputVUCallback = function (dbValue) {
        context.JK.ftueVUCallback(dbValue, $audioInputVuLeft);
        context.JK.ftueVUCallback(dbValue, $audioInputVuRight);
      };
      context.JK.loopbackAudioOutputVUCallback = function (dbValue) {
        context.JK.ftueVUCallback(dbValue, $audioOutputVuLeft);
        context.JK.ftueVUCallback(dbValue, $audioOutputVuRight);
      };
      context.JK.loopbackChatInputVUCallback = function (dbValue) {
      };
    }

    this.getGearTest  = getGearTest;
    this.handleNext   = handleNext;
    this.handleBack   = handleBack;
    this.beforeShow   = beforeShow;
    this.beforeHide   = beforeHide;
    this.initialize   = initialize;

    return this;
  }
})(window, jQuery);