
import { select } from 'weighted-randomly-select';
import * as random from 'lodash.random';

export enum LootFunction {
  WithReplacement = 'chooseWithReplacement',
  WithoutReplacement = 'chooseWithoutReplacement',
  EachItem = 'tryToDropEachItem'
}

export interface LootRollerConfig {
  table: LootTable;
  func: LootFunction;
  args: number;
}

export interface LootTableOptions {
  rollModifier: number;
  seed?: number
}

export interface Rollable {
  result: string;
  chance: number;
  
  maxChance?: number;
}

export class LootRoller {

    static rollTables(lootTableConfigs: LootRollerConfig[]) {
        const items = [];
        lootTableConfigs.forEach(({ table, func, args }) => {
            items.push(...table[func](args));
        });
        return items;
    }

    static rollTable(lootTableConfig: LootRollerConfig) {
        return this.rollTables([lootTableConfig]);
    }

};

export class LootTable {

  private choices: Rollable[];
  private rollModifier: number;

  constructor(choices: string[]|Rollable[], { rollModifier }: LootTableOptions = { rollModifier: 0 }) {
    this.choices = this.configureChoices(choices);
    this.rollModifier = rollModifier || 0;
  }

  private getFormattedChoices(): { alwaysDrop: string[], choices: Rollable[] } {
    const alwaysDrop = [];
    const choices = this.choices.map(x => {

      // if the chance < -1, it always drops
      if(x.chance <= 0) {
        alwaysDrop.push(x.result);
        return null;
      }

      // if there is for some reason a negative total roll, this item can never drop
      const totalChance = x.chance + this.rollModifier;
      if(totalChance <= 0) {
        return null;
      }
      
      return { result: x.result, chance: totalChance, maxChance: x.maxChance };
    }).filter(Boolean);

    return { alwaysDrop, choices };
  }

  private configureChoices(choices: string[]|Rollable[]): Rollable[] {
    // pre-formatted with weights
    if((choices[0] as Rollable).chance) return choices as Rollable[];

    // assign a weight of 1 to everything
    return (choices as string[]).map(item => ({ result: item, chance: 1 }));
  }

  public chooseWithReplacement(numItems = 1): string[] {
    const { choices, alwaysDrop } = this.getFormattedChoices();
    return alwaysDrop.concat(
      Array(numItems).fill(null).map(() => select(choices))
    );
  }

  public chooseWithoutReplacement(numItems = 1): string[] {
    const { choices, alwaysDrop } = this.getFormattedChoices();

    if(numItems > choices.length) throw new Error(`Cannot choose ${numItems} without replacement when array is only ${choices.length} choices.`);

    return alwaysDrop.concat(
      Array(numItems).fill(null).map(() => {

        // choose something
        const choice = select(choices);

        // remove old choice
        choices.splice(choices.indexOf(choice), 1);

        return choice;
      })
    );
  }

  public tryToDropEachItem(comparisonValue: number): string[] {
    const { choices, alwaysDrop } = this.getFormattedChoices();

    return alwaysDrop.concat(
      choices.map(({ result, chance, maxChance }) => {
        const randomValue = random(0, maxChance || comparisonValue || 1);
        if(chance >= randomValue) return result;
        return null;
      }).filter(Boolean)
    );
  }
};