import combineStat from '/imports/api/creature/computation/engine/combineStat.js';
import computeEffect from '/imports/api/creature/computation/engine/computeEffect.js';
import EffectAggregator from '/imports/api/creature/computation/engine/EffectAggregator.js';
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
import { each, union, without } from 'lodash';

export default function computeStat(stat, memo){
  // If the stat is already computed, skip it
  if (stat.computationDetails.computed) return;
  if (stat.computationDetails.busyComputing){
    // Trying to compute this stat again while it is already computing.
    // We must be in a dependency loop.
    stat.computationDetails.computed = true;
    stat.value = NaN;
    stat.computationDetails.busyComputing = false;
    stat.computationDetails.error = 'dependencyLoop';
    if (Meteor.isClient) console.warn('dependencyLoop', stat);
    return;
  }
  // Before doing any work, mark this stat as busy
  stat.computationDetails.busyComputing = true;

  let effects = stat.computationDetails.effects || [];
  let proficiencies = stat.computationDetails.proficiencies || [];

  // Get references to all the stats that share the variable name
  let sameNameStats

  if (stat.computationDetails.idsOfSameName){
    sameNameStats = stat.computationDetails.idsOfSameName.map(
      id => memo.propsById[id]
    );
  } else {
    sameNameStats = [];
  }

  let allStats = [stat, ...sameNameStats];

  // Decide which stat is the last active stat
  // The last active stat is considered the cannonical stat
  let lastActiveStat;
  allStats.forEach(candidateStat => {
    applyToggles(candidateStat, memo);
    if (!candidateStat.inactive) lastActiveStat = candidateStat;
    candidateStat.overridden = undefined;
  });
  if (!lastActiveStat){
    delete memo.statsByVariableName[stat.variableName];
    return;
  }
  // Make sure the active stat has all the effects and proficiencies
  lastActiveStat.computationDetails.effects = effects;
  lastActiveStat.computationDetails.proficiencies = proficiencies;

  // Update the memo's stat with the chosen stat
  memo.statsByVariableName[stat.variableName] = lastActiveStat;

  // Recreate list of the non-cannonical stats
  sameNameStats = without(allStats, lastActiveStat);

  sameNameStats.forEach(statInstance => {
    // Mark the non-cannonical stats as overridden
    statInstance.overridden = true;

    // Apply the cannonical damage
    statInstance.damage = lastActiveStat.damage;
  });

  let baseDependencies = [];
  allStats.forEach(statInstance => {
    // Add this stat and its deps to the dependencies
    baseDependencies = union(
      baseDependencies,
      [statInstance._id],
      statInstance.dependencies,
    );

    // Apply all the base proficiencies
    if (statInstance.baseProficiency && !statInstance.inactive){
      proficiencies.push({
        value: statInstance.baseProficiency,
        stats: [statInstance.variableName],
        type: 'proficiency',
        dependencies: statInstance.overridden ?
          union(statInstance.dependencies, [statInstance._id]) :
          [],
        computationDetails: {
          computed: true,
        }
      });
    }

    // Compute each active stat's baseValue calculation and apply it
    if (!statInstance.inactive) {
      let {
        result,
        context,
        dependencies
      } = evaluateCalculation({
        string: statInstance.baseValueCalculation,
        prop: statInstance,
        memo
      });
      result.value = +result.value;
      if (!isNaN(result.value)){
        statInstance.baseValue = result.value;
      } else {
        statInstance.baseValue = undefined;
      }
      statInstance.dependencies = union(statInstance.dependencies, dependencies);
      if (context.errors.length){
        statInstance.baseValueErrors = context.errors;
      }
      // Apply all the base values
      if (Number.isFinite(statInstance.baseValue)){
        effects.push({
          operation: 'base',
          calculation: statInstance.baseValueCalculation,
          result: statInstance.baseValue,
          stats: [statInstance.variableName],
          dependencies: statInstance.overridden ?
          union(statInstance.dependencies, [statInstance._id]) :
          [],
          computationDetails: {
            computed: true,
          },
        });
      }
    }
  });

  // Compute and aggregate all the effects
  let aggregator = new EffectAggregator();
  let effectDeps = [];
  each(effects, (effect) => {
    // Compute
    computeEffect(effect, memo);
    if (effect.deactivatedByToggle) return;

    // dependencies
    if (effect._id) effectDeps = union(effectDeps, [effect._id]);
    effectDeps = union(effectDeps, effect.dependencies);

    // Add computed effect to aggregator
    aggregator.addEffect(effect);
  });

  // Combine the effects into the stats
  allStats.forEach(statInstance => {
    // Conglomerate all the effects to compute the final stat values
    combineStat(statInstance, aggregator, memo);
    // Mark the stats as computed
    statInstance.computationDetails.computed = true;
    statInstance.computationDetails.busyComputing = false;
    // Only the active stat instance depeneds on the effects
    if (!statInstance.overridden){
      statInstance.dependencies = union(statInstance.dependencies, effectDeps);
    }
  });
}
