/*
 * Copyright (c) 2013, Cloudera, Inc. All Rights Reserved.
 *
 * Cloudera, Inc. licenses this file to you 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
 *
 * This software 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.
 */

package com.cloudera.oryx.rdf.serving.web;

import com.google.common.collect.BiMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;

import com.cloudera.oryx.common.io.DelimitedDataUtils;
import com.cloudera.oryx.common.settings.InboundSettings;
import com.cloudera.oryx.rdf.common.example.Example;
import com.cloudera.oryx.rdf.common.example.Feature;
import com.cloudera.oryx.rdf.common.example.FeatureType;
import com.cloudera.oryx.rdf.common.example.IgnoredFeature;
import com.cloudera.oryx.rdf.common.rule.CategoricalPrediction;
import com.cloudera.oryx.rdf.common.rule.NumericPrediction;
import com.cloudera.oryx.rdf.common.rule.Prediction;
import com.cloudera.oryx.rdf.common.tree.TreeBasedClassifier;
import com.cloudera.oryx.rdf.serving.generation.Generation;

/**
 * <p>Responds to a GET request to {@code /classify/[datum]}. The input is one data point to classify,
 * delimited, like "1,foo,3.0". The response body contains the result of classification on one line.
 * The result depends on the classifier --  could be a number or a category name.</p>
 *
 * @author Sean Owen
 */
public final class ClassifyServlet extends AbstractRDFServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {

    CharSequence pathInfo = request.getPathInfo();
    if (pathInfo == null) {
      response.sendError(HttpServletResponse.SC_BAD_REQUEST, "No path");
      return;
    }
    String line = pathInfo.subSequence(1, pathInfo.length()).toString();

    Generation generation = getGenerationManager().getCurrentGeneration();
    if (generation == null) {
      response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                         "API method unavailable until model has been built and loaded");
      return;
    }

    Writer out = response.getWriter();
    InboundSettings inboundSettings = getInboundSettings();
    TreeBasedClassifier forest = generation.getForest();

    Map<Integer,BiMap<String,Integer>> columnToCategoryNameToIDMapping =
        generation.getColumnToCategoryNameToIDMapping();

    int totalColumns = getTotalColumns();

    String[] tokens = DelimitedDataUtils.decode(line);
    if (tokens.length != totalColumns) {
      response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Wrong column count");
      return;
    }

    Feature[] features = new Feature[totalColumns]; // Too big by 1 but makes math easier

    try {
      for (int col = 0; col < features.length; col++) {
        if (col == inboundSettings.getTargetColumn()) {
          features[col] = IgnoredFeature.INSTANCE;
        } else {
          features[col] = buildFeature(col, tokens[col], columnToCategoryNameToIDMapping);
        }
      }
    } catch (IllegalArgumentException iae) {
      response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad input line");
      return;
    }

    Example example = new Example(null, features);
    Prediction prediction = forest.classify(example);

    if (prediction.getFeatureType() == FeatureType.CATEGORICAL) {
      Map<Integer,String> targetIDToCategory =
          columnToCategoryNameToIDMapping.get(inboundSettings.getTargetColumn()).inverse();
      int categoryID = ((CategoricalPrediction) prediction).getMostProbableCategoryID();
      out.write(targetIDToCategory.get(categoryID));
    } else {
      out.write(Float.toString(((NumericPrediction) prediction).getPrediction()));
    }
    out.write("\n");
  }

}

@@ -1748,16 +1748,194 @@
 %3C/p%3E%0A *%0A
+ * %3Cp%3EIf request parameter %7B@code report%7D is present and set to %7B@code probability%7D, the%0A * output will be the probability of the most-probable class instead of its name.%3C/p%3E%0A *%0A
  * @auth
@@ -2371,24 +2371,108 @@
 .toString();
+%0A    boolean probabilityOnly = %22probability%22.equals(request.getParameter(%22report%22));
 %0A%0A    Genera
@@ -3982,24 +3982,329 @@
 EGORICAL) %7B%0A
+      CategoricalPrediction categoricalPrediction = (CategoricalPrediction) prediction;%0A      int categoryID = categoricalPrediction.getMostProbableCategoryID();%0A      if (probabilityOnly) %7B%0A        out.write(Float.toString(categoricalPrediction.getCategoryProbabilities()%5BcategoryID%5D));%0A      %7D else %7B%0A  
       Map%3CIn
@@ -4309,16 +4309,17 @@
 Integer,
+ 
 String%3E 
@@ -4339,16 +4339,18 @@
 egory =%0A
+  
         
@@ -4439,95 +4439,8 @@
 ;%0A  
-    int categoryID = ((CategoricalPrediction) prediction).getMostProbableCategoryID();%0A
     
@@ -4484,24 +4484,32 @@
 tegoryID));%0A
+      %7D%0A
     %7D else %7B

Add option to output probability