<commit_before>import { IPromise } from 'angular';
import copy from 'fast-copy';
import * as _ from 'lodash';
import { DimError } from '../bungie-api/bungie-service-helper';
import {
  equip as d1equip,
  equipItems as d1EquipItems,
  transfer as d1Transfer
} from '../bungie-api/destiny1-api';
import {
  equip as d2equip,
  equipItems as d2EquipItems,
  transfer as d2Transfer
} from '../bungie-api/destiny2-api';
import { chainComparator, compareBy, reverseComparator } from '../comparators';
import { createItemIndex as d2CreateItemIndex } from './store/d2-item-factory.service';
import { createItemIndex as d1CreateItemIndex } from './store/d1-item-factory.service';
import { DimItem } from './item-types';
import { DimStore } from './store-types';
import { D1StoresService } from './d1-stores.service';
import { D2StoresService } from './d2-stores.service';
import { t } from 'i18next';
import { $q } from 'ngimport';
import { shallowCopy } from '../util';

/**
 * You can reserve a number of each type of item in each store.
 */
export interface MoveReservations {
  [storeId: number]: {
    [type: string]: number;
  };
}

export interface ItemServiceType {
  getSimilarItem(
    item: DimItem,
    exclusions?: Partial<DimItem>[],
    excludeExotic?: boolean
  ): DimItem | null;
  /**
   * Move item to target store, optionally equipping it.
   * @param item the item to move.
   * @param target the store to move it to.
   * @param equip true to equip the item, false to leave it unequipped.
   * @param amount how much of the item to move (for stacks). Can span more than one stack's worth.
   * @param excludes A list of {id, hash} objects representing items that should not be moved aside to make the move happen.
   * @param reservations A map of store id to the amount of space to reserve in it for items like "item".
   * @return A promise for the completion of the whole sequence of moves, or a rejection if the move cannot complete.
   */
  moveTo(
    item: DimItem,
    target: DimStore,
    equip?: boolean,
    amount?: number,
    excludes?: { id: string; hash: number }[],
    reservations?: MoveReservations
  ): IPromise<DimItem>;
  /**
   * Bulk equip items. Only use for multiple equips at once.
   */
  equipItems(store: DimStore, items: DimItem[]): IPromise<DimItem[]>;
}

export const dimItemService = ItemService();

/**
 * A service for moving/equipping items. dimItemMoveService should be preferred for most usages.
 */
function ItemService(): ItemServiceType {
  // We'll reload the stores to check if things have been
  // thrown away or moved and we just don't have up to date info. But let's
  // throttle these calls so we don't just keep refreshing over and over.
  // This needs to be up here because of how we return the service object.
  const throttledReloadStores = _.throttle(
    () => {
      return D1StoresService.reloadStores();
    },
    10000,
    { trailing: false }
  );

  const throttledD2ReloadStores = _.throttle(
    () => {
      return D2StoresService.reloadStores();
    },
    10000,
    { trailing: false }
  );

  return {
    getSimilarItem,
    moveTo,
    equipItems
  };

  function equipApi(item: DimItem): (item: DimItem) => IPromise<any> {
    return item.destinyVersion === 2 ? d2equip : d1equip;
  }

  function equipItemsApi(
    item: DimItem
  ): (store: DimStore, items: DimItem[]) => IPromise<DimItem[]> {
    return item.destinyVersion === 2 ? d2EquipItems : d1EquipItems;
  }

  function transferApi(
    item: DimItem
  ): (item: DimItem, store: DimStore, amount: number) => IPromise<any> {
    return item.destinyVersion === 2 ? d2Transfer : d1Transfer;
  }

  function createItemIndex(item: DimItem): string {
    if (item.isDestiny2()) {
      return d2CreateItemIndex(item);
    } else if (item.isDestiny1()) {
      return d1CreateItemIndex(item);
    } else {
      throw new Error('Destiny 3??');
    }
  }

  /**
   * Update our item and store models after an item has been moved (or equipped/dequipped).
   * @return the new or updated item (it may create a new item!)
   */
  function updateItemModel(
    item: DimItem,
    source: DimStore,
    target: DimStore,
    equip: boolean,
    amount: number = item.amount
  ) {
    // Refresh all the items - they may have been reloaded!
    const storeService = item.getStoresService();
    source = storeService.getStore(source.id)!;
    target = storeService.getStore(target.id)!;
    // We really shouldn't do this!
    item = storeService.getItemAcrossStores(item) || item;

    // If we've moved to a new place
    if (source.id !== target.id || item.location.inPostmaster) {
      // We handle moving stackable and nonstackable items almost exactly the same!
      const stackable = item.maxStackSize > 1;
      // Items to be decremented
      const sourceItems = stackable
        ? _.sortBy(
            source.buckets[item.location.id].filter((i) => {
              return i.hash === item.hash && i.id === item.id && !i.notransfer;
            }),
            (i) => i.amount
          )
        : [item];
      // Items to be incremented. There's really only ever at most one of these, but
      // it's easier to deal with as a list.
      const targetItems = stackable
        ? _.sortBy(
            target.buckets[item.bucket.id].filter((i) => {
              return (
                i.hash === item.hash &&
                i.id === item.id &&
                // Don't consider full stacks as targets
                i.amount !== i.maxStackSize &&
                !i.notransfer
              );
            }),
            (i) => i.amount
          )
        : [];
      // moveAmount could be more than maxStackSize if there is more than one stack on a character!
      const moveAmount = amount || item.amount;
      let addAmount = moveAmount;
      let removeAmount = moveAmount;
      let removedSourceItem = false;

      // Remove inventory from the source
      while (removeAmount > 0) {
        let sourceItem = sourceItems.shift();
        if (!sourceItem) {
          throw new Error(t('ItemService.TooMuch'));
        }

        const amountToRemove = Math.min(removeAmount, sourceItem.amount);
        if (amountToRemove === sourceItem.amount) {
          // Completely remove the source item
          if (source.removeItem(sourceItem)) {
            removedSourceItem = sourceItem.index === item.index;
          }
        } else {
          // Perf hack: by replacing the item entirely with a cloned
          // item that has an adjusted index, we force the ng-repeat
          // to refresh its view of the item, updating the
          // amount. This is because we've switched to bind-once for
          // the amount since it rarely changes.
          source.removeItem(sourceItem);
          sourceItem = copy(sourceItem);
          sourceItem.amount -= amountToRemove;
          sourceItem.index = createItemIndex(sourceItem);
          source.addItem(sourceItem);
        }<commit_msg>Guard just a tiny bit<commit_after>import { IPromise } from 'angular';
import copy from 'fast-copy';
import * as _ from 'lodash';
import { DimError } from '../bungie-api/bungie-service-helper';
import {
  equip as d1equip,
  equipItems as d1EquipItems,
  transfer as d1Transfer
} from '../bungie-api/destiny1-api';
import {
  equip as d2equip,
  equipItems as d2EquipItems,
  transfer as d2Transfer
} from '../bungie-api/destiny2-api';
import { chainComparator, compareBy, reverseComparator } from '../comparators';
import { createItemIndex as d2CreateItemIndex } from './store/d2-item-factory.service';
import { createItemIndex as d1CreateItemIndex } from './store/d1-item-factory.service';
import { DimItem } from './item-types';
import { DimStore } from './store-types';
import { D1StoresService } from './d1-stores.service';
import { D2StoresService } from './d2-stores.service';
import { t } from 'i18next';
import { $q } from 'ngimport';
import { shallowCopy } from '../util';

/**
 * You can reserve a number of each type of item in each store.
 */
export interface MoveReservations {
  [storeId: number]: {
    [type: string]: number;
  };
}

export interface ItemServiceType {
  getSimilarItem(
    item: DimItem,
    exclusions?: Partial<DimItem>[],
    excludeExotic?: boolean
  ): DimItem | null;
  /**
   * Move item to target store, optionally equipping it.
   * @param item the item to move.
   * @param target the store to move it to.
   * @param equip true to equip the item, false to leave it unequipped.
   * @param amount how much of the item to move (for stacks). Can span more than one stack's worth.
   * @param excludes A list of {id, hash} objects representing items that should not be moved aside to make the move happen.
   * @param reservations A map of store id to the amount of space to reserve in it for items like "item".
   * @return A promise for the completion of the whole sequence of moves, or a rejection if the move cannot complete.
   */
  moveTo(
    item: DimItem,
    target: DimStore,
    equip?: boolean,
    amount?: number,
    excludes?: { id: string; hash: number }[],
    reservations?: MoveReservations
  ): IPromise<DimItem>;
  /**
   * Bulk equip items. Only use for multiple equips at once.
   */
  equipItems(store: DimStore, items: DimItem[]): IPromise<DimItem[]>;
}

export const dimItemService = ItemService();

/**
 * A service for moving/equipping items. dimItemMoveService should be preferred for most usages.
 */
function ItemService(): ItemServiceType {
  // We'll reload the stores to check if things have been
  // thrown away or moved and we just don't have up to date info. But let's
  // throttle these calls so we don't just keep refreshing over and over.
  // This needs to be up here because of how we return the service object.
  const throttledReloadStores = _.throttle(
    () => {
      return D1StoresService.reloadStores();
    },
    10000,
    { trailing: false }
  );

  const throttledD2ReloadStores = _.throttle(
    () => {
      return D2StoresService.reloadStores();
    },
    10000,
    { trailing: false }
  );

  return {
    getSimilarItem,
    moveTo,
    equipItems
  };

  function equipApi(item: DimItem): (item: DimItem) => IPromise<any> {
    return item.destinyVersion === 2 ? d2equip : d1equip;
  }

  function equipItemsApi(
    item: DimItem
  ): (store: DimStore, items: DimItem[]) => IPromise<DimItem[]> {
    return item.destinyVersion === 2 ? d2EquipItems : d1EquipItems;
  }

  function transferApi(
    item: DimItem
  ): (item: DimItem, store: DimStore, amount: number) => IPromise<any> {
    return item.destinyVersion === 2 ? d2Transfer : d1Transfer;
  }

  function createItemIndex(item: DimItem): string {
    if (item.isDestiny2()) {
      return d2CreateItemIndex(item);
    } else if (item.isDestiny1()) {
      return d1CreateItemIndex(item);
    } else {
      throw new Error('Destiny 3??');
    }
  }

  /**
   * Update our item and store models after an item has been moved (or equipped/dequipped).
   * @return the new or updated item (it may create a new item!)
   */
  function updateItemModel(
    item: DimItem,
    source: DimStore,
    target: DimStore,
    equip: boolean,
    amount: number = item.amount
  ) {
    // Refresh all the items - they may have been reloaded!
    const storeService = item.getStoresService();
    source = storeService.getStore(source.id)!;
    target = storeService.getStore(target.id)!;
    // We really shouldn't do this!
    item = storeService.getItemAcrossStores(item) || item;

    // If we've moved to a new place
    if (source.id !== target.id || item.location.inPostmaster) {
      // We handle moving stackable and nonstackable items almost exactly the same!
      const stackable = item.maxStackSize > 1;
      // Items to be decremented
      const sourceItems = stackable
        ? _.sortBy(
            source.buckets[item.location.id].filter((i) => {
              return i.hash === item.hash && i.id === item.id && !i.notransfer;
            }),
            (i) => i.amount
          )
        : [item];
      // Items to be incremented. There's really only ever at most one of these, but
      // it's easier to deal with as a list.
      const targetItems = stackable
        ? _.sortBy(
            target.buckets[item.bucket.id].filter((i) => {
              return (
                i.hash === item.hash &&
                i.id === item.id &&
                // Don't consider full stacks as targets
                i.amount !== i.maxStackSize &&
                !i.notransfer
              );
            }),
            (i) => i.amount
          )
        : [];
      // moveAmount could be more than maxStackSize if there is more than one stack on a character!
      const moveAmount = amount || item.amount || 1;
      let addAmount = moveAmount;
      let removeAmount = moveAmount;
      let removedSourceItem = false;

      // Remove inventory from the source
      while (removeAmount > 0) {
        let sourceItem = sourceItems.shift();
        if (!sourceItem) {
          throw new Error(t('ItemService.TooMuch'));
        }

        const amountToRemove = Math.min(removeAmount, sourceItem.amount);
        if (amountToRemove === sourceItem.amount) {
          // Completely remove the source item
          if (source.removeItem(sourceItem)) {
            removedSourceItem = sourceItem.index === item.index;
          }
        } else {
          // Perf hack: by replacing the item entirely with a cloned
          // item that has an adjusted index, we force the ng-repeat
          // to refresh its view of the item, updating the
          // amount. This is because we've switched to bind-once for
          // the amount since it rarely changes.
          source.removeItem(sourceItem);
          sourceItem = copy(sourceItem);
          sourceItem.amount -= amountToRemove;
          sourceItem.index = createItemIndex(sourceItem);
          source.addItem(sourceItem);
        }
{'fraction_non_alphanumeric': 0.1089840108984011, 'fraction_numerical': 0.0068115006811500685, 'mean_word_length': 2.59021879021879, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2640059', 'n_tokens_mistral': 4144, 'n_tokens_neox': 4029, 'n_words': 1363}