text: "use strict";
  /* jslint vars:true, plusplus:true, forin:true */
  /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
  //
  // wallet.js
  //
  // Created by Zach Fox on 2017-08-17
  // Copyright 2017 High Fidelity, Inc
  //
  // Distributed under the Apache License, Version 2.0
  // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
  //
  
  /* global getConnectionData getControllerWorldLocation openLoginWindow WalletScriptingInterface */
  
  (function () { // BEGIN LOCAL_SCOPE
  Script.include("/~/system/libraries/accountUtils.js");
  Script.include("/~/system/libraries/connectionUtils.js");
  var AppUi = Script.require('appUi');
  
  var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace";
  
  // BEGIN AVATAR SELECTOR LOGIC
  var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 };
  var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 };
  var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 };
  
  var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
  
  function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with.
      overlays[key] = this;
      this.key = key;
      this.selected = false;
      this.hovering = false;
      this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
  }
  // Instance methods:
  ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay
      Overlays.deleteOverlay(this.activeOverlay);
      delete overlays[this.key];
  };
  
  ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
      Overlays.editOverlay(this.activeOverlay, properties);
  };
  
  function color(selected, hovering) {
      var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR;
      function scale(component) {
          return component;
      }
      return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) };
  }
  // so we don't have to traverse the overlays to get the last one
  var lastHoveringId = 0;
  ExtendedOverlay.prototype.hover = function (hovering) {
      this.hovering = hovering;
      if (this.key === lastHoveringId) {
          if (hovering) {
              return;
          }
          lastHoveringId = 0;
      }
      this.editOverlay({ color: color(this.selected, hovering) });
      if (hovering) {
          // un-hover the last hovering overlay
          if (lastHoveringId && lastHoveringId !== this.key) {
              ExtendedOverlay.get(lastHoveringId).hover(false);
          }
          lastHoveringId = this.key;
      }
  };
  ExtendedOverlay.prototype.select = function (selected) {
      if (this.selected === selected) {
          return;
      }
  
      this.editOverlay({ color: color(selected, this.hovering) });
      this.selected = selected;
  };
  // Class methods:
  var selectedId = false;
  ExtendedOverlay.isSelected = function (id) {
      return selectedId === id;
  };
  ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier
      return overlays[key];
  };
  ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy.
      var key;
      for (key in overlays) {
          if (iterator(ExtendedOverlay.get(key))) {
              return;
          }
      }
  };
  ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any)
      if (lastHoveringId) {
          ExtendedOverlay.get(lastHoveringId).hover(false);
      }
  };
  
  // hit(overlay) on the one overlay intersected by pickRay, if any.
  // noHit() if no ExtendedOverlay was intersected (helps with hover)
  ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) {
      // Depends on nearer coverOverlays to extend closer to us than farther ones.
      var pickedOverlay = Overlays.findRayIntersection(pickRay);
      if (!pickedOverlay.intersects) {
          if (noHit) {
              return noHit();
          }
          return;
      }
      ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours.
          if ((overlay.activeOverlay) === pickedOverlay.overlayID) {
              hit(overlay);
              return true;
          }
      });
  };
  
  function addAvatarNode(id) {
      return new ExtendedOverlay(id, "sphere", {
          drawInFront: true,
          solid: true,
          alpha: 0.8,
          color: color(false, false),
          ignoreRayIntersection: false
      });
  }
  
  var pingPong = true;
  var OVERLAY_SCALE = 0.032;
  function updateOverlays() {
      var eye = Camera.position;
      AvatarList.getAvatarIdentifiers().forEach(function (id) {
          if (!id) {
              return; // don't update ourself, or avatars we're not interested in
          }
          var avatar = AvatarList.getAvatar(id);
          if (!avatar) {
              return; // will be deleted below if there had been an overlay.
          }
          var overlay = ExtendedOverlay.get(id);
          if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
              overlay = addAvatarNode(id);
          }
          var target = avatar.position;
          var distance = Vec3.distance(target, eye);
          var offset = 0.2;
          // get diff between target and eye (a vector pointing to the eye from avatar position)
          var diff = Vec3.subtract(target, eye);
          var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
          if (headIndex > 0) {
              offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
          }
  
          // move a bit in front, towards the camera
          target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
  
          // now bump it up a bit
          target.y = target.y + offset;
  
          overlay.ping = pingPong;
          overlay.editOverlay({
              color: color(ExtendedOverlay.isSelected(id), overlay.hovering),
              position: target,
              dimensions: OVERLAY_SCALE * distance
          });
      });
      pingPong = !pingPong;
      ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
          if (overlay.ping === pingPong) {
              overlay.deleteOverlay();
          }
      });
  }
  function removeOverlays() {
      selectedId = false;
      lastHoveringId = 0;
      ExtendedOverlay.some(function (overlay) {
          overlay.deleteOverlay();
      });
  }
  
  //
  // Clicks.
  //
  function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
      if (selectedId === id) {
          var message = {
              method: 'updateSelectedRecipientUsername',
              userName: username === "" ? "unknown username" : username
          };
          ui.sendMessage(message);
      }
  }
  function handleClick(pickRay) {
      ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
          var nextSelectedStatus = !overlay.selected;
          var avatarId = overlay.key;
          selectedId = nextSelectedStatus ? avatarId : false;
          if (nextSelectedStatus) {
              Users.requestUsernameFromID(avatarId);
          }
          var message = {
              method: 'selectRecipient',
              id: avatarId,
              isSelected: nextSelectedStatus,
              displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"',
              userName: ''
          };
          ui.sendMessage(message);
  
          ExtendedOverlay.some(function (overlay) {
              var id = overlay.key;
              var selected = ExtendedOverlay.isSelected(id);
              overlay.select(selected);
          });
  
          return true;
      });
  }
  function handleMouseEvent(mousePressEvent) { // handleClick if we get one.
      if (!mousePressEvent.isLeftButton) {
          return;
      }
      handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y));
  }
  function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic
      ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
          overlay.hover(true);
      }, function () {
          ExtendedOverlay.unHover();
      });
  }
  
  // handy global to keep track of which hand is the mouse (if any)
  var currentHandPressed = 0;
  var TRIGGER_CLICK_THRESHOLD = 0.85;
  var TRIGGER_PRESS_THRESHOLD = 0.05;
  
  function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
      var pickRay;
      if (HMD.active) {
          if (currentHandPressed !== 0) {
              pickRay = controllerComputePickRay(currentHandPressed);
          } else {
              // nothing should hover, so
              ExtendedOverlay.unHover();
              return;
          }
      } else {
          pickRay = Camera.computePickRay(event.x, event.y);
      }
      handleMouseMove(pickRay);
  }
  function handleTriggerPressed(hand, value) {
      // The idea is if you press one trigger, it is the one
      // we will consider the mouse.  Even if the other is pressed,
      // we ignore it until this one is no longer pressed.
      var isPressed = value > TRIGGER_PRESS_THRESHOLD;
      if (currentHandPressed === 0) {
          currentHandPressed = isPressed ? hand : 0;
          return;
      }
      if (currentHandPressed === hand) {
          currentHandPressed = isPressed ? hand : 0;
          return;
      }
      // otherwise, the other hand is still triggered
      // so do nothing.
  }
  
  // We get mouseMoveEvents from the handControllers, via handControllerPointer.
  // But we don't get mousePressEvents.
  var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
  var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
  function controllerComputePickRay(hand) {
      var controllerPose = getControllerWorldLocation(hand, true);
      if (controllerPose.valid) {
          return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) };
      }
  }
  function makeClickHandler(hand) {
      return function (clicked) {
          if (clicked > TRIGGER_CLICK_THRESHOLD) {
              var pickRay = controllerComputePickRay(hand);
              handleClick(pickRay);
          }
      };
  }
  function makePressHandler(hand) {
      return function (value) {
          handleTriggerPressed(hand, value);
      };
  }
  triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
  triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
  triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
  triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
  // END AVATAR SELECTOR LOGIC
  
  var sendMoneyRecipient;
  var sendMoneyParticleEffectUpdateTimer;
  var particleEffectTimestamp;
  var sendMoneyParticleEffect;
  var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250;
  var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000;
  var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8;
  var SEND_MONEY_PARTICLE_PROPERTIES = {
      accelerationSpread: { x: 0, y: 0, z: 0 },
      alpha: 1,
      alphaFinish: 1,
      alphaSpread: 0,
      alphaStart: 1,
      azimuthFinish: 0,
      azimuthStart: -6,
      color: { red: 143, green: 5, blue: 255 },
      colorFinish: { red: 255, green: 0, blue: 204 },
      colorSpread: { red: 0, green: 0, blue: 0 },
      colorStart: { red: 0, green: 136, blue: 255 },
      emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate
      emitDimensions: { x: 0, y: 0, z: 0 },
      emitOrientation: { x: 0, y: 0, z: 0 },
      emitRate: 4,
      emitSpeed: 2.1,
      emitterShouldTrail: true,
      isEmitting: 1,
      lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate
      lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1,
      maxParticles: 20,
      name: 'hfc-particles',
      particleRadius: 0.2,
      polarFinish: 0,
      polarStart: 0,
      radiusFinish: 0.05,
      radiusSpread: 0,
      radiusStart: 0.2,
      speedSpread: 0,
      textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png",
      type: 'ParticleEffect'
  };
  
  var MS_PER_SEC = 1000;
  function updateSendMoneyParticleEffect() {
      var timestampNow = Date.now();
      if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * MS_PER_SEC)) {
          deleteSendMoneyParticleEffect();
          return;
      } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) {
          Entities.editEntity(sendMoneyParticleEffect, {
              isEmitting: 0
          });
      } else if (sendMoneyParticleEffect) {
          var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position;
          var distance = Vec3.distance(recipientPosition, MyAvatar.position);
          var accel = Vec3.subtract(recipientPosition, MyAvatar.position);
          accel.y -= 3.0;
          var life = Math.sqrt(2 * distance / Vec3.length(accel));
          Entities.editEntity(sendMoneyParticleEffect, {
              emitAcceleration: accel,
              lifespan: life
          });
      }
  }
  
  function deleteSendMoneyParticleEffect() {
      if (sendMoneyParticleEffectUpdateTimer) {
          Script.clearInterval(sendMoneyParticleEffectUpdateTimer);
          sendMoneyParticleEffectUpdateTimer = null;
      }
      if (sendMoneyParticleEffect) {
          sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect);
      }
      sendMoneyRecipient = null;
  }
  
  function onUsernameChanged() {
      if (Account.username !== Settings.getValue("keepMeLoggedIn/savedUsername")) {
          Settings.setValue("keepMeLoggedIn", false);
          Settings.setValue("keepMeLoggedIn/savedUsername", "");
      }
  }    
      
  var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
  var METAVERSE_SERVER_URL = Account.metaverseServerURL;
  var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
  function openMarketplace(optionalItemOrUrl) {
      // This is a bit of a kluge, but so is the whole file.
      // If given a whole path, use it with no cta.
      // If given an id, build the appropriate url and use the id as the cta.
      // Otherwise, use home and 'marketplace cta'.
      // AND... if call onMarketplaceOpen to setupWallet if we need to.
      var url = optionalItemOrUrl || MARKETPLACE_URL_INITIAL;
      // If optionalItemOrUrl contains the metaverse base, then it's a url, not an item id.
      if (optionalItemOrUrl && optionalItemOrUrl.indexOf(METAVERSE_SERVER_URL) === -1) {
          url = MARKETPLACE_URL + '/items/' + optionalItemOrUrl;
      }
      ui.open(url, MARKETPLACES_INJECT_SCRIPT_URL);
  }
  
  function setCertificateInfo(itemCertificateId) {
      ui.tablet.sendToQml({
          method: 'inspectionCertificate_setCertificateId',
          entityId: "",
          certificateId: itemCertificateId
      });
  }
  
  // Function Name: fromQml()
  //
  // Description:
  //   -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML
  //    in the format "{method, params}", like json-rpc. See also sendToQml().
  function fromQml(message) {
      switch (message.method) {
      case 'passphrasePopup_cancelClicked':
      case 'needsLogIn_cancelClicked':
          ui.close();
          break;
      case 'walletSetup_cancelClicked':
          switch (message.referrer) {
          case '': // User clicked "Wallet" app
          case undefined:
          case null:
              ui.close();
              break;
          case 'purchases':
          case 'marketplace cta':
          case 'mainPage':
              ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
              break;
          default: // User needs to return to an individual marketplace item URL
              ui.open(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL);
              break;
          }
          break;
      case 'needsLogIn_loginClicked':
          ui.close();
          openLoginWindow();
          break;
      case 'disableHmdPreview':
          break; // do nothing here, handled in marketplaces.js
      case 'maybeEnableHmdPreview':
          break; // do nothing here, handled in marketplaces.js
      case 'transactionHistory_linkClicked':
          ui.open(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL);
          break;
      case 'goToMarketplaceMainPage':
          ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
          break;
      case 'goToMarketplaceItemPage':
          ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
          break;
      case 'refreshConnections':
          print('Refreshing Connections...');
          getConnectionData(false);
          break;
      case 'enable_ChooseRecipientNearbyMode':
          if (!isUpdateOverlaysWired) {
              Script.update.connect(updateOverlays);
              isUpdateOverlaysWired = true;
          }
          break;
      case 'disable_ChooseRecipientNearbyMode':
          if (isUpdateOverlaysWired) {
              Script.update.disconnect(updateOverlays);
              isUpdateOverlaysWired = false;
          }
          removeOverlays();
          break;
      case 'sendAsset_sendPublicly':
          deleteSendMoneyParticleEffect();
          sendMoneyRecipient = message.recipient;
          var props = SEND_MONEY_PARTICLE_PROPERTIES;
          props.parentID = MyAvatar.sessionUUID;
          props.position = MyAvatar.position;
          props.position.y += 0.2;
          if (message.effectImage) {
              props.textures = message.effectImage;
          }
          sendMoneyParticleEffect = Entities.addEntity(props, true);
          particleEffectTimestamp = Date.now();
          updateSendMoneyParticleEffect();
          sendMoneyParticleEffectUpdateTimer =
              Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE);
          break;
      case 'transactionHistory_goToBank':
          if (Account.metaverseServerURL.indexOf("staging") >= 0) {
              Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging.
          } else {
              Window.location = "hifi://BankOfHighFidelity";
          }
          break;
      case 'purchases_updateWearables':
          var currentlyWornWearables = [];
          var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case)
  
          var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS);
  
          for (var i = 0; i < nearbyEntities.length; i++) {
              var currentProperties = Entities.getEntityProperties(
                  nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID']
              );
              if (currentProperties.parentID === MyAvatar.sessionUUID) {
                  currentlyWornWearables.push({
                      entityID: nearbyEntities[i],
                      entityCertID: currentProperties.certificateID,
                      entityEdition: currentProperties.editionNumber
                  });
              }
          }
  
          ui.tablet.sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables });
          break;
      case 'purchases_walletNotSetUp':
          ui.tablet.sendToQml({
              method: 'updateWalletReferrer',
              referrer: "purchases"
          });
          break;
      case 'purchases_openGoTo':
          ui.open("hifi/tablet/TabletAddressDialog.qml");
          break;
      case 'purchases_itemInfoClicked':
          var itemId = message.itemId;
          if (itemId && itemId !== "") {
              openMarketplace(itemId);
          }
          break;
      case 'purchases_itemCertificateClicked':
          setCertificateInfo(message.itemCertificateId);
          break;
      case 'clearShouldShowDotHistory':
          shouldShowDotHistory = false;
          ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory);
          break;
      case 'http.request':
          // Handled elsewhere, don't log.
          break;
      case 'closeSendAsset':
          ui.close();
          break;
      default:
          print('wallet.js: Unrecognized message from QML');
      }
  }
  
  var isWired = false;
  function walletOpened() {
      Users.usernameFromIDReply.connect(usernameFromIDReply);
      Controller.mousePressEvent.connect(handleMouseEvent);
      Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
      triggerMapping.enable();
      triggerPressMapping.enable();
      isWired = true;
  
      if (shouldShowDotHistory) {
          ui.sendMessage({
              method: 'updateRecentActivityMessageLight',
              messagesWaiting: shouldShowDotHistory
          });
      }
  }
  
  function walletClosed() {
      off();
  }
  
  function notificationDataProcessPageUpdates(data) {
      return data.data.updates;
  }
  
  function notificationDataProcessPageHistory(data) {
      return data.data.history;
  }
  
  var shouldShowDotUpdates = false;
  function notificationPollCallbackUpdates(updatesArray) {
      shouldShowDotUpdates = shouldShowDotUpdates || updatesArray.length > 0;
      ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory);
  
      if (updatesArray.length > 0) {
          var message;
          if (!ui.notificationInitialCallbackMade[0]) {
              message = updatesArray.length + " of your purchased items " +
                  (updatesArray.length === 1 ? "has an update " : "have updates ") +
                  "available. Open INVENTORY to update.";
              ui.notificationDisplayBanner(message);
  
              ui.notificationPollCaresAboutSince[0] = true;
          } else {
              for (var i = 0; i < updatesArray.length; i++) {
                  message = "Update available for \"" +
                      updatesArray[i].base_item_title + "\"." +
                      "Open INVENTORY to update.";
                  ui.notificationDisplayBanner(message);
              }
          }
      }
  }
  var shouldShowDotHistory = false;
  function notificationPollCallbackHistory(historyArray) {
      if (!ui.isOpen) {
          var notificationCount = historyArray.length;
          shouldShowDotHistory = shouldShowDotHistory || notificationCount > 0;
          ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory);
  
          if (notificationCount > 0) {
              var message;
              if (!ui.notificationInitialCallbackMade[1]) {
                  message = "You have " + notificationCount + " unread recent " +
                      "transaction" + (notificationCount === 1 ? "" : "s") + ". Open INVENTORY to see all activity.";
                  ui.notificationDisplayBanner(message);
              } else {
                  for (var i = 0; i < notificationCount; i++) {
                      var historyMessage = historyArray[i].message;
                      var sanitizedHistoryMessage = historyMessage.replace(/<\/?[^>]+(>|$)/g, "");
                      message = '"' + sanitizedHistoryMessage + '" ' +
                          "Open INVENTORY to see all activity.";
                      ui.notificationDisplayBanner(message);
                  }
              }
          }
      }
  }
  
  function isReturnedDataEmptyUpdates(data) {
      var updatesArray = data.data.updates;
      return updatesArray.length === 0;
  }
  
  function isReturnedDataEmptyHistory(data) {
      var historyArray = data.data.history;
      return historyArray.length === 0;
  }
  
  var DEVELOPER_MENU = "Developer";
  var MARKETPLACE_ITEM_TESTER_LABEL = "Marketplace Item Tester";
  var MARKETPLACE_ITEM_TESTER_QML_SOURCE = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml";
  function installMarketplaceItemTester() {
      if (!Menu.menuExists(DEVELOPER_MENU)) {
          Menu.addMenu(DEVELOPER_MENU);
      }
      if (!Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)) {
          Menu.addMenuItem({
              menuName: DEVELOPER_MENU,
              menuItemName: MARKETPLACE_ITEM_TESTER_LABEL,
              isCheckable: false
          });
      }
  
      Menu.menuItemEvent.connect(function (menuItem) {
          if (menuItem === MARKETPLACE_ITEM_TESTER_LABEL) {
              ui.open(MARKETPLACE_ITEM_TESTER_QML_SOURCE);
          }
      });
  }
  
  function uninstallMarketplaceItemTester() {
      if (Menu.menuExists(DEVELOPER_MENU) &&
          Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)
      ) {
          Menu.removeMenuItem(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL);
      }
  }
  
  var BUTTON_NAME = "INVENTORY";
  var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml";
  var SENDASSET_QML_SOURCE = "hifi/commerce/common/sendAsset/SendAsset.qml";
  var NOTIFICATION_POLL_TIMEOUT = 300000;
  var ui;
  function startup() {
      var notificationPollEndpointArray = ["/api/v1/commerce/available_updates?per_page=10"];
      var notificationPollTimeoutMsArray = [NOTIFICATION_POLL_TIMEOUT];
      var notificationDataProcessPageArray = [notificationDataProcessPageUpdates];
      var notificationPollCallbackArray = [notificationPollCallbackUpdates];
      var notificationPollStopPaginatingConditionMetArray = [isReturnedDataEmptyUpdates];
      var notificationPollCaresAboutSinceArray = [false];
  
      if (!WalletScriptingInterface.limitedCommerce) {
          notificationPollEndpointArray[1] = "/api/v1/commerce/history?per_page=10";
          notificationPollTimeoutMsArray[1] = NOTIFICATION_POLL_TIMEOUT;
          notificationDataProcessPageArray[1] = notificationDataProcessPageHistory;
          notificationPollCallbackArray[1] = notificationPollCallbackHistory;
          notificationPollStopPaginatingConditionMetArray[1] = isReturnedDataEmptyHistory;
          notificationPollCaresAboutSinceArray[1] = true;
      }
  
      ui = new AppUi({
          buttonName: BUTTON_NAME,
          sortOrder: 10,
          home: WALLET_QML_SOURCE,
          additionalAppScreens: SENDASSET_QML_SOURCE,
          onOpened: walletOpened,
          onClosed: walletClosed,
          onMessage: fromQml,
          notificationPollEndpoint: notificationPollEndpointArray,
          notificationPollTimeoutMs: notificationPollTimeoutMsArray,
          notificationDataProcessPage: notificationDataProcessPageArray,
          notificationPollCallback: notificationPollCallbackArray,
          notificationPollStopPaginatingConditionMet: notificationPollStopPaginatingConditionMetArray,
          notificationPollCaresAboutSince: notificationPollCaresAboutSinceArray
      });
      GlobalServices.myUsernameChanged.connect(onUsernameChanged);
      installMarketplaceItemTester();
  }
  
  var isUpdateOverlaysWired = false;
  function off() {
      if (isWired) {
          Users.usernameFromIDReply.disconnect(usernameFromIDReply);
          Controller.mousePressEvent.disconnect(handleMouseEvent);
          Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
          triggerMapping.disable();
          triggerPressMapping.disable();
          isWired = false;
      }
  
      if (isUpdateOverlaysWired) {
          Script.update.disconnect(updateOverlays);
          isUpdateOverlaysWired = false;
      }
      removeOverlays();
  }
  
  function shutdown() {
      GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
      deleteSendMoneyParticleEffect();
      uninstallMarketplaceItemTester();
      off();
  }
  
  //
  // Run the functions.
  //
  startup();
  Script.scriptEnding.connect(shutdown);
  }()); // END LOCAL_SCOPE
  