package water.api;

import water.H2O;
import water.Iced;
import water.util.BeanUtils;
import water.util.Log;

import java.lang.reflect.Field;

// 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).
 */
public class ModelParameterSchemaV2 extends Schema<Iced, ModelParameterSchemaV2> {
  @API(help="name in the JSON, e.g. \"lambda\"")
  public String name;

  @API(help="label in the UI, e.g. \"lambda\"")
  public String label;

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

  @API(help="the field is required")
  public boolean required;

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

  @API(help="default value, e.g. 1")
  public String default_value;

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

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

  @API(help="other fields that must be set before setting this one, e.g. \"response_column\"")
  public String[] dependencies;

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


  public ModelParameterSchemaV2() {
  }

  /** For a given Class generate a client-friendly type name (e.g., int[][] or Frame). */
  private static String consType(Class clz) {
    boolean is_enum = Enum.class.isAssignableFrom(clz);
    boolean is_array = clz.isArray();

    if (is_enum)
      return "enum";

    if (is_array)
      return consType(clz.getComponentType()) + "[]";

    if (water.Model.class.isAssignableFrom(clz))
      return "Model";

    if (water.fvec.Frame.class.isAssignableFrom(clz))
      return "Frame";

    if (water.fvec.Vec.class.isAssignableFrom(clz))
      return "Vec";

    if (water.Key.class.isAssignableFrom(clz))
      return "Key";

    if (String.class.isAssignableFrom(clz))
      return "String";

    Log.warn("Don't know how to generate a client-friendly type name for class: " + clz.toString());
    return clz.toString();
  }

  public ModelParameterSchemaV2(ModelParametersSchema schema, ModelParametersSchema default_schema, Field f) {
    try {
      this.name = f.getName();
      Object o;

      o = f.get(default_schema);
      this.default_value = (o == null ? null : o.toString());

      o = f.get(schema);
      this.actual_value = (o == null ? null : o.toString());

      boolean is_enum = Enum.class.isAssignableFrom(f.getType());
      boolean is_array = f.getType().isArray();
      this.type = consType(f.getType());

      API annotation = f.getAnnotation(API.class);

      if (null != annotation) {
        String l = annotation.label();
        this.label = (null == l || l.isEmpty() ? f.getName() : l);
        this.help = annotation.help();
        this.required = annotation.required();

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

        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);
        }
      }
    }
    catch (Exception e) {
      throw H2O.fail("Caught exception accessing field: " + f + " for schema object: " + this + ": " + e.toString());
    }
  }

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

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

@@ -103,16 +103,48 @@
 l.Log;%0A%0A
+import java.lang.reflect.Array;%0A
 import j
@@ -879,24 +879,185 @@
 fault_value;
+ // TODO: we would like this to be a primitive so that the client doesn't have to parse it. . .  Problem is Icer serialization blows up if the field is an Object
 %0A%0A  @API(hel
@@ -1170,16 +1170,111 @@
 l_value;
+ // TODO: we would like this to be a primitive so that the client doesn't have to parse it. . .
 %0A%0A  @API
@@ -2354,16 +2354,55 @@
 rn %22
-S
+s
 tring%22;
+ // lower-case, to be less Java-centric
 %0A%0A  
@@ -2528,24 +2528,422 @@
 ing();%0A  %7D%0A%0A
+  private static String consValue(Object o) %7B%0A    if (null == o)%0A      return null;%0A%0A    if (! o.getClass().isArray())%0A      return o.toString();%0A%0A    StringBuilder sb = new StringBuilder();%0A    sb.append(%22%5B%22);%0A    for (int i = 0; i %3C Array.getLength(o); i++) %7B%0A      if (i %3E 0) sb.append(%22, %22);%0A      sb.append(consValue(Array.get(o, i)));%0A    %7D%0A    sb.append(%22%5D%22);%0A    return sb.toString();%0A  %7D%0A%0A
   public Mod
@@ -3078,24 +3078,72 @@
 .getName();%0A
+      boolean is_array = f.getType().isArray();%0A
       Object
@@ -3211,44 +3211,24 @@
 e = 
-(o == null ? null : o.toString()
+consValue(o
 );%0A%0A
+%0A
     
@@ -3278,40 +3278,19 @@
 e = 
-(o == null ? null : o.toString()
+consValue(o
 );%0A%0A
@@ -3359,56 +3359,8 @@
 ));%0A
-      boolean is_array = f.getType().isArray();%0A
     

Fix JSON serialization of array parameter values (still contained in strings for now).