import computeStat from '/imports/api/creature/computation/computeStat.js';
import math from '/imports/math.js';

/* Convert a calculation into a constant output and errors*/
export default function evaluateCalculation(string, memo){
  if (!string) return string;
  let errors = [];
  // Parse the string using mathjs
  let calc;
  try {
    calc = math.parse(string);
  } catch (e) {
    errors.push({
      type: 'parsing',
      message: e.message || e
    });
    return {errors, value: string};
  }
  // Ensure all symbol nodes are defined and coputed
  calc.traverse(node => {
    if (node.isSymbolNode){
      let stat = memo.statsByVariableName[node.name];
      if (stat && !stat.computationDetails.computed){
        computeStat(stat, memo);
      }
    }
  });
  // Replace all symbols with their subtitution
  let substitutedCalc = calc.transform(
    symbolSubtitutor(memo.statsByVariableName, errors)
  );
  // Evaluate the expression to a number or return with substitutions
  try {
    let value = substitutedCalc.evaluate(memo.statsByVariableName);
    if (typeof value === 'object') value = value.toString();
    return {errors, value};
  } catch (e){
    errors.push({
      type: 'evaluation',
      message: e.message || e
    });
    let value = substitutedCalc.toString();
    return {errors, value};
  }
}

// returns a function to replace all symbols with either their resolved value
// or zero, keeping the errors
function symbolSubtitutor(scope, errors){
  return function(node){
    // mark symbol nodes that are children of function nodes to be skipped
    if (node.isFunctionNode){
      let fn = node.fn;
      if (fn && fn.isSymbolNode){
        fn.skipReplacement = true;
      }
      return node;
    } else if (node.isSymbolNode && node.skipReplacement !== true){
      //bare symbols of name "stat", should search for stat.value
      let stat = scope[node.name];
      if (stat){
        if (stat.value === undefined){
          errors.push({
            type: 'subsitution',
            message: `${node.name} does not have a value, set to 0`
          });
          return new math.ConstantNode(0);
        } else {
          return new math.ConstantNode(stat.value);
        }
      } else {
        try {
          return new math.ConstantNode(node.evaluate(scope));
        } catch (e) {
          errors.push({
            type: 'subsitution',
            message: `${node.name} not found, set to 0`
          });
          return new math.ConstantNode(0);
        }
      }
    } else if (node.isAccessorNode){
      try {
        let value = node.evaluate(scope);
        if (value === undefined) throw 'Not found';
        return new math.ConstantNode(value);
      } catch (e) {
        errors.push({
          type: 'subsitution',
          message: `${node.toString()} not found, set to 0`
        });
        return new math.ConstantNode(0);
      }
    } else {
      return node;
    }
  }
}
