package water.api.schemas3;

import java.lang.reflect.Field;

import water.AutoBuffer;
import water.H2O;
import water.Iced;
import water.IcedWrapper;
import water.api.API;
import water.api.SchemaMetadata.FieldMetadata;
import water.util.PojoUtils;

// TODO: move into hex.schemas!


/**
 * An instance of a ModelParameters schema contains the metadata for a single Model build parameter (e.g., K for KMeans).
 * TODO: add a superclass.
 * TODO: refactor this into with FieldMetadataBase.
 */
public class ModelParameterSchemaV3 extends SchemaV3<Iced, ModelParameterSchemaV3> {
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // CAREFUL: This class has its own JSON serializer.  If you add a field here you probably also want to add it to the serializer!
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  @API(help="name in the JSON, e.g. \"lambda\"", direction=API.Direction.OUTPUT)
  public String name;

  @API(help="[DEPRECATED] same as name.", direction=API.Direction.OUTPUT)
  public String label;

  @API(help="help for the UI, e.g. \"regularization multiplier, typically used for foo bar baz etc.\"", direction=API.Direction.OUTPUT)
  public String help;

  @API(help="the field is required", direction=API.Direction.OUTPUT)
  public boolean required;

  @API(help="Java type, e.g. \"double\"", direction=API.Direction.OUTPUT)
  public String type;

  @API(help="default value, e.g. 1", direction=API.Direction.OUTPUT)
  public Iced default_value;

  @API(help="actual value as set by the user and / or modified by the ModelBuilder, e.g., 10", direction=API.Direction.OUTPUT)
  public Iced actual_value;

  @API(help="the importance of the parameter, used by the UI, e.g. \"critical\", \"extended\" or \"expert\"", direction=API.Direction.OUTPUT)
  public String level;

  @API(help="list of valid values for use by the front-end", direction=API.Direction.OUTPUT)
  public String[] values;

  @API(help="For Vec-type fields this is the set of other Vec-type fields which must contain mutually exclusive values; for example, for a SupervisedModel the response_column must be mutually exclusive with the weights_column")
  public String[] is_member_of_frames;

  @API(help="For Vec-type fields this is the set of Frame-type fields which must contain the named column; for example, for a SupervisedModel the response_column must be in both the training_frame and (if it's set) the validation_frame")
  public String[] is_mutually_exclusive_with;

  @API(help="Parameter can be used in grid call", direction=API.Direction.OUTPUT)
  public boolean gridable;

  public ModelParameterSchemaV3() {
  }

  /** TODO: refactor using SchemaMetadata. */
  public ModelParameterSchemaV3(ModelParametersSchemaV3 schema, ModelParametersSchemaV3 default_schema, Field f) {
    f.setAccessible(true);
    try {
      this.name = f.getName();
      API annotation = f.getAnnotation(API.class);

      boolean is_array = f.getType().isArray();
      Object o;

      o = f.get(default_schema);
      this.default_value = FieldMetadata.consValue(o);

      o = f.get(schema);
      this.actual_value = FieldMetadata.consValue(o);

      boolean is_enum = Enum.class.isAssignableFrom(f.getType());
      this.type = FieldMetadata.consType(schema, f.getType(), f.getName(), annotation);

      if (null != annotation) {
        String l = annotation.label();
        this.label = this.name;
        this.help = annotation.help();
        this.required = annotation.required();

        this.level = annotation.level().toString();

        this.values = annotation.values();

        // If the field is an enum then the values annotation field had better be set. . .
        if (is_enum && (null == this.values || 0 == this.values.length)) {
          throw H2O.fail("Didn't find values annotation for enum field: " + this.name);
        }

        // NOTE: we just set the raw value here.  We compute the transitive closure
        // before serializing to JSON.  We have to do this automagically since we
        // need to combine the values from multiple fields in multiple levels of the
        // inheritance hierarchy.
        this.is_member_of_frames = annotation.is_member_of_frames();

        this.is_mutually_exclusive_with = annotation.is_mutually_exclusive_with(); // NOTE: later we walk all the fields in the Schema and form the transitive closure of these lists.

        this.gridable = annotation.gridable();
      }
    }
    catch (Exception e) {
      throw H2O.fail("Caught exception accessing field: " + f + " for schema object: " + this + ": " + e.toString());
    }
  }

  public ModelParameterSchemaV3 fillFromImpl(Iced iced) {
    PojoUtils.copyProperties(this, iced, PojoUtils.FieldNaming.ORIGIN_HAS_UNDERSCORES);
    return this;
  }

  public Iced createImpl() {
    // should never get called
    throw H2O.fail("createImpl should never get called in ModelParameterSchemaV2!");
  }

  /**
   * ModelParameterSchema has its own serializer so that default_value and actual_value
   * get serialized as their native types.  Autobuffer assumes all classes that have their
   * own serializers should be serialized as JSON objects, and wraps them in {}, so this
   * can't just be handled by a customer serializer in IcedWrapper.
   *
   * @param ab
   * @return
   */
  public final AutoBuffer writeJSON_impl(AutoBuffer ab) {
    ab.putJSONStr("name", name);                                    ab.put1(',');
    ab.putJSONStr("label", name);                                   ab.put1(',');
    ab.putJSONStr("help", help);                                    ab.put1(',');
    ab.putJSONStrUnquoted("required", required ? "true" : "false"); ab.put1(',');
    ab.putJSONStr("type", type);                                    ab.put1(',');

    if (default_value instanceof IcedWrapper) {
      ab.putJSONStr("default_value").put1(':');
      ((IcedWrapper) default_value).writeUnwrappedJSON(ab);            ab.put1(',');
    } else {
      ab.putJSONStr("default_value").put1(':').putJSON(default_value); ab.put1(',');
    }

    if (actual_value instanceof IcedWrapper) {
      ab.putJSONStr("actual_value").put1(':');
      ((IcedWrapper) actual_value).writeUnwrappedJSON(ab);             ab.put1(',');
    } else {
      ab.putJSONStr("actual_value").put1(':').putJSON(actual_value);   ab.put1(',');
    }

    ab.putJSONStr("level", level);                                            ab.put1(',');
    ab.putJSONAStr("values", values);                                         ab.put1(',');
    ab.putJSONAStr("is_member_of_frames", is_member_of_frames);               ab.put1(',');
    ab.putJSONAStr("is_mutually_exclusive_with", is_mutually_exclusive_with); ab.put1(',');
    ab.putJSONStrUnquoted("gridable", gridable ? "true" : "false");
    return ab;
  }
}

@@ -2115,17 +2115,13 @@
  of 
-other Vec
+Frame
 -typ
@@ -2152,33 +2152,24 @@
 ain 
-mutually exclusive values
+the named column
 ; fo
@@ -2233,50 +2233,73 @@
  be 
-mutually exclusive with the weights_column
+in both the training_frame and (if it's set) the validation_frame
 %22)%0A 
@@ -2381,37 +2381,41 @@
 s is the set of 
-Frame
+other Vec
 -type fields whi
@@ -2430,32 +2430,41 @@
 contain 
-the named column
+mutually exclusive values
 ; for ex
@@ -2524,73 +2524,50 @@
  be 
-in both the training_frame and (if it's set) the validation_frame
+mutually exclusive with the weights_column
 %22)%0A 

Fix swapped by mistake API help messages for ModelParametersSchemaV3 class. (#3796)