(function (context, $) {

  "use strict";

  context.JK = context.JK || {};
  context.JK.FakeJamClient = function (app, p2pMessageFactory) {
    var ChannelGroupIds = context.JK.ChannelGroupIds
    var logger = context.JK.logger;
    logger.info("*** Fake JamClient instance initialized. ***");

    // Change this to false if you want to see FTUE with fake jam client
    var ftueStatus = true;
    var eventCallbackName = '';
    var alertCallbackName = '';
    var eventCallbackRate = 1000;
    var vuValue = -70;
    var vuChange = 10;
    var callbackTimer = null;
    var _mix = -30;
    var device_id = -1;
    var latencyCallback = null;
    var frameSize = 2.5;
    var fakeJamClientRecordings = null;
    var p2pCallbacks = null;
    var videoShared = false;
    var metronomeActive = false;
    var metronomeBPM = false;
    var metronomeSound = false;
    var metronomeMeter = 0;
    var backingTrackPath = "";
    var backingTrackLoop = false;
    var simulateNoInputs = false;

    function dbg(msg) {
      logger.debug('FakeJamClient: ' + msg);
    }

    // Bummer that javascript doesn't have much in the way of reflection.
    // arguments.callee.name would probably do what we want, but it's deprecated
    // and not allowed when using "strict"
    // (Wishing I could write a single function which debug logs the name of the
    // current function and a JSON stringify of the arguments).

    function getClientParentChildRole() {
      // 0 = client
      // 1 = parent
      return 1;
    }

    function getParentClientId() {
      return null;
    }

    function applySubscriptionPolicy() {

    }
    function OpenSystemBrowser(href) {
      dbg("OpenSystemBrowser('" + href + "')");
      context.window.open(href);
    }

    function RestartApplication() {
    }

    function ShutdownApplication() {
    }

    function FTUEPageEnter() {
    }

    function FTUEPageLeave() {
    }

    function FTUECancel() {
    }

    function FTUEGetMusicProfileName() {

      if (simulateNoInputs) {
        return "System Default (Playback Only)"
      }
      else {
        return "FTUEAttempt-1"
      }
    }

    function FTUESetMusicProfileName() {

    }

    function FTUESetPreferredMixerSampleRate() {
    }

    function FTUESetPreferredOutputSampleRate() {
    }

    function FTUESetPreferredChatSampleRate() {
    }

    function FTUEgetInputDeviceSampleRate() {
      return 44100;
    }

    function FTUEgetOutputDeviceSampleRate() {
      return 44100;
    }


    function FTUESelectVideoCaptureDevice(device, settings) {
      return true;
    }

    function FTUESetVideoEncodeResolution(resolution) {

    }

    function testVideoRender() {

    }

    function FTUEGetVideoCaptureDeviceNames() {
      return {"xy323ss": "Built-in Webcam HD"}
    }

    function FTUECurrentSelectedVideoDevice() {
      //return {}
      return {"xy323ss": "Built-in Webcam HD"}
    }

    function FTUEGetAvailableEncodeVideoResolutions() {
      return {
        1: "CIF    (352X288)",
        2: "VGA    (640X480)",
        3: "4CIF   (704X576)",
        4: "1/2WHD (640X360)",
        5: "WHD    (1280X720)",
        6: "FHD    (1920x1080)"
      }
    }

    function FTUEGetVideoCaptureDeviceCapabilities() {
      return {}
    }

    function FTUESetSendFrameRates(fps) {

    }

    function FTUEGetSendFrameRates() {
      return {20: 20, 24: 24, 30: 30}
    }

    function GetCurrentVideoResolution() {
      return 3;
    }

    function GetCurrentVideoFrameRate() {
      return 30;
    }

    function GetSampleRate() {
      return 48;
    }

    function FTUESetVideoShareEnable() {

    }

    function FTUEGetVideoShareEnable() {
      return false;
    }

    function isSessVideoShared() {
      return videoShared;
    }

    function SessStopVideoSharing() {
      videoShared = false;
    }

    function SessStartVideoSharing(bitrate) {
      if (!bitrate || typeof(bitrate) == "undefined") {
        bitrate = 0
      }
      videoShared = true;
    }

    function IsVstLoaded() {
      return false;
    }

    function hasVstAssignment() {
      return false;
    }

    function FTUEGetInputLatency() {
      dbg("FTUEGetInputLatency");
      return 2;
    }

    function FTUESetInputLatency(latency) {
      dbg("FTUESetInputLatency:" + latency);
    }

    function FTUEGetOutputLatency() {
      dbg("FTUEGetOutputLatency");
      return 2;
    }

    function FTUESetOutputLatency(latency) {
      dbg("FTUESetOutputLatency:" + latency);
    }

    function FTUEGetVolumeRanges() {
      dbg("FTUEGetVolumeRanges");
      return {
        input_maximum: 20,
        input_minimum: -80,
        output_maximum: 20,
        output_minimum: -80
      };
    }

    function FTUEHasControlPanel() {
      dbg("FTUEHasControlPanel");
      return true;
    }

    function FTUEGetFrameSize(newSize) {
      dbg("FTUEGetFrameSize");
      return frameSize;
    }

    function FTUESetFrameSize(newSize) {
      dbg("FTUESetFrameSize:" + newSize);
      // one of 2.5, 5 or 10
      frameSize = newSize;
    }

    function FTUEOpenControlPanel() {
      dbg("FTUEOpenControlPanel");
      context.alert("Simulated ASIO Dialog");
    }

    function FTUEInit() {
      dbg("FTUEInit");
    }

    function FTUERefreshDevices() {
      dbg("FTUERefreshDevices()");
    }

    function FTUESave(b) {
      dbg("FTUESave(" + b + ")");
      return {};
    }

    function FTUEGetStatus() {
      return ftueStatus;
    }

    function FTUESetStatus(b) {
      ftueStatus = b;
    }

    function FTUESetMusicDevice(id) {
      dbg("FTUESetMusicDevice");
    }

    function FTUEGetAudioDevices() {
      return {
        "devices": [{
          "display_name": "Built-in",
          "guid": "Built-in",
          "input_count": 1,
          "name": "Built-in",
          "output_count": 1,
          "port_audio_name": "Built-in"
        }, {
          "display_name": "JamKazam Virtual Monitor",
          "guid": "JamKazam Virtual Monitor",
          "input_count": 0,
          "name": "JamKazam Virtual Monitor",
          "output_count": 1,
          "port_audio_name": "JamKazam Virtual Monitor"
        }]
      }
    }

    function FTUEGetDevices() {
      dbg('FTUEGetMusicDevices');
      return {
        "ASIO4ALL v2": "ASIO4ALL v2 - ASIO",
        "M-Audio FW ASIO": "M-AUDIO FW ASIO - ASIO"
      };
    }

    function FTUEGetMusicInputs() {
      dbg('FTUEGetMusicInputs');
      return {
        "i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~1": "Multichannel (FWAPMulti) - Channel 1/Multichannel (FWAPMulti) - Channel 2"
      };
    }

    function FTUEGetMusicOutputs() {
      dbg('FTUEGetMusicOutputs');
      return {
        "o~11~Multichannel (FWAPMulti)~0^o~11~Multichannel (FWAPMulti)~1": "Multichannel (FWAPMulti) - Channel 1/Multichannel (FWAPMulti) - Channel 2"
      };
    }

    function FTUEGetChatInputs() {
      dbg('FTUEGetChatInputs');
      return {
        "i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~1": "Multichannel (FWAPMulti) - Channel 1/Multichannel (FWAPMulti) - Channel 2"
      };
    }

    function FTUEGetChannels() {
      return {
        "inputs": [
          {
            "assignment": 1,
            "chat": false,
            "device_id": "Built-in Microph",
            "device_type": 5,
            "id": "i~5~Built-in Microph~0~0~Built-in",
            "name": "Built-in Microph - Left",
            "number": 0
          },
          {
            "assignment": 0,
            "chat": false,
            "device_id": "Built-in Microph",
            "device_type": 5,
            "id": "i~5~Built-in Microph~1~0~Built-in",
            "name": "Built-in Microph - Right",
            "number": 1
          }
        ],
        "outputs": [
          {
            "assignment": -1,
            "chat": false,
            "device_id": "Built-in Output",
            "device_type": 5,
            "id": "o~5~Built-in Output~0~0~Built-in",
            "name": "Built-in Output - Left",
            "number": 0
          },
          {
            "assignment": -1,
            "chat": false,
            "device_id": "Built-in Output",
            "device_type": 5,
            "id": "o~5~Built-in Output~1~0~Built-in",
            "name": "Built-in Output - Right",
            "number": 1
          }
        ]
      };
    }

    function FTUEClearChannelAssignments() {
    }

    function FTUEClearChatInput() {
    }

    function FTUEGetAudioDevices() {
      return {
        "devices": [
          {
            "display_name": "Built-in",
            "guid": "Built-in",
            "input_count": 1,
            "name": "Built-in",
            "output_count": 1,
            "port_audio_name": "Built-in"
          },
          {
            "display_name": "JamKazam Virtual Monitor",
            "guid": "JamKazam Virtual Monitor",
            "input_count": 0,
            "name": "JamKazam Virtual Monitor",
            "output_count": 1,
            "port_audio_name": "JamKazam Virtual Monitor"
          }
        ]
      };
    }

    function FTUEStartIoPerfTest() {
    }

    function FTUEGetIoPerfData() {
      return {
        "in_var": 0.15,
        "out_var": 0.25,
        "in_median": 399.9,
        "out_median": 400.3,
        "in_target": 400,
        "out_target": 400
      };
    }

    function FTUESetInputMusicDevice() {
    }

    function FTUESetOutputMusicDevice() {
    }

    function FTUEGetInputMusicDevice() {
      return 'Built-in';
    }

    function FTUEGetOutputMusicDevice() {
      return 'Built-in';
    }

    function FTUESetMusicInput() {
      dbg('FTUESetMusicInput');
    }

    function FTUESetChatInput() {
      dbg('FTUESetChatInput');
    }

    function FTUESetMusicOutput() {
      dbg('FTUESetMusicOutput');
    }

    function FTUEGetInputVolume() {
      dbg('FTUEGetInputVolume');
      return -60;
    }

    function FTUESetInputVolume() {
      dbg('FTUESetInputVolume');
    }

    function FTUEGetOutputVolume() {
      dbg('FTUEGetOutputVolume');
      return -40;
    }

    function FTUESetOutputVolume() {
      dbg('FTUESetOutputVolume');
    }

    function FTUEGetChatInputVolume() {
      dbg('FTUEGetChatInputVolume');
      return -10;
    }

    function FTUESetChatInputVolume() {
      dbg('FTUESetChatInputVolume');
    }

    function FTUERegisterVUCallbacks() {
      dbg('FTUERegisterVUCallbacks');
    }

    function FTUERegisterLatencyCallback(functionName) {
      dbg('FTUERegisterLatencyCallback');
      latencyCallback = functionName;
    }

    function FTUEStartLatency() {
      function cb() {
        // Change 4192 to modify latency MS results (in microseconds)
        eval(latencyCallback + "(10.0)");
      }

      context.setTimeout(cb, 1000);
    }

    function FTUEGetExpectedLatency() {
      return {latencyknown: true, latency: 5}
    }

    function FTUEGetGoodConfigurationList() {
      return ['default'];
    }

    function FTUEGetAllAudioConfigurations() {
      return ['default'];
    }

    function FTUEGetGoodAudioConfigurations() {
      return ['default'];
    }

    function FTUEGetConfigurationDevice() {
      return 'Good Device';
    }

    function FTUELoadAudioConfiguration() {
      return true;
    }

    function FTUEIsMusicDeviceWDM() {
      return false;
    }

    function FTUECreateUpdatePlayBackProfile() {
      return true;
    }

    function RegisterVolChangeCallBack(functionName) {
      dbg('RegisterVolChangeCallBack');
    }


    function GetOS() {
      return 100000000;
    }

    function GetOSAsString() {
      //return "Win32";
      return "MacOSX";
    }

    function LatencyUpdated(map) {
      dbg('LatencyUpdated:' + JSON.stringify(map));
    }

    function LeaveSession(map) {
      dbg('LeaveSession:' + JSON.stringify(map));
    }

    // this is not a real bridge method; purely used by the fake jam client
    function RegisterP2PMessageCallbacks(callbacks) {
      p2pCallbacks = callbacks;
    }

    function P2PMessageReceived(from, payload) {

      dbg('P2PMessageReceived');
      // this function is different in that the payload is a JSON ready string;
      // whereas a real p2p message is base64 encoded binary packaged data

      try {
        payload = JSON.parse(payload);
      }
      catch (e) {
        logger.warn("unable to parse payload as JSON from client %o, %o, %o", from, e, payload);
      }

      var callback = p2pCallbacks[payload.type];
      if (callback) {
        callback(from, payload);
      }
    }

    function JoinSession(sessionId) {
      dbg('JoinSession:' + sessionId);
    }

    function ParticipantLeft(session, participant) {
      dbg('ParticipantLeft:' + JSON.stringify(session) + ',' +
        JSON.stringify(participant));
    }

    function ParticipantJoined(session, participant) {
      dbg('ParticipantJoined:' + JSON.stringify(session) + ',' +
        JSON.stringify(participant));
    }

    function RecordTestBegin() {
      dbg('RecordTestBegin');
    }

    function RecordTestEnd() {
      dbg('RecordTestBegin');
    }

    function RecordTestPlayback() {
      dbg('RecordTestBegin');
    }

    function SendP2PMessage(s1, s2) {
      dbg('SendP2PMessage:' + s1 + ',' + s2);
    }

    function SetASIOEnabled(i1, i2, i3, i4, i5, i6) {
      dbg('SetASIOEnabled(' + i1 + ',' + i2 + ',' + i3 + ',' + i4 + ',' + i5 + ',' + i6 + ')');
    }

    function SignalLatencyUpdated(client) {
      dbg('SignalLatencyUpdated:' + JSON.stringify(client));
    }

    function SignalSendP2PMessage(s1, ba) {
      dbg('SignalSendP2PMessage:' + JSON.stringify(arguments));
    }

    function StartPlayTest(s) {
      dbg('StartPlayTest' + JSON.stringify(arguments));
    }

    function StartRecordTest(s) {
      dbg('StartRecordTest' + JSON.stringify(arguments));
    }

    function StartRecording(recordingId, groupedClientTracks) {
      dbg('StartRecording');
      fakeJamClientRecordings.StartRecording(recordingId, groupedClientTracks);
    }

    function StopPlayTest() {
      dbg('StopPlayTest');
    }

    function StopRecording(recordingId, groupedTracks, errorReason, detail) {
      dbg('StopRecording');
      fakeJamClientRecordings.StopRecording(recordingId, groupedTracks, errorReason, detail);
    }

    function AbortRecording(recordingId, errorReason, errorDetail) {
      dbg('AbortRecording');
      fakeJamClientRecordings.AbortRecording(recordingId, errorReason, errorDetail);
    }

    function OnTrySyncCommand(cmd) {

    }

    function GetRecordingManagerState() {
      return {running: false}
    }

    function TestASIOLatency(s) {
      dbg('TestASIOLatency' + JSON.stringify(arguments));
    }

    function TestLatency(clientID, callbackFunctionName, timeoutCallbackName) {
      logger.debug("Fake JamClient: TestLatency called with client, " + clientID + " and callback function name: " + callbackFunctionName);
      var response = {
        clientID: clientID,
        latency: 50
      };
      var js = callbackFunctionName + "(" + JSON.stringify(response) + ");";
      eval(js);
    }

    function IsMyNetworkWireless() {
      // 1=true, 0 = false, -1=unknown
      return 1;
    }

    function SetNetworkTestScore(numClients) {

    }

    function SetVideoNetworkTestScore(numClients) {

    }

    function GetNetworkTestScore() {
      return 8;
    }


    function GetVideoNetworkTestScore() {
      return 8;
    }

    function SetLatencyTestBlocked(blocked) {

    }

    function isLatencyTestBlocked() {
      return false;
    }

    function GetLastLatencyTestTimes() {
      return {initiated: 10000, requested: 10000}
    }

    function GetASIODevices() {
      var response = [{
        "device_id": 0,
        "device_name": "Realtek High Definition Audio",
        "device_type": 0,
        "interfaces": [{
          "interface_id": 0,
          "interface_name": "Realtek HDA SPDIF Out",
          "pins": [{"is_input": false, "pin_id": 0, "pin_name": "PC Speaker"}]
        }, {
          "interface_id": 1,
          "interface_name": "Realtek HD Audio rear output",
          "pins": [{"is_input": false, "pin_id": 0, "pin_name": "PC Speaker"}]
        }, {
          "interface_id": 2,
          "interface_name": "Realtek HD Audio Mic input",
          "pins": [{"is_input": true, "pin_id": 0, "pin_name": "Recording Control"}]
        }, {
          "interface_id": 3,
          "interface_name": "Realtek HD Audio Line input",
          "pins": [{"is_input": true, "pin_id": 0, "pin_name": "Recording Control"}]
        }, {
          "interface_id": 4,
          "interface_name": "Realtek HD Digital input",
          "pins": [{"is_input": true, "pin_id": 0, "pin_name": "Capture"}]
        }, {
          "interface_id": 5,
          "interface_name": "Realtek HD Audio Stereo input",
          "pins": [{"is_input": true, "pin_id": 0, "pin_name": "Recording Control"}]
        }],
        "wavert_supported": false
      }, {
        "device_id": 1,
        "device_name": "M-Audio FW Audiophile",
        "device_type": 1,
        "interfaces": [{
          "interface_id": 0,
          "interface_name": "FWAPMulti",
          "pins": [{"is_input": false, "pin_id": 0, "pin_name": "Output"}, {
            "is_input": true,
            "pin_id": 1,
            "pin_name": "Input"
          }]
        }, {
          "interface_id": 1,
          "interface_name": "FW AP 1/2",
          "pins": [{"is_input": false, "pin_id": 0, "pin_name": "Output"}, {
            "is_input": true,
            "pin_id": 1,
            "pin_name": "Input"
          }]
        }, {
          "interface_id": 2,
          "interface_name": "FW AP SPDIF",
          "pins": [{"is_input": false, "pin_id": 0, "pin_name": "Output"}, {
            "is_input": true,
            "pin_id": 1,
            "pin_name": "Input"
          }]
        }, {
          "interface_id": 3,
          "interface_name": "FW AP 3/4",
          "pins": [{"is_input": false, "pin_id": 0, "pin_name": "Output"}]
        }],
        "wavert_supported": false
      }, {
        "device_id": 2,
        "device_name": "Virtual Audio Cable",
        "device_type": 2,
        "interfaces": [{
          "interface_id": 0,
          "interface_name": "Virtual Cable 2",
          "pins": [{"is_input": true, "pin_id": 0, "pin_name": "Capture"}, {
            "is_input": false,
            "pin_id": 1,
            "pin_name": "Output"
          }]
        }, {
          "interface_id": 1,
          "interface_name": "Virtual Cable 1",
          "pins": [{"is_input": true, "pin_id": 0, "pin_name": "Capture"}, {
            "is_input": false,
            "pin_id": 1,
            "pin_name": "Output"
          }]
        }],
        "wavert_supported": false
      }, {
        "device_id": 3,
        "device_name": "WebCamDV WDM Audio Capture",
        "device_type": 3,
        "interfaces": [{
          "interface_id": 0,
          "interface_name": "WebCamDV Audio",
          "pins": [{"is_input": true, "pin_id": 0, "pin_name": "Recording Control"}, {
            "is_input": false,
            "pin_id": 1,
            "pin_name": "Volume Control"
          }]
        }],
        "wavert_supported": false
      }];
      return response;
    }

    // Session Functions

    function SessionCurrrentJamTrackPlayPosMs() {
      return 0;
    }

    function GetJamTrackSettings() {
      return {tracks: []}
    }

    function SessionGetJamTracksPlayDurationMs() {
      return 60000;
    }

    function SessionAddTrack() {
    }

    function SessionAudioResync() {
      logger.debug("Fake JamClient: SessionAudioResync()");
    }

    function getConnectionDetail(arg1, arg2) {
      return {}
    }

    function SessionGetAllControlState(isMasterOrPersonal) {
      var mixerIds = SessionGetIDs()
      return SessionGetControlState(mixerIds, isMasterOrPersonal);
    }

    function SessionGetControlState(mixerIds, isMasterOrPersonal) {
      dbg("SessionGetControlState");
      var groups =
        [ChannelGroupIds.MasterGroup,
          ChannelGroupIds.MonitorGroup,
          ChannelGroupIds.AudioInputMusicGroup,
          ChannelGroupIds.AudioInputChatGroup,
          ChannelGroupIds.AudioInputChatGroup,
          ChannelGroupIds.UserMusicInputGroup,
          ChannelGroupIds.UserChatInputGroup,
          ChannelGroupIds.PeerMediaTrackGroup,
          ChannelGroupIds.JamTrackGroup,
          ChannelGroupIds.MetronomeGroup];
      var names = [
        "FWAPMulti",
        "FWAPMulti",
        "FWAPMulti",
        "FWAPMulti",
        "",
        "",
        "",
        "",
        "",
        ""
      ];

      var media_types = [
        "Master",
        "Monitor",
        "AudioInputMusic",
        "AudioInputChat",
        "StreamOutMusic",
        "UserMusicInput",
        "PeerAudioInputMusic",
        "PeerMediaTrack",
        "JamTrack",
        "MetronomeTrack"
      ]
      var clientIds = [
        "",
        "",
        "",
        "",
        "3933ebec-913b-43ab-a4d3-f21dc5f8955b",
        "",
        "",
        "",
        "",
        ""
      ];
      var response = [];
      for (var i = 0; i < mixerIds.length; i++) {

        // for testing no inputs, set simulateNoInputs = true
        if (simulateNoInputs && i == 2) continue;

        response.push({
          client_id: clientIds[i],
          group_id: groups[i],
          id: mixerIds[i] + (isMasterOrPersonal ? 'm' : 'p'),
          master: isMasterOrPersonal,
          media_type: media_types[i],
          monitor: !isMasterOrPersonal,
          mute: false,
          name: names[i],
          range_high: 20,
          range_low: -80,
          record: true,
          stereo: true,
          volume_left: -40,
          volume_right: -40,
          pan: 0,
          instrument_id: 50, // see globals.js
          mode: isMasterOrPersonal,
          rid: mixerIds[i]
        });
      }
      return response;
    }

    function SessionGetIDs() {
      return [
        "FWAPMulti_0_10000",
        "FWAPMulti_1_10100",
        "FWAPMulti_2_10200",
        "FWAPMulti_3_10500",
        "User@208.191.152.98#",
        "User@208.191.152.98_*"
      ];
    }

    function RegisterRecordingManagerCallbacks(commandStart, commandProgress, commandStop, commandsChanged) {

    }

    function RegisterRecordingCallbacks(startRecordingCallbackName, stopRecordingCallbackName, startedRecordingCallbackName, stoppedRecordingCallbackName, abortedRecordingCallbackName) {
      fakeJamClientRecordings.RegisterRecordingCallbacks(startRecordingCallbackName, stopRecordingCallbackName, startedRecordingCallbackName, stoppedRecordingCallbackName, abortedRecordingCallbackName);
    }

    function SessionRegisterCallback(callbackName) {
      eventCallbackName = callbackName;
      if (callbackTimer) {
        context.clearInterval(callbackTimer);
      }
      if (eventCallbackName) {
        callbackTimer = context.setInterval(doCallbacks, eventCallbackRate);
      }
    }

    function SessionSetAlertCallback(callback) {
      alertCallbackName = callback;

      // simulate a backend alert
      /**setTimeout(function() {
              eval(alertCallbackName + '(27)');
            }, 3000)*/
    }

    function SessionSetConnectionStatusRefreshRate(milliseconds) {
      dbg('SessionSetConnectionStatusRefreshRate: ' + milliseconds);
    }

    function SessionSetControlState(stringValue, isMasterOrPersonal) {
      dbg('SessionSetControlState: ' + stringValue + " m/p=" + isMasterOrPersonal);
    }

    function SessionSetRecordingFilename(filename) {
    }

    function SessionSetRecordingFolder(folderName) {
    }

    function SessionStartPlay() {
    }

    function SessionStartRecording() {
    }

    function SessionStopPlay() {
    }

    function SessionStopRecording() {
    }

    function SessionAddPlayTrack() {
      return true;
    }

    function SessionRemoveAllPlayTracks() {
    }

    function isSessionTrackPlaying() {
      return false;
    }

    function SessionCurrrentPlayPosMs() {
      return 0;
    }

    function SessionGetTracksPlayDurationMs() {
      return 0;
    }

    function SessionGetDeviceLatency() {
      return 10.0;
    }

    function SessionPageEnter() {
      logger.debug("FakeJamClient: SessionPageEnter");
      return {}
    }

    function SessionPageLeave() {
      logger.debug("FakeJamClient: SessionPageLeave")
    }

    function SetMixerMode(mode) {
    }

    function SessionGetMasterLocalMix() {
      logger.debug('SessionGetMasterLocalMix. Returning: ' + _mix);
      return _mix;
    }

    function SessionSetMasterLocalMix(level) {
      logger.debug('SessionSetMasterLocalMix(' + level + ')');
      _mix = level;
    }

    function SessionSetUserName(client_id, name) {

    }

    function doCallbacks() {
      var names = ["vu"];
      //var ids = ["FWAPMulti_2_10200", "FWAPMulti_0_10000"];
      var ids = ["i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~1",
        "i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~2"];

      var args = [];
      for (var i = 0; i < ids.length; i++) {
        for (var j = 0; j < names.length; j++) {
          args.push('"' + names[j] + '"');
          args.push('"' + ids[i] + '"');
          args.push(vuValue);
        }
      }
      var js = eventCallbackName + '(' + args.join(',') + ')';
      //eval(js);

      vuValue += vuChange;
      if (vuValue > 10 || vuValue < -70) {
        vuChange = vuChange * -1;
      }

    }

    function SetVURefreshRate(rateMS) {
      eventCallbackRate = rateMS;
      if (callbackTimer) {
        context.clearInterval(callbackTimer);
      }
      if (eventCallbackName) {
        callbackTimer = context.setInterval(doCallbacks, eventCallbackRate);
      }
    }

    // Track Functions


    // Returns a list of objects representing all available audio devices and
    // pins on the current system. On my windows box, I get 38 objects back.
    // First couple examples included here. Note that the list tends to come
    // back with all of the left inputs, then all the right inputs, then all
    // the left outputs, then all the right outputs.
    function TrackGetChannels() {
      // Real example:
      /*
       {
       device_id: "1394\\M-Audio&FW_Audiophile",
       full_id: "i~1~\\\\?\\1394#m-audio&fw_audiophile#d4eb0700036c0d00#{6994ad04-93ef-11d0-a3cc-00a0c9223196}\\fwap_12",
       id: "\\\\?\\1394#m-audio&fw_audiophile#d4eb0700036c0d00#{6994ad04-93ef-11d0-a3cc-00a0c9223196}\\fwap_12",
       input: true,
       left: true,
       name: "M-Audio FW Audiophile: FW AP 1/2 - Left"
       },
       */

      // But we'll just build a list of names and fake it
      var devices = [
        "M-Audio FW Audiophile: FW AP 1/2"
        //"M-Audio FW Audiophile: FW Multi 1/2",
        //"M-Audio FW Audiophile: FW SPDIF 1/2",
        //"Realtek High Definition Audio: Realtek HD Digital",
        // "Realtek High Definition Audio: Realtek HD Line",
        // "Realtek High Definition Audio: Realtek HD Audio Mic",
        // "Realtek High Definition Audio: Realtek HD Audio Stereo",
        //"WebCamDV WDM Audio Capture: WebCamDV Audio"
      ];
      var suffixes = [" - Left", " - Right", " - Left", " - Right"];
      var lefts = [true, false, true, false];
      var inputs = [true, true, false, false];
      var types = ["music", "non-music", "music", "non-music"];
      var response = [];
      var name, o, i, j;
      for (j = 0; j < 4; j++) {
        for (i = 0; i < devices.length; i++) {
          name = devices[i] + suffixes[j];
          o = {
            device_id: name,
            full_id: name,
            id: name,
            input: inputs[j],
            left: lefts[j],
            name: name,
            device_type: types[j]
          };
          response.push(o);
        }
      }
      return response;
    }

    function TrackGetCount() {
      return 1; // Real version returns whatever was set
    }

    function TrackGetInstrument(trackNumber) {
      if (trackNumber === 1) {
        return 100;
      }
      else {
        return 200;
      }
    }

    function TrackGetChatEnable() {
      return true;
    }

    function TrackSetChatEnable(chat) {

    }

    function TrackSaveAssignments() {
    }

    function TrackGetDevices() {
      var response = {
        "Built-in": "Built-in",
        "ASIO4ALL v2": "ASIO4ALL v2 - ASIO",
        "BEHRINGER USB AUDIO": "BEHRINGER USB AUDIO - ASIO",
        "{2B7D1F21-FA6E-11D4-0002-DDCA793267F2}": "SoundMAX HD Audio O - WDM",
        "{ABCC5A6C-C263-463B-A72F-A5BF64C86EBA}": "Generic USB Audio Device - WDM"
      };
      return response;
    }

    function TrackDeleteProfile(id) {

    }


    function TrackGetMusicDeviceID() {
      return device_id;
    }

    function TrackSetMusicDevice(id) {
      device_id = id;
    }

    function TrackGetMusicDeviceNames(input) {
      var names = [];
      names.push("Built-in");
      return names;
    }

    // mock response of this method by allowing only even device IDs to open control panel
    function TrackHasControlPanel() {
      return true;
    }

    function TrackIsMusicDeviceType(type) {
      return true;
    }

    function TrackGetChatUsesMusic() {
      return 0;
    }

    function TrackSetChatUsesMusic(usesMusic) {
    }

    function TrackOpenControlPanel() {
      return;
    }

    function TrackLoadAssignments() {
    }

    function TrackSave() {
    }

    // Set a Track Assignment.
    // assignment is an enum:
    // CHAT = -2, OUTPUT = -1, UNASSIGNED = 0
    function TrackSetAssignment(chanId, isInput, assignment) {
    }

    // Get a Track Assignment.
    function TrackGetAssignment(chanId, isInput) {
      // CHAT = -2, OUTPUT = -1, UNASSIGNED = 0
      return 0;
    }

    function TrackSetCount(count) {
    }

    function TrackSetInstrument(track, instrumentId) {
    }

    function JamTrackStopPlay() {
    }

    function JamTrackPlay() {
      return true;
    }

    function JamTrackIsPlayable() {
      return true;
    }

    function JamTrackGetTrackDetail() {
      return {key_state: 'unknown'}
    }

    function JamTrackKeysRequest() {
    }

    function JamTrackDownload() {
    }

    // Method which sets volume
    function UpdateMixer(mixerId) {
    }

    // scoring knobs
    function GetScoreWorkTimingInterval() {
      return {interval: 1000, backoff: 60000}
    }

    function SetScoreWorkTimingInterval(knobs) {
      return true;
    }

    function SessionOpenBackingTrackFile(path, loop) {
      backingTrackPath = path
      backingTrackLoop = loop
    }

    function SessionSetBackingTrackFileLoop(path, loop) {
      backingTrackPath = path
      backingTrackLoop = loop
    }

    function SessionCloseBackingTrackFile(path) {
      backingTrackPath = ""
    }

    function SessionOpenMetronome(bpm, click, meter, mode) {
      logger.debug("Setting metronome BPM: ", bpm)
      metronomeActive = true
      metronomeBPM = bpm
      metronomeSound = click
      metronomeMeter = meter
    }

    //change setting - click. Mode 0: = mono, 1, = left ear, 2= right ear
    function SessionSetMetronome(bpm, click, meter, mode) {
      SessionOpenMetronome(bpm, click, meter, mode)
    }

    //close everywhere
    function SessionCloseMetronome() {
      metronomeActive = false
    }

    function setMetronomeOpenCallback(callback) {

    }

    function getMyNetworkState() {
      return {
        ntp_stable: Math.random() > 0.5
      }
    }

    function getPeerState(clientId) {
      return {
        ntp_stable: Math.random() > 0.5
      }
    }


    // stun
    function NetworkTestResult() {
      return {remote_udp_blocked: false}
    }

    // Client Update Functions
    function IsAppInWritableVolume() {
      return true;
    }

    function ClientUpdateVersion() {
      return "Compiled 1.2.3";
    }

    function ClientUpdateStartDownload(url, progressCallback, successCallback, failureCallback) {

      // simulate a bunch of download callbacks
      var count = 0;
      var max = 100;
      var bytesReceived = 0;
      var bytesTotal = 10000;

      function fire() {
        count++;
        context.setTimeout(function () {
          bytesReceived = ( count / max ) * bytesTotal;
          context.JK.ClientUpdate.DownloadProgressCallback(bytesReceived, bytesTotal, 0, 0);

          if (count < max) {
            fire();
          }
          else {
            context.JK.ClientUpdate.DownloadFailureCallback("/some/path/here");
          }
        }, 50);
      }

      fire();

    }

    function getBackingTrackList() {
      return {
        backing_tracks: [
          {name: "This is a really long name for a song dude.mp3", size: 4283},
          {name: "foo.mp3", size: 325783838}
        ]
      };
    }

    function ClientUpdateStartUpdate(path, successCallback, failureCallback) {
    }

    // -------------------------------
    // fake jam client methods
    // not a part of the actual bridge
    // -------------------------------
    function SetFakeRecordingImpl(fakeRecordingsImpl) {
      fakeJamClientRecordings = fakeRecordingsImpl;
    }

    function TestNetworkPktBwRate(targetClientId, successCallback, timeoutCallback, testType, duration, numClients, payloadSize) {
      var progress = {
        progress: true,
        upthroughput: .95,
        downthroughput: .95,
        upjitter: 2.3,
        downjitter: 2.3
      }
      var count = 0;
      var interval = setInterval(function () {

        eval(successCallback + "(" + JSON.stringify(progress) + ");");

        if (progress.upthroughput < 1) {
          progress.upthroughput += .05;
        }
        if (progress.downthroughput < 1) {
          progress.downthroughput += .05;
        }

        count++;
        if (count == duration) {
          clearInterval(interval);

          delete progress['progress']
          progress.pass = true;
          eval(successCallback + "(" + JSON.stringify(progress) + ");");
        }
      }, 1000);
    }

    function StopNetworkTest(targetClientId) {
    }

    function OnLoggedIn(userId, sessionToken) {
    }

    function OnLoggedOut() {
    }

    function UserAttention(option) {
    }

    function SessionGetMacHash() {
      return null;
    }

    function IsFrontendVisible() {
      return true;
    }

    function LastUsedProfileName() {
      return 'default'
    };
    function SetLastUsedProfileName(name) {
      return true;
    };
    function log(level, message) {
      console.log("beep : " + message)
    }

    function getOperatingMode() {
      if (location.pathname == '/latency_tester') {
        return 'server';
      }
      else {
        return 'client';
      }
    }

    // passed an array of recording objects from the server
    function GetLocalRecordingState(recordings) {
      var result = {recordings: []};
      var recordingResults = result.recordings;

      var possibleAnswers = ['HQ', 'RT', 'MISSING', 'PARTIALLY_MISSING'];

      $.each(recordings, function (i, recording) {
        // just make up a random yes-hq/yes-rt/missing answer
        var recordingResult = {};
        recordingResult['aggregate_state'] = possibleAnswers[Math.floor((Math.random() * 4))];
        recordingResults.push(recordingResult);
      })

      return result;
    }

    function OpenRecording(claimedRecording) {
      return {success: true}
    }

    function PreviewRecording(claimedRecording) {
      return OpenRecording(claimedRecording);
    }

    function CloseRecording() {
    }

    function ClosePreviewRecording() {
      CloseRecording();
    }

    function OnDownloadAvailable() {
    }

    function SaveToClipboard(text) {
    }

    function IsNativeClient() { /* must always return false in all scenarios due to not ruin scoring !*/
      return false;
    }

    function IsAudioStarted() {
      logger.debug("FakeJamClient: IsAudioStarted");
      return false;
    }

    function StopAudio() {
      logger.debug("FakeJamClient: StopAudio");
    }

    function ResetPageCounters() {
      logger.debug("FakeJamClient: ResetPageCounters");
    }

    function ReloadAudioSystem(leaveRunning, loadLastProfile, reinitAssignments) {
      logger.debug("FakeJamClient: ReloadAudioSystem");
      return {};
    }

    function SessionLiveBroadcastStart(host, port, mount, sourceUser, sourcePass, preferredClientId, bitrate) {
      logger.debug("SessionLiveBroadcastStart requested");
    }

    function SessionLiveBroadcastStop() {
      logger.debug("SessionLiveBroadcastStop requested");
    }

    function RegisterQuitCallback() {
    }

    function LeaveSessionAndMinimize() {
    }

    function SetAutoStart() {
    }

    function GetAutoStart() {
      return true;
    }

    function SaveSettings() {
    }

    function GetUseStaticPort() {
      return false;
    }

    function SetUseStaticPort() {
    }

    function GetStaticPort() {
      return 0
    }

    function SetStaticPort() {
    }


    function VSTLoad() {
    }

    function VSTScan(callback) {
      setTimeout(eval(callback + "()"), 1000)
    }

    function hasVstHost() {
      return false;
    }

    function getPluginList() {
      return {vsts: []}
    }

    function clearPluginList() {
    }

    function listTrackAssignments() {
      return {}
    }

    // Javascript Bridge seems to camel-case
    // Set the instance functions:
    this.AbortRecording = AbortRecording;
    this.OnTrySyncCommand = OnTrySyncCommand;
    this.GetRecordingManagerState = GetRecordingManagerState;
    this.GetASIODevices = GetASIODevices;
    this.GetOS = GetOS;
    this.GetOSAsString = GetOSAsString;
    this.JoinSession = JoinSession;
    this.LatencyUpdated = LatencyUpdated;
    this.LeaveSession = LeaveSession;
    this.P2PMessageReceived = P2PMessageReceived;
    this.ParticipantJoined = ParticipantJoined;
    this.ParticipantLeft = ParticipantLeft;
    this.RecordTestBegin = RecordTestBegin;
    this.RecordTestEnd = RecordTestEnd;
    this.RecordTestPlayback = RecordTestPlayback;
    this.RegisterVolChangeCallBack = RegisterVolChangeCallBack;
    this.SendP2PMessage = SendP2PMessage;
    this.SetASIOEnabled = SetASIOEnabled;
    this.SignalLatencyUpdated = SignalLatencyUpdated;
    this.SignalSendP2PMessage = SignalSendP2PMessage;
    this.StartPlayTest = StartPlayTest;
    this.StartRecordTest = StartRecordTest;
    this.StartRecording = StartRecording;
    this.StopPlayTest = StopPlayTest;
    this.StopRecording = StopRecording;
    this.TestASIOLatency = TestASIOLatency;
    this.TestLatency = TestLatency;
    this.IsMyNetworkWireless = IsMyNetworkWireless;
    this.SetNetworkTestScore = SetNetworkTestScore;
    this.GetNetworkTestScore = GetNetworkTestScore;
    this.SetVideoNetworkTestScore = SetVideoNetworkTestScore;
    this.GetVideoNetworkTestScore = GetVideoNetworkTestScore;
    this.SetLatencyTestBlocked = SetLatencyTestBlocked;
    this.isLatencyTestBlocked = isLatencyTestBlocked;
    this.GetLastLatencyTestTimes = GetLastLatencyTestTimes;
    this.RegisterQuitCallback = RegisterQuitCallback;
    this.LeaveSessionAndMinimize = LeaveSessionAndMinimize;
    this.GetAutoStart = GetAutoStart;
    this.SetAutoStart = SetAutoStart;
    this.GetUseStaticPort = GetUseStaticPort;
    this.SetUseStaticPort = SetUseStaticPort;
    this.GetStaticPort = GetStaticPort;
    this.SetStaticPort = SetStaticPort;
    this.connected = true;

    // FTUE (round 3)
    this.FTUESetInputMusicDevice = FTUESetInputMusicDevice;
    this.FTUESetOutputMusicDevice = FTUESetOutputMusicDevice;
    this.FTUEGetInputMusicDevice = FTUEGetInputMusicDevice;
    this.FTUEGetOutputMusicDevice = FTUEGetOutputMusicDevice;
    this.FTUEGetChatInputVolume = FTUEGetChatInputVolume;
    this.FTUEGetChatInputs = FTUEGetChatInputs;
    this.FTUEGetChannels = FTUEGetChannels;
    this.FTUEGetAudioDevices = FTUEGetAudioDevices;
    this.FTUEStartIoPerfTest = FTUEStartIoPerfTest;
    this.FTUEGetIoPerfData = FTUEGetIoPerfData;
    this.FTUEGetDevices = FTUEGetDevices;
    this.FTUEGetFrameSize = FTUEGetFrameSize;
    this.FTUECancel = FTUECancel;
    this.FTUEPageEnter = FTUEPageEnter;
    this.FTUEPageLeave = FTUEPageLeave;
    this.FTUEGetMusicProfileName = FTUEGetMusicProfileName;
    this.FTUESetMusicProfileName = FTUESetMusicProfileName;
    this.FTUESetPreferredMixerSampleRate = FTUESetPreferredMixerSampleRate;
    this.FTUESetPreferredOutputSampleRate = FTUESetPreferredOutputSampleRate;
    this.FTUESetPreferredChatSampleRate = FTUESetPreferredChatSampleRate;
    this.FTUEgetInputDeviceSampleRate = FTUEgetInputDeviceSampleRate;
    this.FTUEgetOutputDeviceSampleRate = FTUEgetOutputDeviceSampleRate;
    this.FTUEGetInputLatency = FTUEGetInputLatency;
    this.FTUEGetInputVolume = FTUEGetInputVolume;
    this.FTUEGetMusicInputs = FTUEGetMusicInputs;
    this.FTUEGetMusicOutputs = FTUEGetMusicOutputs;
    this.FTUEGetOutputLatency = FTUEGetOutputLatency;
    this.FTUEGetOutputVolume = FTUEGetOutputVolume;
    this.FTUEGetStatus = FTUEGetStatus;
    this.FTUEGetVolumeRanges = FTUEGetVolumeRanges;
    this.FTUEHasControlPanel = FTUEHasControlPanel;
    this.FTUEInit = FTUEInit;
    this.FTUEOpenControlPanel = FTUEOpenControlPanel;
    this.FTUERegisterLatencyCallback = FTUERegisterLatencyCallback;
    this.FTUERegisterVUCallbacks = FTUERegisterVUCallbacks;
    this.FTUERefreshDevices = FTUERefreshDevices;
    this.FTUESave = FTUESave;
    this.FTUESetChatInput = FTUESetChatInput;
    this.FTUESetChatInputVolume = FTUESetChatInputVolume;
    this.FTUESetInputVolume = FTUESetInputVolume;
    this.FTUESetFrameSize = FTUESetFrameSize;
    this.FTUESetInputLatency = FTUESetInputLatency;
    this.FTUESetMusicDevice = FTUESetMusicDevice;
    this.FTUESetMusicInput = FTUESetMusicInput;
    this.FTUESetMusicOutput = FTUESetMusicOutput;
    this.FTUESetOutputLatency = FTUESetOutputLatency;
    this.FTUESetOutputVolume = FTUESetOutputVolume;
    this.FTUESetStatus = FTUESetStatus;
    this.FTUEStartLatency = FTUEStartLatency;
    this.FTUEGetExpectedLatency = FTUEGetExpectedLatency;
    this.FTUEGetGoodConfigurationList = FTUEGetGoodConfigurationList;
    this.FTUEGetAllAudioConfigurations = FTUEGetAllAudioConfigurations;
    this.FTUEGetGoodAudioConfigurations = FTUEGetGoodAudioConfigurations;
    this.FTUEGetConfigurationDevice = FTUEGetConfigurationDevice;
    this.FTUEIsMusicDeviceWDM = FTUEIsMusicDeviceWDM;
    this.FTUELoadAudioConfiguration = FTUELoadAudioConfiguration;
    this.FTUEClearChannelAssignments = FTUEClearChannelAssignments;
    this.FTUEClearChatInput = FTUEClearChatInput;
    this.FTUECreateUpdatePlayBackProfile = FTUECreateUpdatePlayBackProfile;

    // Session
    this.SessionAddTrack = SessionAddTrack;
    this.SessionCurrrentJamTrackPlayPosMs = SessionCurrrentJamTrackPlayPosMs;
    this.SessionGetJamTracksPlayDurationMs = SessionGetJamTracksPlayDurationMs;
    this.SessionGetControlState = SessionGetControlState;
    this.SessionGetAllControlState = SessionGetAllControlState;
    this.SessionSetUserName = SessionSetUserName;
    this.SessionGetIDs = SessionGetIDs;
    this.RegisterRecordingManagerCallbacks = RegisterRecordingManagerCallbacks;
    this.RegisterRecordingCallbacks = RegisterRecordingCallbacks;
    this.SessionRegisterCallback = SessionRegisterCallback;
    this.SessionSetAlertCallback = SessionSetAlertCallback;
    this.SessionSetControlState = SessionSetControlState;
    this.SessionSetRecordingFilename = SessionSetRecordingFilename;
    this.SessionSetRecordingFolder = SessionSetRecordingFolder;
    this.SessionStartPlay = SessionStartPlay;
    this.SessionStartRecording = SessionStartRecording;
    this.SessionStopPlay = SessionStopPlay;
    this.SessionAddPlayTrack = SessionAddPlayTrack;
    this.SessionRemoveAllPlayTracks = SessionRemoveAllPlayTracks;
    this.SessionStopRecording = SessionStopRecording;
    this.isSessionTrackPlaying = isSessionTrackPlaying;
    this.SessionCurrrentPlayPosMs = SessionCurrrentPlayPosMs;
    this.SessionGetTracksPlayDurationMs = SessionGetTracksPlayDurationMs;
    this.SessionPageEnter = SessionPageEnter;
    this.SessionPageLeave = SessionPageLeave;
    this.SetMixerMode = SetMixerMode;

    this.SetVURefreshRate = SetVURefreshRate;
    this.SessionGetMasterLocalMix = SessionGetMasterLocalMix;
    this.SessionSetMasterLocalMix = SessionSetMasterLocalMix;
    this.SessionGetDeviceLatency = SessionGetDeviceLatency;
    this.SessionAudioResync = SessionAudioResync;
    this.getConnectionDetail = getConnectionDetail;

    // Track
    this.TrackGetChannels = TrackGetChannels;
    this.TrackSetAssignment = TrackSetAssignment;
    this.TrackGetAssignment = TrackGetAssignment;
    this.TrackSetCount = TrackSetCount;
    this.TrackGetCount = TrackGetCount;
    this.TrackSave = TrackSave;
    this.TrackLoadAssignments = TrackLoadAssignments;
    this.TrackSetInstrument = TrackSetInstrument;
    this.TrackGetInstrument = TrackGetInstrument;
    this.TrackSaveAssignments = TrackSaveAssignments;

    this.TrackGetDevices = TrackGetDevices;
    this.TrackDeleteProfile = TrackDeleteProfile;
    this.TrackGetMusicDeviceID = TrackGetMusicDeviceID;
    this.TrackSetMusicDevice = TrackSetMusicDevice;
    this.TrackGetMusicDeviceNames = TrackGetMusicDeviceNames;
    this.TrackHasControlPanel = TrackHasControlPanel;
    this.TrackOpenControlPanel = TrackOpenControlPanel;
    this.TrackIsMusicDeviceType = TrackIsMusicDeviceType;

    this.TrackGetChatEnable = TrackGetChatEnable;
    this.TrackSetChatEnable = TrackSetChatEnable;

    this.TrackGetChatUsesMusic = TrackGetChatUsesMusic;
    this.TrackSetChatUsesMusic = TrackSetChatUsesMusic;

    this.GetJamTrackSettings = GetJamTrackSettings;
    this.JamTrackStopPlay = JamTrackStopPlay;
    this.JamTrackPlay = JamTrackPlay;
    this.JamTrackIsPlayable = JamTrackIsPlayable;
    this.JamTrackGetTrackDetail = JamTrackGetTrackDetail;
    this.JamTrackKeysRequest = JamTrackKeysRequest;
    this.JamTrackDownload = JamTrackDownload;

    // Scoring Knobs
    this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval;
    this.SetScoreWorkTimingInterval = SetScoreWorkTimingInterval;

    // Backing tracks:
    this.getBackingTrackList = getBackingTrackList;
    this.SessionCloseBackingTrackFile = SessionCloseBackingTrackFile;
    this.SessionOpenBackingTrackFile = SessionOpenBackingTrackFile;
    this.SessionSetBackingTrackFileLoop = SessionSetBackingTrackFileLoop;

    // Metronome:
    this.SessionCloseMetronome = SessionCloseMetronome;
    this.SessionOpenMetronome = SessionOpenMetronome;
    this.SessionSetMetronome = SessionSetMetronome;
    this.setMetronomeOpenCallback = setMetronomeOpenCallback;
    this.getMyNetworkState = getMyNetworkState;
    this.getPeerState = getPeerState;

    // Client Update
    this.IsAppInWritableVolume = IsAppInWritableVolume;
    this.ClientUpdateVersion = ClientUpdateVersion;
    this.ClientUpdateStartDownload = ClientUpdateStartDownload;
    this.ClientUpdateStartUpdate = ClientUpdateStartUpdate;

    this.getClientParentChildRole = getClientParentChildRole;
    this.getParentClientId = getParentClientId;
    this.OpenSystemBrowser = OpenSystemBrowser;
    this.RestartApplication = RestartApplication;
    this.ShutdownApplication = ShutdownApplication;

    // Websocket/Auth sessions
    this.OnLoggedIn = OnLoggedIn;
    this.OnLoggedOut = OnLoggedOut;
    this.SessionGetMacHash = SessionGetMacHash;

    this.UserAttention = UserAttention;
    this.IsFrontendVisible = IsFrontendVisible;
    this.LastUsedProfileName = LastUsedProfileName;
    this.SetLastUsedProfileName = SetLastUsedProfileName;

    // Recording Playback
    this.GetLocalRecordingState = GetLocalRecordingState;
    this.OpenRecording = OpenRecording;
    this.CloseRecording = CloseRecording;
    this.PreviewRecording = PreviewRecording;
    this.ClosePreviewRecording = ClosePreviewRecording;
    this.OnDownloadAvailable = OnDownloadAvailable;

    // Video functionality:
    this.testVideoRender = testVideoRender;
    this.FTUESelectVideoCaptureDevice = FTUESelectVideoCaptureDevice;
    this.FTUESetVideoEncodeResolution = FTUESetVideoEncodeResolution;
    this.FTUEGetVideoCaptureDeviceNames = FTUEGetVideoCaptureDeviceNames;
    this.FTUECurrentSelectedVideoDevice = FTUECurrentSelectedVideoDevice;
    this.FTUEGetAvailableEncodeVideoResolutions = FTUEGetAvailableEncodeVideoResolutions;
    this.FTUEGetVideoCaptureDeviceCapabilities = FTUEGetVideoCaptureDeviceCapabilities;
    this.FTUEGetSendFrameRates = FTUEGetSendFrameRates;
    this.FTUESetSendFrameRates = FTUESetSendFrameRates;
    this.GetCurrentVideoResolution = GetCurrentVideoResolution;
    this.GetCurrentVideoFrameRate = GetCurrentVideoFrameRate;
    this.GetSampleRate = GetSampleRate;
    this.FTUESetVideoShareEnable = FTUESetVideoShareEnable;
    this.FTUEGetVideoShareEnable = FTUEGetVideoShareEnable;
    this.isSessVideoShared = isSessVideoShared;
    this.SessStopVideoSharing = SessStopVideoSharing;
    this.SessStartVideoSharing = SessStartVideoSharing

    this.IsVstLoaded = IsVstLoaded;
    this.hasVstAssignment = hasVstAssignment;

    // Clipboard
    this.SaveToClipboard = SaveToClipboard;

    // Capabilities
    this.IsNativeClient = IsNativeClient;

    // Audio
    this.IsAudioStarted = IsAudioStarted;
    this.StopAudio = StopAudio;
    this.ResetPageCounters = ResetPageCounters;
    this.ReloadAudioSystem = ReloadAudioSystem;

    // Broadcasting
    this.SessionLiveBroadcastStart = SessionLiveBroadcastStart;
    this.SessionLiveBroadcastStop = SessionLiveBroadcastStop;
    this.SessionSetConnectionStatusRefreshRate = SessionSetConnectionStatusRefreshRate;

    // fake calls; not a part of the actual jam client
    this.RegisterP2PMessageCallbacks = RegisterP2PMessageCallbacks;
    this.SetFakeRecordingImpl = SetFakeRecordingImpl;

    // network test
    this.TestNetworkPktBwRate = TestNetworkPktBwRate;
    this.StopNetworkTest = StopNetworkTest;
    this.log = log;
    this.getOperatingMode = getOperatingMode;
    this.VSTLoad = VSTLoad;
    this.VSTScan = VSTScan;
    this.hasVstHost = hasVstHost;
    this.getPluginList = getPluginList;
    this.clearPluginList = clearPluginList;
    this.listTrackAssignments = listTrackAssignments;
    this.applySubscriptionPolicy = applySubscriptionPolicy;
    this.clientID = "devtester";
  };

})(window, jQuery);
