import { comparators } from '../lib/comparators';
import guid from '../../utils/guid';

const EQUALS_REGEXP = /^equals$/;

/**
 * This Class represents a Rule to be evaluated given a set of attributes
 * Rules have:
 * - An attribute (e.g. 'seriesDescription')
 * - A constraint Object, in the form required by Validate.js:
 *
 * rule.constraint = {
 *   contains: {
 *      value: 'T-1'
 *      }
 *   };
 *
 *  Note: In this example we use the 'contains' Validator, which is a custom Validator defined in Viewerbase
 *
 * - A value for whether or not they are Required to be matched (default: False)
 * - A value for their relative weighting during Protocol or Image matching (default: 1)
 */
export default class Rule {
  /**
   * The Constructor for the Class to create a Rule with the bare
   * minimum information
   *
   * @param name The desired name for the Rule
   */
  constructor(attribute, constraint, required, weight) {
    // Create a new UUID for this Rule
    this.id = guid();

    // Set the Rule's weight (defaults to 1)
    this.weight = weight || 1;

    // If an attribute is specified, assign it
    if (attribute) {
      this.attribute = attribute;
    }

    // If a constraint is specified, assign it
    if (constraint) {
      this.constraint = constraint;
    }

    // If a value for 'required' is specified, assign it
    if (required === undefined) {
      // If no value was specified, default to False
      this.required = false;
    } else {
      this.required = required;
    }

    // Cache for constraint info object
    this._constraintInfo = void 0;

    // Cache for validator and value object
    this._validatorAndValue = void 0;
  }

  /**
   * Occasionally the Rule class needs to be instantiated from a JavaScript Object.
   * This function fills in a Protocol with the Object data.
   *
   * @param input A Rule as a JavaScript Object, e.g. retrieved from JSON
   */
  fromObject(input) {
    // Check if the input already has an ID
    // If so, keep it. It not, create a new UUID
    this.id = input.id || guid();

    // Assign the specified input data to the Rule
    this.required = input.required;
    this.weight = input.weight;
    this.attribute = input.attribute;
    this.constraint = input.constraint;
  }

  /**
   * Get the constraint info object for the current constraint
   * @return {Object\undefined} Constraint object or undefined if current constraint
   *                            is not valid or not found in comparators list
   */
  getConstraintInfo() {
    let constraintInfo = this._constraintInfo;
    // Check if info is cached already
    if (constraintInfo !== void 0) {
      return constraintInfo;
    }

    const ruleConstraint = Object.keys(this.constraint)[0];

    if (ruleConstraint !== void 0) {
      constraintInfo = comparators.find(
        comparator => ruleConstraint === comparator.id
      );
    }

    // Cache this information for later use
    this._constraintInfo = constraintInfo;

    return constraintInfo;
  }

  /**
   * Check if current rule is related to priors
   * @return {Boolean} True if a rule is related to priors or false otherwise
   */
  isRuleForPrior() {
    // @TODO: Should we check this too? this.attribute === 'relativeTime'
    return this.attribute === 'abstractPriorValue';
  }

  /**
   * If the current rule is a rule for priors, returns the number of referenced priors. Otherwise, returns -1.
   * @return {Number} The number of referenced priors or -1 if not applicable. Returns zero if the actual value could not be determined.
   */
  getNumberOfPriorsReferenced() {
    if (!this.isRuleForPrior()) {
      return -1;
    }

    // Get rule's validator and value
    const ruleValidatorAndValue = this.getConstraintValidatorAndValue();
    const { value, validator } = ruleValidatorAndValue;
    const intValue = parseInt(value, 10) || 0; // avoid possible NaN

    // "Equal to" validators
    if (EQUALS_REGEXP.test(validator)) {
      // In this case, -1 (the oldest prior) indicates that at least one study is used
      return intValue < 0 ? 1 : intValue;
    }

    // Default cases return value
    return 0;
  }

  /**
   * Get the constraint validator and value
   * @return {Object|undefined} Returns an object containing the validator and it's value or undefined
   */
  getConstraintValidatorAndValue() {
    let validatorAndValue = this._validatorAndValue;

    // Check if validator and value are cached already
    if (validatorAndValue !== void 0) {
      return validatorAndValue;
    }

    // Get the constraint info object
    const constraintInfo = this.getConstraintInfo();

    // Constraint info object exists and is valid
    if (constraintInfo !== void 0) {
      const validator = constraintInfo.validator;
      const currentValidator = this.constraint[validator];

      if (currentValidator) {
        const constraintValidator = constraintInfo.validatorOption;
        const constraintValue = currentValidator[constraintValidator];

        validatorAndValue = {
          value: constraintValue,
          validator: constraintInfo.id,
        };

        this._validatorAndValue = validatorAndValue;
      }
    }

    return validatorAndValue;
  }
}
