(function (context, $) {

  "use strict";

  context.JK = context.JK || {};
  context.JK.NetworkTest = function (app) {

    var NETWORK_TEST_TYPES = {
      RestPhase: 0,
      PktTest100NormalLatency: 1,
      PktTest200MediumLatency: 2,
      PktTest400LowLatency: 3,
      PktTestRateSweep: 4,
      RcvOnly: 5
    }
    var STARTING_NUM_CLIENTS_AUDIO = 4;
    var STARTING_NUM_CLIENTS_VIDEO = 2;
    var AUDIO_PAYLOAD_SIZE = gon.global.ftue_network_test_packet_size;
    var VIDEO_PAYLOAD_SIZE = gon.global.ftue_network_test_packet_size_video;
    var MINIMUM_ACCEPTABLE_SESSION_SIZE = 2;
    var RETRY_THRESHOLD = 2;

    var gearUtils = context.JK.GearUtils;
    var rest = context.JK.Rest();
    var logger = context.JK.logger;
    var $step = null;
    var TEST_SUCCESS_CALLBACK = 'JK.HandleNetworkTestSuccess';
    var TEST_TIMEOUT_CALLBACK = 'JK.HandleNetworkTestTimeout';

    var $startNetworkTestBtn = null;
    var $foreverNetworkTestBtn = null;
    var $testResults = null;
    var $testScoreAudio = null;
    var $testScoreVideo= null;
    var $testText = null;
    var $inProgressText = null;
    var $audioResultText = null;
    var $videoResultText = null;
    var testedSuccessfully = false;
    var $scoringBar = null;
    var $goodMarker = null;
    var $goodLine = null;
    var $currentScore = null;
    var $scoredClientsAudio = null;
    var $scoredClientsVideo = null;
    var $subscore = null;
    var $watchVideo = null;
    var $container = null;
    var backendGuardTimeout = null;
    var primeGuardTimeout = null;
    var primeDeferred = null;

    var serverClientId = '';
    var audioScoring = false;
    var videoScoring = false;
    var numClientToTestAudio = STARTING_NUM_CLIENTS_AUDIO;
    var numClientToTestVideo = STARTING_NUM_CLIENTS_VIDEO;
    var testSummary = {audioAttempts: [], videoAttempts: [], final: null}
    var $self = $(this);
    var scoringZoneWidth = 100;//px
    var inGearWizard = false;
    var operatingSystem = null;
    var PRIME_PUMP_TIME = 1;

    var forever = false;

    // these try to make it such that we only pass a NetworkTest Pass/Failed one time in a new user flow
    var trackedPass = false;
    var lastNetworkFailure = null;
    var bandwidthSamples = [];

    var NETWORK_TEST_START  = 'network_test.start';
    var NETWORK_TEST_DONE   = 'network_test.done';
    var NETWORK_TEST_FAIL   = 'network_test.fail';
    var NETWORK_TEST_CANCEL = 'network_test.cancel';

    function createSuccessCallbackName(priming) {
      if (priming) {
        if (inGearWizard) {
          return TEST_SUCCESS_CALLBACK + 'ForPumpPrimingGW';
        }
        else {
          return TEST_SUCCESS_CALLBACK + 'ForPumpPrimingDialog';
        }
      }
      else {
        if (inGearWizard) {
          return TEST_SUCCESS_CALLBACK + 'ForGearWizard';
        }
        else {
          return TEST_SUCCESS_CALLBACK + 'ForDialog';
        }
      }
    }

    function createTimeoutCallbackName(priming) {
      if (priming) {
        if (inGearWizard) {
          return TEST_TIMEOUT_CALLBACK + 'ForPumpPrimingGW';
        }
        else {
          return TEST_TIMEOUT_CALLBACK + 'ForPumpPrimingDialog';
        }
      }
      else {
        if (inGearWizard) {
          return TEST_TIMEOUT_CALLBACK + 'ForGearWizard';
        }
        else {
          return TEST_TIMEOUT_CALLBACK + 'ForDialog';
        }
      }
    }

    // this averages bandwidthSamples; this method is meant just for GA data
    function avgBandwidth(num_others) {
      if (bandwidthSamples.length == 0) {
        return 0;
      }
      else {
        var total = 0;
        context._.each(bandwidthSamples, function (sample) {
          total += (sample * num_others * 400 * (100 + 28)); // sample is a percentage of 400.  So sample * 400 gives us how many packets/sec.   100 is payload; 28 is UDP+ETHERNET overhead, to give us bandwidth
        })
        return total / bandwidthSamples.length;
      }
    }

    function reset() {
      trackedPass = false;
      lastNetworkFailure = null;
      resetTestState();
    }

    function resetTestState() {
      serverClientId = '';
      audioScoring = false;
      videoScoring = false;
      numClientToTestAudio = STARTING_NUM_CLIENTS_AUDIO;
      numClientToTestVideo = STARTING_NUM_CLIENTS_VIDEO;
      testSummary = {audioAttempts: [], videoAttempts:[]};
      configureStartButton();
      $scoredClientsAudio.empty();
      $testResults.removeClass('good acceptable bad testing');
      $testScoreAudio.removeClass('good acceptable bad testing');
      $testScoreVideo.removeClass('good acceptable bad testing');
      $scoredClientsAudio.text('-')
      $scoredClientsVideo.text('-')
      $inProgressText.empty();
      $audioResultText.empty();
      $audioResultText.hide();
      $videoResultText.empty();
      $videoResultText.hide();
      $subscore.empty();
      $currentScore.width(0);
      bandwidthSamples = [];

    }

    function renderStartTestAudio() {
      configureStartButton();
      $testResults.addClass('testing');
      $testScoreAudio.addClass('testing');
      $goodLine.css('left', (gon.ftue_packet_rate_treshold * 100) + '%');
      $goodMarker.css('left', (gon.ftue_packet_rate_treshold * 100) + '%');
    }

    function renderStartTestVideo() {
      configureStartButton();
      $testResults.addClass('testing');
      $testScoreVideo.addClass('testing');
      $goodLine.css('left', (gon.ftue_packet_rate_treshold * 100) + '%');
      $goodMarker.css('left', (gon.ftue_packet_rate_treshold * 100) + '%');
    }

    function renderStopTestAudio(score, text) {
      if(!score || score.length == 0) {
        $scoredClientsAudio.html('0');

        $inProgressText.html('The audio test did not pass. Video is not tested in this case.<br><br>Please click <a rel="external" href="https://jamkazam.desk.com/customer/portal/articles/1716139-what-to-do-if-you-cannot-pass-the-network-test">HERE</a> for help information.');
      }
      else {
        $scoredClientsAudio.html(score);
      }

      if(text && text.length > 0) {
        $audioResultText.text(text);
      }
      $testResults.removeClass('testing');
      $testScoreAudio.removeClass('testing');

    }

    function renderStopTestVideo(score, text) {
      logger.debug("renderStopTestVideo", score, text)

      // don't show the audio result text until the test is over (it looks confusing otherwise).
      if($audioResultText.text() && $audioResultText.text().length > 0) {
        $audioResultText.show();
      }

      $inProgressText.text('Your router and Internet service will support:')

      if(!score || score.length == 0) {
        $scoredClientsVideo.html('-');
      }
      else {

        if(score < 2) {
          $scoredClientsVideo.html('0')
          $videoResultText.html('No other players when in a video + audio session.').show();
          $testScoreVideo.addClass('acceptable');
        }
        else {
          $scoredClientsVideo.html(score);

          var summary = "Video + audio sessions with up to " + score + " players";

          if (text && text.length > 0) {
            // presence of text means there was an error on the last test pass.
            summary += '.  Note that there was an error when testing for ' + (score + 1) + 'players.  Support code=' + text
          }

          $videoResultText.html(summary).show();
        }
      }

      $testResults.removeClass('testing');
      $testScoreVideo.removeClass('testing');
    }

    function postDiagnostic() {
      rest.createDiagnostic({
        type: 'NETWORK_TEST_RESULT',
        data: {client_type: context.JK.clientType(), client_id: context.JK.JamServer.clientID, summary: testSummary}
      });
    }

    function appendContextualStatement() {
      if (inGearWizard) {
        return " You can skip this step for now."
      }
      else {
        return '';
      }
    }

    function getLastNetworkFailure() {
      return lastNetworkFailure;
    }

    function haltScoring() {
      context.jamClient.SetLatencyTestBlocked(true)
      rest.updateNetworkTesting({client_id: app.clientId, is_network_testing: true})
        .fail(function(jqXHR) {

          if(jqXHR.status == 404) {
            // assume connection is missing
            app.notifyAlert("Not Connected", "You must be connected to the server to run the network test.")
          }
          else {
            app.notifyServerError(jqXHR, "Unable to tell server that we are beginning the network test")
          }
        })
    }

    function resumeScoring() {
      context.jamClient.SetLatencyTestBlocked(false)
      rest.updateNetworkTesting({client_id: app.clientId, is_network_testing: false})
        .fail(function(jqXHR) {
          if(jqXHR.status == 404) {
            // assume connection is missing
            // do nothing in this case
          }
          else {
            app.notifyServerError(jqXHR, "Unable to tell server that we are ending the network test")
          }
        })
    }

    function storeLastNetworkFailure(reason, data) {
      if (!trackedPass) {
        lastNetworkFailure = {reason: reason, data: data};
      }
    }

    function testFinishedAudio() {
      var attempt = getCurrentAttemptAudio();

      if (!testSummary.final) {
        testSummary.final = {reason: attempt.reason};
      }

      var reason = testSummary.final.reason;
      var success = false;

      if (reason == "success") {
        renderStopTestAudio(attempt.num_clients, "Audio-only sessions with up to " + attempt.num_clients + " players")
        testedSuccessfully = true;
        if (!testSummary.final.num_clients) {
          testSummary.final.num_clients = attempt.num_clients;
        }

        // context.jamClient.GetNetworkTestScore() == 0 is a rough approximation if the user has passed the FTUE before
        if (inGearWizard || context.jamClient.GetNetworkTestScore() == 0) {
          trackedPass = true;
          lastNetworkFailure = null;
          context.JK.GA.trackNetworkTest(context.JK.detectOS(), testSummary.final.num_clients);
        }

        context.jamClient.SetNetworkTestScore(attempt.num_clients);
        if (testSummary.final.num_clients == 2) {
          $testScoreAudio.addClass('acceptable');
        }
        else {
          $testScoreAudio.addClass('good');
        }
        success = true;
      }
      else if (reason == "minimum_client_threshold") {
        context.jamClient.SetNetworkTestScore(0);
        renderStopTestAudio('', "We're sorry, but your router and Internet service will not effectively support JamKazam sessions. Please click the HELP button for more information.")
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.bandwidth, avgBandwidth(attempt.num_clients - 1));
      }
      else if (reason == "unreachable" || reason == "no-transmit") {
        context.jamClient.SetNetworkTestScore(0);
        // https://jamkazam.atlassian.net/browse/VRFS-2323
        renderStopTestAudio('', "We're sorry, but your router will not support JamKazam in its current configuration. Please click <a rel='external' href='https://jamkazam.desk.com/customer/portal/articles/1716139-what-to-do-if-you-cannot-pass-the-network-test'>HERE</a> for more information.");
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.stun, attempt.num_clients);
      }
      else if (reason == "internal_error") {
        context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection.");
        renderStopTestAudio('', '');
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == "remote_peer_cant_test") {
        context.JK.alertSupportedNeeded("The JamKazam service is experiencing technical difficulties.");
        renderStopTestAudio('', '');
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == "server_comm_timeout") {
        gearUtils.skipNetworkTest();
        context.JK.alertSupportedNeeded("Communication with the JamKazam network service has timed out." + appendContextualStatement());
        renderStopTestAudio('', '');
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == 'backend_gone') {
        context.JK.alertSupportedNeeded("The JamKazam client is experiencing technical difficulties.");
        renderStopTestAudio('', '');
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == "invalid_response") {
        gearUtils.skipNetworkTest();
        context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection.<br/><br/>Reason:  " + attempt.backend_data.reason + '.');
        renderStopTestAudio('', '');
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == 'no_servers') {
        gearUtils.skipNetworkTest();
        context.JK.Banner.showAlert("No network test servers are available." + appendContextualStatement());
        renderStopTestAudio('', '');
        testedSuccessfully = true;
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == 'no_network') {
        context.JK.Banner.showAlert("Please try again later. Your network appears down.");
        renderStopTestAudio('', '');
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.noNetwork);
      }
      else if (reason == "rest_api_error") {
        gearUtils.skipNetworkTest();
        context.JK.alertSupportedNeeded("Unable to acquire a network test server." + appendContextualStatement());
        testedSuccessfully = true;
        renderStopTestAudio('', '');
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == "timeout") {
        gearUtils.skipNetworkTest();
        context.JK.alertSupportedNeeded("Communication with the JamKazam network service timed out." + appendContextualStatement());
        testedSuccessfully = true;
        renderStopTestAudio('', '');
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else {
        gearUtils.skipNetworkTest();
        context.JK.alertSupportedNeeded("The JamKazam client software had a logic error while scoring your Internet connection.");
        renderStopTestAudio('', '');
        storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }

      numClientToTestAudio = STARTING_NUM_CLIENTS_AUDIO;
      audioScoring = false;
      postDiagnostic();

      if (success) {

        if(window.VideoStore.isVideoEnabled()) {
          startVideoTest();
        }
        else {
          // don't test video if it's disabled
          renderStopTestVideo(null, null)

          numClientToTestVideo = STARTING_NUM_CLIENTS_VIDEO;
          videoScoring = false;
          configureStartButton();

          $self.triggerHandler(NETWORK_TEST_DONE)
        }
      }
      else {
        configureStartButton();

        $self.triggerHandler(NETWORK_TEST_FAIL)
      }
    }

    function testFinishedVideo() {

      var attempt = getCurrentAttemptVideo();

      if (!testSummary.video_final) {
        testSummary.video_final = {reason: attempt.reason, num_clients: attempt.num_clients};
      }

      if (!testSummary.video_final.num_clients) {
        testSummary.video_final.num_clients = attempt.num_clients;
      }

      var reason = testSummary.video_final.reason;
      var success = false;

      logger.debug("testFinishedVideo", testSummary)

      if (reason == "success") {
        renderStopTestVideo(attempt.num_clients, null)
        //testedSuccessfully = true;
        if (!testSummary.video_final.num_clients) {
          testSummary.video_final.num_clients = attempt.num_clients;
        }

        // context.jamClient.GetNetworkTestScore() == 0 is a rough approximation if the user has passed the FTUE before
        if (inGearWizard || context.jamClient.GetVideoNetworkTestScore() == 0) {
          //trackedPass = true;
          //lastNetworkFailure = null;
          //context.JK.GA.trackNetworkTest(context.JK.detectOS(), testSummary.final.num_clients);
        }

        context.jamClient.SetVideoNetworkTestScore(attempt.num_clients);
        if (!testSummary.video_final.num_clients) {
          $testScoreVideo.addClass('acceptable');
        }
        else if (testSummary.video_final.num_clients >= 2) {
          $testScoreVideo.addClass('good');
        }
        else {
          $testScoreVideo.addClass('acceptable');
        }
        success = true;
      }
      else if (reason == "minimum_client_threshold") {
        context.jamClient.SetVideoNetworkTestScore(testSummary.video_final.num_clients - 1);
        renderStopTestVideo(testSummary.video_final.num_clients - 1, reason)
        //storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.bandwidth, avgBandwidth(attempt.num_clients - 1));
      }
      else if (reason == "unreachable" || reason == "no-transmit") {
        context.jamClient.SetVideoNetworkTestScore(testSummary.video_final.num_clients - 1);
        // https://jamkazam.atlassian.net/browse/VRFS-2323
        renderStopTestVideo(testSummary.video_final.num_clients - 1, reason);
        // storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.stun, attempt.num_clients);
      }
      else if (reason == "internal_error") {
        context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection.");
        renderStopTestVideo(testSummary.video_final.num_clients - 1, reason);
        //storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == "remote_peer_cant_test") {
        context.JK.alertSupportedNeeded("The JamKazam service is experiencing technical difficulties.");
        renderStopTestVideo(testSummary.video_final.num_clients - 1, reason);
        //storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == "server_comm_timeout") {
        //gearUtils.skipNetworkTest();
        context.JK.alertSupportedNeeded("Communication with the JamKazam network service has timed out.");
        renderStopTestVideo(testSummary.video_final.num_clients - 1, reason);
        //storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == 'backend_gone') {
        context.JK.alertSupportedNeeded("The JamKazam client is experiencing technical difficulties.");
        renderStopTestVideo(testSummary.video_final.num_clients - 1, reason);
        //storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == "invalid_response") {
        //gearUtils.skipNetworkTest();
        context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection.<br/><br/>Reason:  " + attempt.backend_data.reason + '.');
        renderStopTestVideo(testSummary.video_final.num_clients - 1, reason);
        //storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == 'no_servers') {
        // gearUtils.skipNetworkTest();
        context.JK.Banner.showAlert("No network test servers are available.");
        renderStopTestVideo(null, reason);
        //testedSuccessfully = true;
        //storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == 'no_network') {
        context.JK.Banner.showAlert("Please try again later. Your network appears down.");
        renderStopTestVideo(testSummary.video_final.num_clients - 1, reason);
        //storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.noNetwork);
      }
      else if (reason == "rest_api_error") {
        //gearUtils.skipNetworkTest();
        context.JK.alertSupportedNeeded("Unable to acquire a network test server.");
        //testedSuccessfully = true;
        renderStopTestVideo(null, reason);
        //storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else if (reason == "timeout") {
        //gearUtils.skipNetworkTest();
        context.JK.alertSupportedNeeded("Communication with the JamKazam network service timed out.");
        //testedSuccessfully = true;
        renderStopTestVideo(testSummary.video_final.num_clients - 1, reason);
        //storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }
      else {
        //gearUtils.skipNetworkTest();
        context.JK.alertSupportedNeeded("The JamKazam client software had a logic error while scoring your Internet connection.");
        renderStopTestVideo(testSummary.video_final.num_clients - 1, reason);
        //storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
      }

      numClientToTestVideo = STARTING_NUM_CLIENTS_VIDEO;
      videoScoring = false;
      configureStartButton();
      postDiagnostic();

      $self.triggerHandler(NETWORK_TEST_DONE)
    }

    function startVideoTest() {

      videoScoring = true;

      renderStartTestVideo();

      setTimeout(attemptTestPassVideo, 500);
    }

    function getCurrentAttemptAudio() {
      return testSummary.audioAttempts[testSummary.audioAttempts.length - 1];
    }
    function getCurrentAttemptVideo() {
      return testSummary.videoAttempts[testSummary.videoAttempts.length - 1];
    }

    function isFirstAttemptAudio() {
      return testSummary.audioAttempts.length == 0 || testSummary.audioAttempts.length == 1;
    }

    function isFirstAttemptVideo() {
      return testSummary.videoAttempts.length == 0 || testSummary.videoAttempts.length == 1;
    }

    function hasGoneDown() {
      var goneDown = false;
      context._.each(testSummary.audioAttempts, function(attempt) {
        if(attempt.num_clients == STARTING_NUM_CLIENTS_AUDIO - 1) {
          goneDown = true
          return false;
        }
      });
      return goneDown;
    }

    // is this a retry attempt? If so, how many times now has it been.
    // 0 = this is the 1st attempt
    // > 0 indicates the number of retries.
    function numRetryAttempts() {
      // starting at the end of the attempts array, see how many have the same session count, which is implicitely
      // indicative of a retry
      var i = 0;
      var testSessionSize = null;
      var numSameSizeTests = 0;

      for(i = testSummary.audioAttempts.length - 1; i >= 0; i--) {
        var attempt = testSummary.audioAttempts[i];

        if(testSessionSize === null) {
          // this is the 1st loop through. just recording the testSessionSize
          testSessionSize = attempt.num_clients;
        }
        else {
          if(testSessionSize == attempt.num_clients) {
            numSameSizeTests++;
          }
          else {
            break; // different size session found, so we are digging back into non-retry territory. bail out
          }
        }
      }
      return numSameSizeTests;
    }

    function hasTooManyRetries() {
      return numRetryAttempts() >= RETRY_THRESHOLD;

      return false;
    }

    function primeTimedOut() {
      logger.warn("backend never completed priming pump phase");
      audioScoring = false;
      primeDeferred.reject();
    }

    function backendTimedOut() {

      if (audioScoring) {
        testSummary.final = {reason: 'backend_gone'}
        testFinishedAudio();
      }
      else {
        testSummary.video_final = {reason: 'backend_gone'}
        testFinishedVideo();
      }
    }

    function cancel() {

    }

    function clearBackendGuard() {
      if (backendGuardTimeout) {
        clearTimeout(backendGuardTimeout);
        backendGuardTimeout = null;
      }
    }

    // we know we are attempting a pass if the backend or prime guard timeout is not null
    function isAttemptingPass() {
      return backendGuardTimeout != null || primeGuardTimeout != null;
    }

    function clearPrimeGuard() {
      if (primeGuardTimeout) {
        clearTimeout(primeGuardTimeout);
        primeGuardTimeout = null;
      }
    }

    function setPrimeGuard() {
      clearPrimeGuard();
      primeGuardTimeout = setTimeout(function () {
        clearPrimeGuard();
        primeTimedOut()
      }, (PRIME_PUMP_TIME + 10) * 1000);
    }

    function setBackendGuard() {
      clearBackendGuard();
      backendGuardTimeout = setTimeout(function () {
        clearBackendGuard();
        backendTimedOut()
      }, (gon.ftue_network_test_duration + 5) * 1000);
    }

    function attemptTestPassAudio() {

      var attempt = {};
      attempt.payload_size = AUDIO_PAYLOAD_SIZE;
      attempt.duration = gon.ftue_network_test_duration;
      attempt.test_type = 'PktTest400LowLatency';
      attempt.num_clients = numClientToTestAudio;
      attempt.server_client_id = serverClientId;
      attempt.received_progress = false;
      testSummary.audioAttempts.push(attempt);

      $scoredClientsAudio.text(numClientToTestAudio);

      //context.jamClient.StopNetworkTest('');

      $inProgressText.html("Simulating the network traffic of a " + numClientToTestAudio + "-person <b>audio-only</b> session.  Video will be tested next.");

      updateProgress(0, false);

      setBackendGuard();

      audioScoring = true;
      logger.debug("network test attempt: " + numClientToTestAudio + "-person audio session, 400 packets/s, " + AUDIO_PAYLOAD_SIZE + " byte payload")
      context.jamClient.TestNetworkPktBwRate(serverClientId, createSuccessCallbackName(false), createTimeoutCallbackName(false),
        NETWORK_TEST_TYPES.PktTest400LowLatency,
        gon.ftue_network_test_duration,
        numClientToTestAudio - 1,
        AUDIO_PAYLOAD_SIZE, gon.global.ftue_network_test_backend_retries);
    }

    function attemptTestPassVideo() {

      var attempt = {};
      attempt.payload_size = VIDEO_PAYLOAD_SIZE;
      attempt.duration = gon.ftue_network_test_duration;
      attempt.test_type = 'PktTest400LowLatency';
      attempt.num_clients = numClientToTestVideo;
      attempt.server_client_id = serverClientId;
      attempt.received_progress = false;
      testSummary.videoAttempts.push(attempt);

      $scoredClientsVideo.text(numClientToTestVideo);
      //context.jamClient.StopNetworkTest('');

      $inProgressText.html("Simulating the network traffic of a " + numClientToTestVideo + "-person <b>video-enabled</b> session.");

      updateProgress(0, false);

      setBackendGuard();

      logger.debug("network test attempt: " + numClientToTestVideo + "-person video session, 400 packets/s, " + VIDEO_PAYLOAD_SIZE + " byte payload")
      context.jamClient.TestNetworkPktBwRate(serverClientId, createSuccessCallbackName(false), createTimeoutCallbackName(false),
        NETWORK_TEST_TYPES.PktTest400LowLatency,
        gon.ftue_network_test_duration,
        numClientToTestVideo - 1,
        VIDEO_PAYLOAD_SIZE, gon.global.ftue_network_test_backend_retries);
    }
    
    // you have to score a little to 'prime' the logic to know whether it's on wireless or not
    function primePump() {
      audioScoring = true;
      primeDeferred = new $.Deferred();

      setPrimeGuard();

      context.jamClient.TestNetworkPktBwRate(serverClientId, createSuccessCallbackName(true), createTimeoutCallbackName(true),
        NETWORK_TEST_TYPES.PktTest400LowLatency,
        PRIME_PUMP_TIME,
        2,
        AUDIO_PAYLOAD_SIZE, gon.global.ftue_network_test_backend_retries);


      return primeDeferred;
    }

    function cancelTest() {
      audioScoring = false;
      configureStartButton();
      renderStopTestAudio();
      $self.triggerHandler(NETWORK_TEST_CANCEL)
    }

    function postPumpRun() {
      // check if on Wifi 1st
      var isWireless = context.jamClient.IsMyNetworkWireless();
      if (isWireless == -1) {
        logger.warn("unable to determine if the user is on wireless or not for network test. skipping prompt.")
      }

      if (isWireless == 1) {
        context.JK.Banner.showAlert({buttons: [
          {name: 'CANCEL', click: function () {
            cancelTest();
          }},
          {name: 'RUN NETWORK TEST ANYWAY', click: function () {
            attemptTestPassAudio();
            ;
          }}
        ],
          html: "<p>It appears that your computer is connected to your network using WiFi.</p>" +
            "<p>We strongly advise against running the JamKazam application on a WiFi connection. " +
            "We recommend using a wired Ethernet connection from your computer to your router. " +
            "A WiFi connection is likely to cause significant issues in both latency and audio quality.</p>"})
      }
      else {
        attemptTestPassAudio();
      }
    }

    function pauseForRecentScoresTime() {
      var lastScoreTimes = context.jamClient.GetLastLatencyTestTimes()

      console.log(lastScoreTimes)

      return 0;

      var noPause = 0;
      var longAgo = 1000000;
      var initiated = lastScoreTimes.initiatied;
      var requested = lastScoreTimes.requested;

      if(initiated === null || initiated === undefined) {
        logger.warn("lastScoreTimes.initiated is not set");
        initiated = longAgo;
      }
      if(requested === null || requested === undefined) {
        logger.warn("lastScoreTimes.requested is not set");
        requested = longAgo;
      }

      if(initiated == 0) {
        logger.debug("lastScoreTimes.initiated is zero");
        initiated = longAgo;
      }
      if(requested == 0) {
        logger.debug("lastScoreTimes.requested is zero");
        requested = longAgo;
      }

      if(initiated < 0) {
        logger.debug("lastScoreTimes.initiated is less than zero");
        initiated = longAgo;
      }
      if(requested < 0) {
        logger.debug("lastScoreTimes.requested is less than zero");
        requested = longAgo;
      }

      var mostRecentValue = initiated < requested ? initiated : requested;

      if(mostRecentValue > gon.globalftue_network_test_min_wait_since_last_score * 1000) {
        return noPause; // our last score was past our min wait; so no delay necessary
      }
      else {
        // pause for the remainder of the min wait threshold
        var remainder = gon.globalftue_network_test_min_wait_since_last_score * 1000 - mostRecentValue;

        if(remainder > 1500) {
          // we need to update the UI because this is a long time for a mystery pause
          $startNetworkTestBtn.text('SHORT QUIET PERIOD...')
        }

        return remainder;
      }
    }
    function prepareNetworkTest() {

      if (audioScoring || videoScoring) return false;

      setTimeout(function() {

        logger.info("starting network test");
        resetTestState();
        audioScoring = true;
        $self.triggerHandler(NETWORK_TEST_START);
        renderStartTestAudio();
        rest.getLatencyTester()
          .done(function (response) {
            // ensure there are no tests ongoing

            serverClientId = response.client_id;

            testSummary.serverClientId = serverClientId;

            logger.info("beginning network test against client_id: " + serverClientId);

            primePump()
              .done(function () {
                postPumpRun();
              })
              .fail(function () {
                logger.debug("unable to determine user's network type. primePump failed.")
                context.JK.Banner.showAlert({
                  title: 'Unable to Determine Network Type',
                  buttons: [
                    {name: 'CANCEL', click: function () {
                      cancelTest();
                    }},
                    {name: 'RUN NETWORK TEST ANYWAY', click: function () {
                      attemptTestPassAudio();
                      ;
                    }}
                  ],
                  html: "<p>We are unable to determine if your computer is connected to your network using WiFi.</p>" +
                    "<p>We strongly advise against running the JamKazam application on a WiFi connection. " +
                    "We recommend using a wired Ethernet connection from your computer to your router. " +
                    "A WiFi connection is likely to cause significant issues in both latency and audio quality.</p>"})
              });
          })
          .fail(function (jqXHR) {
            if (jqXHR.status == 404) {
              // means there are no network testers available.
              // we have to skip this part of the UI
              testSummary.final = {reason: 'no_servers'}
            }
            else {
              if (context.JK.isNetworkError(arguments)) {
                testSummary.final = {reason: 'no_network'}
              }
              else {
                testSummary.final = {reason: 'rest_api_error'}
              }
            }
            testFinishedAudio();
          })
      }, pauseForRecentScoresTime())


      return false;
    }

    function updateProgress(throughput, showSubscore) {

      var width = throughput * 100;

      $currentScore.stop().data('showSubscore', showSubscore);

      if (!showSubscore) {
        $subscore.text('');
      }

      $currentScore.animate({
        duration: 1000,
        width: width + '%'
      }, {
        step: function (now, fx) {
          if (showSubscore) {
            var newWidth = ( 100 * parseFloat($currentScore.css('width')) / parseFloat($currentScore.parent().css('width')) );
            $subscore.text((Math.round(newWidth * 10) / 10) + '%');
          }
        }
      }).css('overflow', 'visible');
      ;
    }

    function primePumpTimeout(data) {
      if(!isAttemptingPass()) {
        logger.error("primePumpTimeout: already completed the test pass. indicates backend sent > 1 final event");
        return;
      }
      clearPrimeGuard();
      audioScoring = false;
      logger.debug("the prime pump routine timed out")
      primeDeferred.reject();
    }

    function primePumpComplete(data) {
      if(!isAttemptingPass()) {
        logger.error("primePumpComplete: already completed the test pass. indicates backend sent > 1 final event");
        return;
      }

      if (data.progress === true) {
        // waiting...
        logger.debug("pump prime progress report");
        setPrimeGuard();
      }
      else {
        clearPrimeGuard();
        // we could check for errors, but it's confusing to do so. we just want to let the backend figure out if
        // the interface is wireless, or not
        setTimeout(function () {
          logger.debug("pump primed");
          audioScoring = false;
          primeDeferred.resolve();
        }, 500); // give backend room to breath for timing/race issues

      }
    }

    function networkTestCompleteAudio(data) {
      if(!isAttemptingPass()) {
        logger.error("networkTestComplete: already completed the test pass. indicates backend sent > 1 final event");
        return;
      }

      var attempt = getCurrentAttemptAudio();

      function refineTest(up) {
        if (up === null) {
          logger.debug("retrying test at size: " + numClientToTestAudio);
          setTimeout(attemptTestPassAudio, 500); // wait a second to avoid race conditions with client/server comm
        }
        else if (up) {
          if (numClientToTestAudio == gon.ftue_network_test_max_clients) {
            attempt.reason = "success";
            testFinishedAudio();
          }
          else if(hasGoneDown()) {
            // this means we've gone up before... so don't go back down (i.e., creating a loop)
            attempt.reason = "success";
            testSummary.final = { reason: 'success', num_clients: numClientToTestAudio }
            testFinishedAudio();
          }
          else {
            numClientToTestAudio++;
            logger.debug("increasing number of clients to " + numClientToTestAudio);
            setTimeout(attemptTestPassAudio, 500); // wait a second to avoid race conditions with client/server comm
          }
        }
        else {
          // reduce numclients if we can
          if (numClientToTestAudio == MINIMUM_ACCEPTABLE_SESSION_SIZE) {
            // we are too low already.  fail the user
            attempt.reason = "minimum_client_threshold";
            testFinishedAudio();
          }
          else if (numClientToTestAudio > STARTING_NUM_CLIENTS_AUDIO) {
            // this means we've gone up before... so don't go back down (i.e., creating a loop)
            attempt.reason = "success";
            testSummary.final = { reason: 'success', num_clients: numClientToTestAudio - 1 }
            testFinishedAudio();
          }
          else {
            numClientToTestAudio--;
            logger.debug("reducing number of clients to " + numClientToTestAudio);
            setTimeout(attemptTestPassAudio, 500); // wait a second to avoid race conditions with client/server comm
          }
        }
      }

      attempt.backend_data = data;

      if (data.progress === true) {

        setBackendGuard();

        var animate = true;
        if (data.downthroughput && data.upthroughput) {

          if (data.downthroughput > 0 || data.upthroughput > 0) {
            attempt.received_progress = true;
            animate = true;
          }

          if (attempt.received_progress) {
            // take the lower
            var throughput = data.downthroughput < data.upthroughput ? data.downthroughput : data.upthroughput;

            bandwidthSamples.push(data.upthroughput);

            updateProgress(throughput, true);
          }
        }
      }
      else {
        clearBackendGuard();
        logger.debug("network test pass completed.  data: ", data);

        if (data.reason == "unreachable") {
          logger.debug("network test: unreachable (STUN issue or similar)")
          attempt.reason = data.reason;
          testFinishedAudio();
        }
        else if (data.reason == "no-transmit") {
          logger.debug("network test: no-transmit (STUN issue or similar)");
          attempt.reason = data.reason;
          testFinishedAudio();
        }
        else if (data.reason == "internal_error") {
          // oops
          logger.debug("network test: internal_error (client had a unexpected problem)");
          attempt.reason = data.reason;
          testFinishedAudio();
        }
        else if (data.reason == "remote_peer_cant_test") {
          // old client
          logger.debug("network test: remote_peer_cant_test (old client)")
          attempt.reason = data.reason;
          testFinishedAudio();
        }
        else if (data.reason == "server_comm_timeout") {
          logger.debug("network test: server_comm_timeout (communication with server problem)")
          attempt.reason = data.reason;
          testFinishedAudio();
        }
        else {
          if (!data.downthroughput || !data.upthroughput) {
            // we have to assume this is bad.  just not a reason we know about in code
            logger.debug("network test: no test data (unknown issue? " + data.reason + ")")
            attempt.reason = "invalid_response";
            testFinishedAudio();
          }
          else {
            // success... but we still have to verify if this data is within threshold
            if (data.downthroughput < gon.ftue_packet_rate_treshold) {
              logger.debug("network test: downthroughput too low.  downthroughput: " + data.downthroughput + ", threshold: " + gon.ftue_packet_rate_treshold);
              refineTest(false);
            }
            else if (data.upthroughput < gon.ftue_packet_rate_treshold) {
              logger.debug("network test: upthroughput too low.  upthroughput: " + data.upthroughput + ", threshold: " + gon.ftue_packet_rate_treshold);
              refineTest(false);
            }
            else {
              // true success.  we can accept this score
              logger.debug("network test: success")
              refineTest(true);
            }
          }
        }

        // VRFS-1742
        // context.jamClient.StopNetworkTest(serverClientId);
      }

    }

    function networkTestCompleteVideo(data) {
      if(!isAttemptingPass()) {
        logger.error("networkTestCompleteVideo: already completed the test pass. indicates backend sent > 1 final event");
        return;
      }

      var attempt = getCurrentAttemptVideo();

      function refineTest(up) {
        if (up === null) {
          logger.debug("retrying video test at size: " + numClientToTestVideo);
          setTimeout(attemptTestPassVideo, 500); // wait a second to avoid race conditions with client/server comm
        }
        else if (up) {
          if (numClientToTestVideo == gon.ftue_network_test_max_clients) {
            attempt.reason = "success";
            testFinishedVideo();
          }
          else {
            numClientToTestVideo++;
            logger.debug("increasing number of clients to " + numClientToTestVideo);
            setTimeout(attemptTestPassVideo, 500); // wait a second to avoid race conditions with client/server comm
          }
        }
        else {
          attempt.reason = "success";
          testSummary.video_final = { reason: 'success', num_clients: numClientToTestVideo - 1 }
          testFinishedVideo();
        }
      }

      attempt.backend_data = data;

      if (data.progress === true) {

        setBackendGuard();

        var animate = true;
        if (data.downthroughput && data.upthroughput) {

          if (data.downthroughput > 0 || data.upthroughput > 0) {
            attempt.received_progress = true;
            animate = true;
          }

          if (attempt.received_progress) {
            // take the lower
            var throughput = data.downthroughput < data.upthroughput ? data.downthroughput : data.upthroughput;

            bandwidthSamples.push(data.upthroughput);

            updateProgress(throughput, true);
          }
        }
      }
      else {
        clearBackendGuard();
        logger.debug("network video test pass completed.  data: ", data);

        if (data.reason == "unreachable") {
          logger.debug("video network test: unreachable (STUN issue or similar)")
          attempt.reason = data.reason;
          testFinishedVideo();
        }
        else if (data.reason == "no-transmit") {
          logger.debug("video network test: no-transmit (STUN issue or similar)");
          attempt.reason = data.reason;
          testFinishedVideo();
        }
        else if (data.reason == "internal_error") {
          // oops
          logger.debug("video network test: internal_error (client had a unexpected problem)");
          attempt.reason = data.reason;
          testFinishedVideo();
        }
        else if (data.reason == "remote_peer_cant_test") {
          // old client
          logger.debug("video network test: remote_peer_cant_test (old client)")
          attempt.reason = data.reason;
          testFinishedVideo();
        }
        else if (data.reason == "server_comm_timeout") {
          logger.debug("video network test: server_comm_timeout (communication with server problem)")
          attempt.reason = data.reason;
          testFinishedVideo();
        }
        else {
          if (!data.downthroughput || !data.upthroughput) {
            // we have to assume this is bad.  just not a reason we know about in code
            logger.debug("video network test: no test data (unknown issue? " + data.reason + ")")
            attempt.reason = "invalid_response";
            testFinishedVideo();
          }
          else {
            // success... but we still have to verify if this data is within threshold
            if (data.downthroughput < gon.ftue_packet_rate_treshold) {
              logger.debug("video network test: downthroughput too low.  downthroughput: " + data.downthroughput + ", threshold: " + gon.ftue_packet_rate_treshold);
              refineTest(false);
            }
            else if (data.upthroughput < gon.ftue_packet_rate_treshold) {
              logger.debug("video network test: upthroughput too low.  upthroughput: " + data.upthroughput + ", threshold: " + gon.ftue_packet_rate_treshold);
              refineTest(false);
            }
            else {
              // true success.  we can accept this score
              logger.debug("vido network test: success")
              refineTest(true);
            }
          }
        }
      }

    }

    function networkTestTimeoutAudio(data) {
      if(!isAttemptingPass()) {
        logger.error("networkTestTimeout: already completed the audio test pass. indicates backend sent > 1 final event");
        return;
      }

      clearBackendGuard();

      logger.warn("network timeout when testing latency test: " + data);

      var attempt = getCurrentAttemptAudio();
      attempt.reason = 'timeout';
      attempt.backend_data = data;
      testFinishedAudio();
    }
    
    function networkTestTimeoutVideo(data) {
      if(!isAttemptingPass()) {
        logger.error("networkTestTimeout: already completed the video test pass. indicates backend sent > 1 final event");
        return;
      }

      clearBackendGuard();

      logger.warn("network timeout when testing latency test: " + data);

      var attempt = getCurrentAttemptVideo();
      attempt.reason = 'timeout';
      attempt.backend_data = data;
      testFinishedVideo();
    }

    function hasScoredNetworkSuccessfully() {
      return testedSuccessfully;
    }

    function configureStartButton() {
      if (audioScoring || videoScoring) {
        $startNetworkTestBtn.text('NETWORK TEST RUNNING...').removeClass('button-orange').addClass('button-grey')
      }
      else {
        $startNetworkTestBtn.text('START NETWORK TEST').removeClass('button-grey').addClass('button-orange');
      }
    }

    function initializeNextButtonState() {
      $dialog.setNextState(hasScoredNetworkSuccessfully() && !audioScoring && !videoScoring);
    }

    function initializeBackButtonState() {
      $dialog.setBackState(!audioScoring && !videoScoring);
    }

    function beforeHide() {

    }

    function initializeVideoWatchButton() {
      if (operatingSystem == "Win32") {
        $watchVideo.attr('href', 'https://www.youtube.com/watch?v=rhAdCVuwhBc');
      }
      else {
        $watchVideo.attr('href', 'https://www.youtube.com/watch?v=0r1py0AYJ4Y');
      }
    }

    function initialize(_$step, _inGearWizard) {
      $step = _$step;
      inGearWizard = _inGearWizard;

      $startNetworkTestBtn = $step.find('.start-network-test');
      $foreverNetworkTestBtn = $step.find('.forever-network-test')

      if ($startNetworkTestBtn.length == 0) {
        console.log('no start network test button found in network-test. in gear wizard=' + inGearWizard)
        return
        //throw 'no start network test button found in network-test. in gear wizard=' + inGearWizard
      }

      $testResults = $step.find('.network-test-results');
      $testScoreAudio = $step.find('.network-test-score-audio');
      $scoredClientsAudio = $testScoreAudio.find('.scored-clients');
      $testScoreVideo = $step.find('.network-test-score-video');
      $scoredClientsVideo = $testScoreVideo.find('.scored-clients');
      $testText = $step.find('.network-test-text');
      $inProgressText = $step.find('.in-progress')
      $audioResultText = $step.find('.audio-result')
      $videoResultText = $step.find('.video-result')
      $scoringBar = $step.find('.scoring-bar');
      $goodMarker = $step.find('.good-marker');
      $goodLine = $step.find('.good-line');
      $currentScore = $step.find('.current-score');

      $subscore = $step.find('.subscore');
      $watchVideo = $step.find('.watch-video');
      $container = $step.find('.network-test');
      if(inGearWizard) {
        $container.attr('data-mode', 'gear-wizard')
      }
      else {
        $container.attr('data-mode', 'standalone')
      }
      $startNetworkTestBtn.on('click', function () {
        forever = false;
        prepareNetworkTest();
        return false;
      });
      if(context.JK.currentUserAdmin) {
        $foreverNetworkTestBtn.on('click', function() {
          forever = true;
          prepareNetworkTest();
          return false;
        }).show();
      }

      operatingSystem = context.JK.GetOSAsString();

      initializeVideoWatchButton();


      // if this network test is instantiated anywhere else than the gearWizard, or a dialog, then this will have to be expanded
      if (inGearWizard) {
        context.JK.HandleNetworkTestSuccessForPumpPrimingGW = function (data) {
          primePumpComplete(data)
        };
        context.JK.HandleNetworkTestTimeoutForPumpPrimingGW = function (data) {
          primePumpTimeout(data)
        };
        context.JK.HandleNetworkTestSuccessForGearWizard = function (data) {
          if(audioScoring) {
            networkTestCompleteAudio(data);
          }
          else if(videoScoring) {
            networkTestCompleteVideo(data);
          }
          else {
            logger.warn("unknown state in HandleNetworkTestSuccessForGearWizard");
          }

        }; // pin to global for bridge callback
        context.JK.HandleNetworkTestTimeoutForGearWizard = function (data) {
          if(audioScoring) {
            networkTestTimeoutAudio(data);
          }
          else if(videoScoring) {
            networkTestTimeoutVideo(data);
          }

        }; // pin to global for bridge callback
      }
      else {
        context.JK.HandleNetworkTestSuccessForPumpPrimingDialog = function (data) {
          primePumpComplete(data)
        };
        context.JK.HandleNetworkTestTimeoutForPumpPrimingDialog = function (data) {
          primePumpTimeout(data)
        };
        context.JK.HandleNetworkTestSuccessForDialog = function (data) {
          if(audioScoring) {
            networkTestCompleteAudio(data);
          }
          else if(videoScoring) {
            networkTestCompleteVideo(data);
          }
          else {
            logger.warn("unknown state in HandleNetworkTestSuccessForDialog");
          }
        }; // pin to global for bridge callback
        context.JK.HandleNetworkTestTimeoutForDialog = function (data) {
          if(audioScoring) {
            networkTestTimeoutAudio(data);
          }
          else if(videoScoring) {
            networkTestTimeoutVideo(data);
          }
        }; // pin to global for bridge callback
      }

    }

    this.isScoring = function () {
      return audioScoring || videoScoring;
    };
    this.hasScoredNetworkSuccessfully = hasScoredNetworkSuccessfully;
    this.initialize = initialize;
    this.reset = reset;
    this.cancel = cancel;
    this.getLastNetworkFailure = getLastNetworkFailure;
    this.haltScoring = haltScoring;
    this.resumeScoring = resumeScoring;

    this.NETWORK_TEST_START = NETWORK_TEST_START;
    this.NETWORK_TEST_DONE = NETWORK_TEST_DONE;
    this.NETWORK_TEST_FAIL = NETWORK_TEST_FAIL;
    this.NETWORK_TEST_CANCEL = NETWORK_TEST_CANCEL;

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