<commit_before>import { UIViewInjectedProps } from '@uirouter/react';
import { DestinyInventoryItemDefinition } from 'bungie-api-ts/destiny2';
import { t } from 'i18next';
import * as React from 'react';
import { connect } from 'react-redux';
import { Subscription } from 'rxjs/Subscription';
import * as _ from 'lodash';
import { DestinyAccount } from '../accounts/destiny-account.service';
import CharacterDropdown from '../character-select/CharacterDropdown';
import { Loading } from '../dim-ui/Loading';
import { D2StoresService } from '../inventory/d2-stores.service';
import { InventoryBucket, InventoryBuckets } from '../inventory/inventory-buckets';
import { D2Item } from '../inventory/item-types';
import { DimStore } from '../inventory/store-types';
import { DestinyTrackerService } from '../item-review/destiny-tracker.service';
import { RootState } from '../store/reducers';
import GeneratedSets from './generated-sets/GeneratedSets';
import { filterPlugs } from './generated-sets/utils';
import './loadoutbuilder.scss';
import LockedArmor from './locked-armor/LockedArmor';
import startNewProcess from './process';
import { ArmorSet, LockableBuckets, LockedItemType } from './types';

interface ProvidedProps {
  account: DestinyAccount;
}

interface StoreProps {
  storesLoaded: boolean;
  stores: DimStore[];
  buckets: InventoryBuckets;
}

type Props = ProvidedProps & StoreProps;

interface State {
  processRunning: number;
  requirePerks: boolean;
  requireBurn: 'none' | 'arc' | 'solar' | 'void';
  lockedMap: { [bucketHash: number]: LockedItemType[] };
  processedSets: ArmorSet[];
  selectedStore?: DimStore;
  trackerService?: DestinyTrackerService;
}

const burnTypes = {
  none: 'BurnTypeNone',
  arc: 'BurnTypeArc',
  solar: 'BurnTypeSolar',
  void: 'BurnTypeVoid'
};

const perks: {
  [classType: number]: { [bucketHash: number]: any };
} = {};
const items: {
  [classType: number]: { [bucketHash: number]: { [itemHash: number]: D2Item[] } };
} = {};

function mapStateToProps(state: RootState): StoreProps {
  return {
    buckets: state.inventory.buckets!,
    storesLoaded: state.inventory.stores.length > 0,
    stores: state.inventory.stores
  };
}

/**
 * The Loadout Builder screen
 */
export class LoadoutBuilder extends React.Component<Props & UIViewInjectedProps, State> {
  private storesSubscription: Subscription;

  constructor(props: Props) {
    super(props);
    this.state = {
      requirePerks: true,
      requireBurn: 'none',
      processRunning: 0,
      lockedMap: {},
      processedSets: []
    };
  }

  componentDidMount() {
    this.storesSubscription = D2StoresService.getStoresStream(this.props.account).subscribe(
      (stores) => {
        if (!stores) {
          return;
        }

        this.setState({ selectedStore: stores.find((s) => s.current) });
        for (const store of stores) {
          for (const item of store.items) {
            if (
              !item ||
              !item.sockets ||
              !item.bucket.inArmor ||
              !['Exotic', 'Legendary'].includes(item.tier)
            ) {
              continue;
            }
            if (!perks[item.classType]) {
              perks[item.classType] = {};
              items[item.classType] = {};
            }
            if (!perks[item.classType][item.bucket.hash]) {
              perks[item.classType][item.bucket.hash] = new Set<DestinyInventoryItemDefinition>();
              items[item.classType][item.bucket.hash] = [];
            }

            if (!items[item.classType][item.bucket.hash][item.hash]) {
              items[item.classType][item.bucket.hash][item.hash] = [];
            }
            items[item.classType][item.bucket.hash][item.hash].push(item);

            // build the filtered unique perks item picker
            item.sockets.categories.length === 2 &&
              item.sockets.categories[0].sockets.filter(filterPlugs).forEach((socket) => {
                socket!.plugOptions.forEach((option) => {
                  perks[item.classType][item.bucket.hash].add(option.plugItem);
                });
              });
          }
        }

        // sort exotic perks first, then by index
        Object.keys(perks).forEach((classType) =>
          Object.keys(perks[classType]).forEach((bucket) =>
            (perks[classType][bucket] = [...perks[classType][bucket]].sort(
              (a, b) => b.index - a.index
            )).sort((a, b) => b.inventory.tierType - a.inventory.tierType)
          )
        );

        if (!this.state.selectedStore) {
          this.onCharacterChanged(stores.find((s) => s.current)!.id);
        } else {
          const selectedStore = stores.find((s) => s.id === this.state.selectedStore!.id)!;
          this.setState({ selectedStore });
          this.computeSets({ classType: selectedStore.classType });
        }
      }
    );
  }

  componentWillUnmount() {
    this.storesSubscription.unsubscribe();
  }

  /**
   * This function should be fired any time that a configuration option changes
   *
   * The work done in this function is to filter down items to process based on what is locked
   */
  computeSets = ({
    classType = this.state.selectedStore!.classType,
    lockedMap = this.state.lockedMap,
    requirePerks = this.state.requirePerks,
    requireBurn = this.state.requireBurn
  }: {
    classType?: number;
    lockedMap?: { [bucketHash: number]: LockedItemType[] };
    requirePerks?: boolean;
    requireBurn?: string;
  }) => {
    const allItems = { ...items[classType] };
    const filteredItems: { [bucket: number]: D2Item[] } = {};

    Object.keys(allItems).forEach((bucketStr) => {
      const bucket = parseInt(bucketStr, 10);

      // if we are locking an item in that bucket, filter to only those items
      if (lockedMap[bucket] && lockedMap[bucket][0].type === 'item') {
        filteredItems[bucket] = [lockedMap[bucket][0].item as D2Item];
        return;
      }

      // otherwise flatten all item instances to each bucket
      filteredItems[bucket] = _.flatten(
        Object.values(allItems[bucket]).map((items) => {
          if (!lockedMap[bucket]) {
            return items.reduce((a, b) => (a.basePower > b.basePower ? a : b));
          }
          return items;
        })
      );

      // filter out items without extra perks on them
      if (requirePerks) {
        filteredItems[bucket] = filteredItems[bucket].filter((item) => {
          if (
            item &&
            item.sockets &&
            item.sockets.categories &&
            item.sockets.categories.length === 2
          ) {
            return item.sockets.categories[0].sockets.filter(filterPlugs).length;
          }
        });
      }

      // filter out items that do not match the burn type
      if (requireBurn !== 'none') {
        filteredItems[bucket] = filteredItems[bucket].filter((item: D2Item) => {
          return item && item.dmg === requireBurn;
        });<commit_msg>clarification in comments about how items are filtered down (pre-process)<commit_after>import { UIViewInjectedProps } from '@uirouter/react';
import { DestinyInventoryItemDefinition } from 'bungie-api-ts/destiny2';
import { t } from 'i18next';
import * as React from 'react';
import { connect } from 'react-redux';
import { Subscription } from 'rxjs/Subscription';
import * as _ from 'lodash';
import { DestinyAccount } from '../accounts/destiny-account.service';
import CharacterDropdown from '../character-select/CharacterDropdown';
import { Loading } from '../dim-ui/Loading';
import { D2StoresService } from '../inventory/d2-stores.service';
import { InventoryBucket, InventoryBuckets } from '../inventory/inventory-buckets';
import { D2Item } from '../inventory/item-types';
import { DimStore } from '../inventory/store-types';
import { DestinyTrackerService } from '../item-review/destiny-tracker.service';
import { RootState } from '../store/reducers';
import GeneratedSets from './generated-sets/GeneratedSets';
import { filterPlugs } from './generated-sets/utils';
import './loadoutbuilder.scss';
import LockedArmor from './locked-armor/LockedArmor';
import startNewProcess from './process';
import { ArmorSet, LockableBuckets, LockedItemType } from './types';

interface ProvidedProps {
  account: DestinyAccount;
}

interface StoreProps {
  storesLoaded: boolean;
  stores: DimStore[];
  buckets: InventoryBuckets;
}

type Props = ProvidedProps & StoreProps;

interface State {
  processRunning: number;
  requirePerks: boolean;
  requireBurn: 'none' | 'arc' | 'solar' | 'void';
  lockedMap: { [bucketHash: number]: LockedItemType[] };
  processedSets: ArmorSet[];
  selectedStore?: DimStore;
  trackerService?: DestinyTrackerService;
}

const burnTypes = {
  none: 'BurnTypeNone',
  arc: 'BurnTypeArc',
  solar: 'BurnTypeSolar',
  void: 'BurnTypeVoid'
};

const perks: {
  [classType: number]: { [bucketHash: number]: any };
} = {};
const items: {
  [classType: number]: { [bucketHash: number]: { [itemHash: number]: D2Item[] } };
} = {};

function mapStateToProps(state: RootState): StoreProps {
  return {
    buckets: state.inventory.buckets!,
    storesLoaded: state.inventory.stores.length > 0,
    stores: state.inventory.stores
  };
}

/**
 * The Loadout Builder screen
 */
export class LoadoutBuilder extends React.Component<Props & UIViewInjectedProps, State> {
  private storesSubscription: Subscription;

  constructor(props: Props) {
    super(props);
    this.state = {
      requirePerks: true,
      requireBurn: 'none',
      processRunning: 0,
      lockedMap: {},
      processedSets: []
    };
  }

  componentDidMount() {
    this.storesSubscription = D2StoresService.getStoresStream(this.props.account).subscribe(
      (stores) => {
        if (!stores) {
          return;
        }

        this.setState({ selectedStore: stores.find((s) => s.current) });
        for (const store of stores) {
          for (const item of store.items) {
            if (
              !item ||
              !item.sockets ||
              !item.bucket.inArmor ||
              !['Exotic', 'Legendary'].includes(item.tier)
            ) {
              continue;
            }
            if (!perks[item.classType]) {
              perks[item.classType] = {};
              items[item.classType] = {};
            }
            if (!perks[item.classType][item.bucket.hash]) {
              perks[item.classType][item.bucket.hash] = new Set<DestinyInventoryItemDefinition>();
              items[item.classType][item.bucket.hash] = [];
            }

            if (!items[item.classType][item.bucket.hash][item.hash]) {
              items[item.classType][item.bucket.hash][item.hash] = [];
            }
            items[item.classType][item.bucket.hash][item.hash].push(item);

            // build the filtered unique perks item picker
            item.sockets.categories.length === 2 &&
              item.sockets.categories[0].sockets.filter(filterPlugs).forEach((socket) => {
                socket!.plugOptions.forEach((option) => {
                  perks[item.classType][item.bucket.hash].add(option.plugItem);
                });
              });
          }
        }

        // sort exotic perks first, then by index
        Object.keys(perks).forEach((classType) =>
          Object.keys(perks[classType]).forEach((bucket) =>
            (perks[classType][bucket] = [...perks[classType][bucket]].sort(
              (a, b) => b.index - a.index
            )).sort((a, b) => b.inventory.tierType - a.inventory.tierType)
          )
        );

        if (!this.state.selectedStore) {
          this.onCharacterChanged(stores.find((s) => s.current)!.id);
        } else {
          const selectedStore = stores.find((s) => s.id === this.state.selectedStore!.id)!;
          this.setState({ selectedStore });
          this.computeSets({ classType: selectedStore.classType });
        }
      }
    );
  }

  componentWillUnmount() {
    this.storesSubscription.unsubscribe();
  }

  /**
   * This function should be fired any time that a configuration option changes
   *
   * The work done in this function is to filter down items to process based on what is locked
   */
  computeSets = ({
    classType = this.state.selectedStore!.classType,
    lockedMap = this.state.lockedMap,
    requirePerks = this.state.requirePerks,
    requireBurn = this.state.requireBurn
  }: {
    classType?: number;
    lockedMap?: { [bucketHash: number]: LockedItemType[] };
    requirePerks?: boolean;
    requireBurn?: string;
  }) => {
    const allItems = { ...items[classType] };
    const filteredItems: { [bucket: number]: D2Item[] } = {};

    Object.keys(allItems).forEach((bucketStr) => {
      const bucket = parseInt(bucketStr, 10);

      // if we are locking an item in that bucket, filter to only include that single item
      if (lockedMap[bucket] && lockedMap[bucket][0].type === 'item') {
        filteredItems[bucket] = [lockedMap[bucket][0].item as D2Item];
        return;
      }

      // otherwise flatten all item instances to each bucket
      filteredItems[bucket] = _.flatten(
        Object.values(allItems[bucket]).map((items) => {
          // if nothing is locked in the current bucket
          if (!lockedMap[bucket]) {
            // pick the item instance with the highest power
            return items.reduce((a, b) => (a.basePower > b.basePower ? a : b));
          }
          // otherwise, return all item instances (and then filter down later by perks)
          return items;
        })
      );

      // filter out items without extra perks on them
      if (requirePerks) {
        filteredItems[bucket] = filteredItems[bucket].filter((item) => {
          if (
            item &&
            item.sockets &&
            item.sockets.categories &&
            item.sockets.categories.length === 2
          ) {
            return item.sockets.categories[0].sockets.filter(filterPlugs).length;
          }
        });
      }

      // filter out items that do not match the burn type
      if (requireBurn !== 'none') {
        filteredItems[bucket] = filteredItems[bucket].filter((item: D2Item) => {
          return item && item.dmg === requireBurn;
        });
{'fraction_non_alphanumeric': 0.13917089678511, 'fraction_numerical': 0.002961082910321489, 'mean_word_length': 2.5802624936900553, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 43, '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': '5386765', 'n_tokens_mistral': 4181, 'n_tokens_neox': 4089, 'n_words': 895}