text: // Copyright 2015 The Oppia Authors. All Rights Reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //      http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS-IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  /**
   * @fileoverview Classification service for answer groups.
   */
  
   // TODO(bhenning): Find a better place for these constants.
  
  // NOTE TO DEVELOPERS: These constants must be the same (in name and value) as
  // the corresponding classification constants defined in core.domain.exp_domain.
  oppia.constant('EXPLICIT_CLASSIFICATION', 'explicit')
  oppia.constant('TRAINING_DATA_CLASSIFICATION', 'training_data_match')
  oppia.constant('STATISTICAL_CLASSIFICATION', 'statistical_classifier')
  oppia.constant('DEFAULT_OUTCOME_CLASSIFICATION', 'default_outcome')
  
  oppia.factory('AnswerClassificationService', [
    '$http', 'LearnerParamsService', 'AlertsService',
    'AnswerClassificationResultObjectFactory',
    'PredictionAlgorithmRegistryService', 'StateClassifierMappingService',
    'INTERACTION_SPECS', 'ENABLE_ML_CLASSIFIERS', 'EXPLICIT_CLASSIFICATION',
    'DEFAULT_OUTCOME_CLASSIFICATION', 'STATISTICAL_CLASSIFICATION',
    'RULE_TYPE_CLASSIFIER',
    function($http, LearnerParamsService, AlertsService,
        AnswerClassificationResultObjectFactory,
        PredictionAlgorithmRegistryService, StateClassifierMappingService,
        INTERACTION_SPECS, ENABLE_ML_CLASSIFIERS, EXPLICIT_CLASSIFICATION,
        DEFAULT_OUTCOME_CLASSIFICATION, STATISTICAL_CLASSIFICATION,
        RULE_TYPE_CLASSIFIER) {
      /**
       * Finds the first answer group with a rule that returns true.
       *
       * @param {*} answer - The answer that the user has submitted.
       * @param {array} answerGroups - The answer groups of the interaction. Each
       *     answer group contains rule_specs, which is a list of rules.
       * @param {object} defaultOutcome - The default outcome of the interaction.
       * @param {function} interactionRulesService The service which contains the
       *     explicit rules of that interaction.
       *
       * @return {object} An AnswerClassificationResult domain object.
       */
      var classifyAnswer = function(
          answer, answerGroups, defaultOutcome, interactionRulesService) {
        // Find the first group that contains a rule which returns true
        // TODO(bhenning): Implement training data classification.
        for (var i = 0; i < answerGroups.length; i++) {
          for (var j = 0; j < answerGroups[i].rules.length; j++) {
            var rule = answerGroups[i].rules[j];
            if (rule.type !== RULE_TYPE_CLASSIFIER &&
                interactionRulesService[rule.type](
                  answer, rule.inputs)) {
              return AnswerClassificationResultObjectFactory.createNew(
                answerGroups[i].outcome, i, j, EXPLICIT_CLASSIFICATION);
            }
          }
        }
  
        // If no rule in any answer group returns true, the default 'group' is
        // returned. Throws an error if the default outcome is not defined.
        if (defaultOutcome) {
          return AnswerClassificationResultObjectFactory.createNew(
            defaultOutcome, answerGroups.length, 0, DEFAULT_OUTCOME_CLASSIFICATION
          );
        } else {
          AlertsService.addWarning('Something went wrong with the exploration.');
        }
      };
  
      /**
       * Finds the first rule that contains RULE_TYPE_CLASSIFIER as rule type.
       *
       * @param {object} answerGroup - An answer group of the interaction. Each
       *     answer group containts rule_specs which is a list of rules.
       *
       * @return {integer} The index of the retrieved rule with rule_type
       *     RULE_TYPE_CLASSIFIER.
       */
      var findClassifierRuleIndex = function(answerGroup) {
        for (var i = 0; i < answerGroup.rules.length; i++) {
          var rule = answerGroup.rules[i];
          if (rule.type === RULE_TYPE_CLASSIFIER) {
            return i;
          }
        }
        throw Error('Classifier Rule type is not present in this answer group.');
      };
  
      return {
        /**
         * Classifies the answer according to the answer groups. and returns the
         * corresponding answer classification result.
         *
         * @param {string} explorationId - The exploration ID.
         * @param {object} oldState - The state where the user submitted the
         *   answer.
         * @param {*} answer - The answer that the user has submitted.
         * @param {boolean} isInEditorMode - Whether the function is being called
         *   in editor mode.
         * @param {function} interactionRulesService - The service which contains
         *   the explicit rules of that interaction.
         *
         * @return {AnswerClassificationResult} The resulting
         *   AnswerClassificationResult domain object.
         */
        getMatchingClassificationResult: function(
            explorationId, stateName, oldState, answer, isInEditorMode,
            interactionRulesService) {
          var answerClassificationResult = null;
  
          var answerGroups = oldState.interaction.answerGroups;
          var defaultOutcome = oldState.interaction.defaultOutcome;
          if (interactionRulesService) {
            answerClassificationResult = classifyAnswer(
              answer, answerGroups, defaultOutcome, interactionRulesService);
          } else {
            AlertsService.addWarning(
              'Something went wrong with the exploration: no ' +
              'interactionRulesService was available.');
            throw Error(
              'No interactionRulesService was available to classify the answer.');
          }
  
          var ruleBasedOutcomeIsDefault = (
            answerClassificationResult.outcome === defaultOutcome);
          var interactionIsTrainable = INTERACTION_SPECS[
            oldState.interaction.id].is_trainable;
  
          if (ruleBasedOutcomeIsDefault && interactionIsTrainable &&
              ENABLE_ML_CLASSIFIERS) {
            var classifier = StateClassifierMappingService.getClassifier(
              stateName);
            if (classifier && classifier.classifierData && (
              classifier.algorithmId && classifier.dataSchemaVersion)) {
              var predictionService = (
                PredictionAlgorithmRegistryService.getPredictionService(
                  classifier.algorithmId, classifier.dataSchemaVersion));
              // If prediction service exists, we run classifier. We return the
              // default outcome otherwise.
              if (predictionService) {
                var predictedAnswerGroupIndex = predictionService.predict(
                  classifier.classifierData, answer);
                answerClassificationResult = (
                  AnswerClassificationResultObjectFactory.createNew(
                    answerGroups[predictedAnswerGroupIndex].outcome,
                    predictedAnswerGroupIndex,
                    findClassifierRuleIndex(
                      answerGroups[predictedAnswerGroupIndex]),
                    STATISTICAL_CLASSIFICATION));
              }
            }
          }
  
          return answerClassificationResult;
        }
      };
    }
  ]);
  