(function (context, $) {

  "use strict";

  context.JK = context.JK || {};

  context.JK.ClientUpdate = function (app) {
    var self = this;
    var EVENTS = context.JK.EVENTS;
    var logger = context.JK.logger;
    var ellipsesJiggleTimer = null;
    var forceShow = false; // manual test helper
    // updated once a download is started
    var updateUri = null;
    var updateSize = 0;
    app.clientUpdating = false;


    function cancelUpdate(e) {
      if ((e.ctrlKey || e.metaKey) && e.keyCode == 78) {
        logger.debug("update canceled!");
        app.layout.closeDialog('client-update');
        app.clientUpdating = false;
      }
    }

    // responsible for updating the contents of the update dialog
    // as well as registering for any event handlers
    function updateClientUpdateDialog(templateId, options) {
      options = options || {};

      var template = $('#template-' + templateId).html();
      var templateHtml = context._.template(template, options, {variable: 'data'});

      $('#client_update .dialog-inner').html(templateHtml);

      $('#client_update').attr('data-mode', templateId);

      // assign click handlers
      if (templateId == "update-start") {

        $('body').on('keyup', cancelUpdate);

        $("#client_update a.close-application").click(function () {
          // noop atm
          return false;
        })

        $("#client_update a.start-update").click(function () {
          var uri = options.uri.replace('https://', 'http://');
          startDownload(uri)
          return false;
        })
      }
      else if (templateId == "update-downloading") {

        $('body').off('keyup', cancelUpdate);

        $("#client_update a.close-application").click(function () {
          // noop atm
          return false;
        })
      }
      else if (templateId == "update-restarting" || templateId == "update-error") {

        $("#client_update a.open-downloads").click(function () {
          context.JK.popExternalLink("https://www.jamkazam.com/downloads")
          return false;
        })
      }

      if(!app.layout.isDialogShowing('client-update')) {
        app.layout.showDialog('client-update')
      }

      //$('#client_update').show()
      //$('#client_update_overlay').show()
    }

    /***************************************/
    /******** CALLBACKS FROM BACKEND *******/
    /***************************************/
    function clientUpdateDownloadProgress(bytesReceived, bytesTotal, downloadSpeedMegSec, timeRemaining) {
      // this fires way too many times to leave in. uncomment if debugging update feature
      //logger.debug("bytesReceived: " + bytesReceived, ", bytesTotal: " + bytesTotal, ", downloadSpeed: " + downloadSpeedMegSec, ", timeRemaining: " + timeRemaining + ", updateSize: " + updateSize);

      bytesReceived = Number(bytesReceived)
      bytesTotal = Number(bytesTotal)
      // bytesTotal from Qt is not trust worthy; trust server's answer instead
      var progressWidth = ((bytesReceived / updateSize) * 100).toString() + "%";
      $('#progress-bar').width(progressWidth)
      //$("#progressbar_detail").text(parseInt(bytesReceived) + "/" + parseInt(updateSize))
    }

    function clientUpdateDownloadSuccess(updateLocation) {
      logger.debug("client update downloaded successfully to: " + updateLocation);

      updateClientUpdateDialog("update-proceeding");

      setTimeout(function () {
        // This method is synchronous, and does alot of work on a mac in particular, hanging the UI.
        // So, we do a sleep loop to make sure the UI is updated with the last message to the user, before we hang the UI
        startUpdate(updateLocation);
      }, 500);

    }

    function clientUpdateDownloadFailure(errorMsg) {
      logger.error("client update download error: " + errorMsg)

      updateClientUpdateDialog("update-error", {error_msg: "Unable to download client update.  Error reason: <br/>" + errorMsg });
    }


    function clientUpdateLaunchSuccess(userTimeToRead) {
      if(userTimeToRead === undefined) {
        userTimeToRead = 1000; // older clients didn't pass this in, and exit very quickly
      }

      logger.debug("client update launching in: " + userTimeToRead);

      // set timer to update countdown
      var rounded =  Math.round(userTimeToRead / 1000);

      // simple countdown timer
      var timer = setInterval(function(){
        var $countdown = $('#client_update .countdown-secs');
        var countdown = parseInt($countdown.text());

        if(countdown <= 0) {
          clearInterval(timer);
        }
        else {
          $countdown.text(countdown - 1);
        }
      }, rounded * 1000);

      updateClientUpdateDialog("update-restarting", {countdown: rounded, os: context.JK.GetOSAsString()});
    }

    function clientUpdateLaunchFailure(errorMsg) {
      logger.error("client update launch error: " + errorMsg)

      updateClientUpdateDialog("update-error", {error_msg: "Unable to launch client updater.  Error reason: <br/>" + errorMsg});
    }

    function clientUpdateLaunchStatuses(statuses) {
      logger.debug("client update launch statuses");

      if (statuses) {
        for (var i = 0; i < statuses.length; i++) {
          var status = statuses[i];
        }
      }
    }

    function clientUpdateLaunchStatusChange(done, status) {
      logger.debug("client update launch status change. starting=" + done + ", status=" + status);

      if (!done) {
        var $ellipses = $('<span class="ellipses">.</span>');
        $ellipses.data('count', 1);
        var $status = $('<span class="status"></span>');
        $status.text(status);
        $status.append($ellipses);
        $('#client-updater-updating').append($status);

        ellipsesJiggleTimer = setInterval(function () {
          var count = $ellipses.data('count');
          if (!count) {
            count = 0; // only the real client sometimes returns undefined for count
          }
          count++;
          if (count > 3) {
            count = 1;
          }
          $ellipses.text(Array(count + 1).join("."));
          $ellipses.data('count', count);
        }, 500);
      }
      else {
        clearInterval(ellipsesJiggleTimer);
        $('#client-updater-updating span.status').last().css('color', 'gray').find('span.ellipses').text('...');
      }

    }

    /********************************************/
    /******** END: CALLBACKS FROM BACKEND *******/
    /********************************************/

      // if the current version doesn't not match the server version, attempt to do an upgrade
    function shouldUpdate(currentVersion, version) {
      if(forceShow) return true;

      if (version === undefined || version == null || version == "") {
        return false;
      }
      else {
        return currentVersion != version;
      }
    }

    function runCheck(product, version, uri, size, currentVersion) {

      if (app.clientUpdating) {
        logger.debug("client is already updating; skipping")
        return
      }

      if(currentVersion === undefined) {
        currentVersion = context.jamClient.ClientUpdateVersion();

        if (!forceShow && (currentVersion == null || currentVersion.indexOf("Compiled")) > -1) {
          // this is a developer build; it doesn't make much sense to do an packaged update, so skip
          logger.debug("skipping client update check because this is a development build ('" + currentVersion + "')")
          return;
        }

        // # strange client oddity: remove quotes, if found, from start and finish of version.
        if (currentVersion.indexOf('"') == 0 && currentVersion.lastIndexOf('"') == currentVersion.length - 1) {
          currentVersion = currentVersion.substring(1, currentVersion.length - 1);
        }
      }

      logger.debug("our client version: " + currentVersion + ", server client version: " + version);

      // test url in lieu of having a configured server with a client-update available

      if (shouldUpdate(currentVersion, version)) {
        app.clientUpdating = true;
        updateUri = uri;
        updateSize = size;

        if(context.SessionStore.inSession())  {
          logger.debug("deferring client update because in session")
          return;
        }

        // test metadata in lieu of having a configured server with a client-update available
        //updateSize = 10000;
        //version = "1.2.3"

        // this will update the client dialog to how it should look when an update is just starting
        // and show it front-and-center on the screen
        updateClientUpdateDialog("update-start", { uri: updateUri })
      }
    }

    // check if updated is needed
    function check() {

      var os = context.JK.GetOSAsString();

      // check kill switch before all other logic
      if (!gon.check_for_client_updates) {
        logger.debug("skipping client update because the server is telling us not to")
        return;
      }

      var product = "JamClient"
      var currentVersion = context.jamClient.ClientUpdateVersion();

      if (!forceShow && (currentVersion == null || currentVersion.indexOf("Compiled")) > -1) {
        // this is a developer build; it doesn't make much sense to do an packaged update, so skip
        logger.debug("skipping client update check because this is a development build ('" + currentVersion + "')")
        return;
      }

      // # strange client oddity: remove quotes, if found, from start and finish of version.
      if (currentVersion.indexOf('"') == 0 && currentVersion.lastIndexOf('"') == currentVersion.length - 1) {
        currentVersion = currentVersion.substring(1, currentVersion.length - 1);
      }

      $.ajax({
        type: "GET",
        url: "/api/versioncheck?product=" + product + "&os=" + os,
        success: function (response) {
          runCheck(product, response.version, response.uri, response.size, currentVersion);
        },
        error: function (jqXHR, textStatus, errorThrown) {
          logger.error("Unable to do a client update check against /api/versioncheck");
        }
      });
    }

    function startDownload(url) {
      logger.debug("starting client updater download from: " + url);

      updateClientUpdateDialog("update-downloading")

      context.jamClient.ClientUpdateStartDownload(url,
        "JK.ClientUpdate.DownloadProgressCallback",
        "JK.ClientUpdate.DownloadSuccessCallback",
        "JK.ClientUpdate.DownloadFailureCallback");
    }

    function startUpdate(updaterFilePath) {
      logger.debug("starting client update from: " + updaterFilePath)

      context.jamClient.ClientUpdateStartUpdate(updaterFilePath,
        "JK.ClientUpdate.LaunchUpdateSuccessCallback",
        "JK.ClientUpdate.LaunchUpdateFailureCallback",
        "JK.ClientUpdate.LaunchUpdateStatusesCallback",
        "JK.ClientUpdate.LaunchUpdateStatusChangeCallback");
    }

    function initialize() {
      context.JK.ClientUpdate.DownloadProgressCallback = clientUpdateDownloadProgress;
      context.JK.ClientUpdate.DownloadSuccessCallback = clientUpdateDownloadSuccess;
      context.JK.ClientUpdate.DownloadFailureCallback = clientUpdateDownloadFailure;
      context.JK.ClientUpdate.LaunchUpdateSuccessCallback = clientUpdateLaunchSuccess;
      context.JK.ClientUpdate.LaunchUpdateFailureCallback = clientUpdateLaunchFailure;
      context.JK.ClientUpdate.LaunchUpdateStatusesCallback = clientUpdateLaunchStatuses;
      context.JK.ClientUpdate.LaunchUpdateStatusChangeCallback = clientUpdateLaunchStatusChange;

      app.bindDialog('client-update', {});

      $(document).on(EVENTS.SESSION_ENDED, function(e, data){
        if(app.clientUpdating) {
          updateClientUpdateDialog("update-start", { uri: updateUri})
        }
      });

      return self;
    }

    // Expose publics
    this.initialize = initialize;
    this.check = check;
    this.runCheck = runCheck;
  }

  return this;

})(window, jQuery);