modeldb-main/backend/server/src/main/java/ai/verta/modeldb/versioning/blob/visitors/Validator.java
package ai.verta.modeldb.versioning.blob.visitors;

import ai.verta.modeldb.common.exceptions.ModelDBException;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenBlob;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenBlobDiff;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenContinuousHyperparameterSetConfigBlob;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenDiffStatusEnumDiffStatus;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenDockerEnvironmentBlob;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenEnvironmentVariablesBlob;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenGitCodeBlob;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenHyperparameterSetConfigBlob;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenHyperparameterValuesConfigBlob;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenPathDatasetComponentBlob;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenPythonEnvironmentBlob;
import ai.verta.modeldb.versioning.autogenerated._public.modeldb.versioning.model.AutogenPythonRequirementEnvironmentBlob;
import ai.verta.modeldb.versioning.blob.diff.ProtoType;
import io.grpc.Status.Code;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

public class Validator extends Visitor {

  private static final String HAS_A_STRING_VALUE_WHICH_IS_NOT_IN_A_VALID_NUMERIC_NOTATION =
      "has a STRING_VALUE which is not in a valid numeric notation";

  private boolean isNull(ProtoType proto) {
    return proto == null;
  }

  public void validate(AutogenBlobDiff autogenBlobDiff) throws ModelDBException {
    if (isNull(autogenBlobDiff)) {
      return;
    }
    if (autogenBlobDiff.getCode() == null
        && autogenBlobDiff.getDataset() == null
        && autogenBlobDiff.getEnvironment() == null
        && autogenBlobDiff.getConfig() == null) {
      throw new ModelDBException("Unknown blob type specified", Code.INVALID_ARGUMENT);
    }
    if (autogenBlobDiff.getLocation() == null || autogenBlobDiff.getLocation().isEmpty()) {
      throw new ModelDBException("Blob diff location is empty", Code.INVALID_ARGUMENT);
    }
    autogenBlobDiff.postVisitDeep(this);
  }

  public void validate(AutogenBlob autogenBlob) throws ModelDBException {
    if (isNull(autogenBlob)) {
      return;
    }
    if (autogenBlob.getCode() == null
        && autogenBlob.getDataset() == null
        && autogenBlob.getEnvironment() == null
        && autogenBlob.getConfig() == null) {
      throw new ModelDBException("Unknown blob type specified", Code.INVALID_ARGUMENT);
    }
    autogenBlob.postVisitDeep(this);
  }

  @Override
  public AutogenContinuousHyperparameterSetConfigBlob
      postVisitAutogenContinuousHyperparameterSetConfigBlob(
          AutogenContinuousHyperparameterSetConfigBlob autogenContinuousHyperparameterSetConfigBlob)
          throws ModelDBException {
    AutogenHyperparameterValuesConfigBlob beginSetConfigBlob =
        autogenContinuousHyperparameterSetConfigBlob.getIntervalBegin();
    AutogenHyperparameterValuesConfigBlob endSetConfigBlob =
        autogenContinuousHyperparameterSetConfigBlob.getIntervalEnd();
    AutogenHyperparameterValuesConfigBlob stepSetConfigBlob =
        autogenContinuousHyperparameterSetConfigBlob.getIntervalStep();
    /*if (beginSetConfigBlob == null) {
      throw new ModelDBException(
          "Hyperparameter set doesn't have interval begin", Code.INVALID_ARGUMENT, HttpStatus.BAD_REQUEST);
    }
    if (endSetConfigBlob == null) {
      throw new ModelDBException(
          "Hyperparameter set doesn't have interval end", Code.INVALID_ARGUMENT, HttpStatus.BAD_REQUEST);
    }
    if (stepSetConfigBlob == null) {
      throw new ModelDBException(
          "Hyperparameter set doesn't have interval step", Code.INVALID_ARGUMENT, HttpStatus.BAD_REQUEST);
    }*/

    checkStringIsDouble(beginSetConfigBlob, "beginSetConfigBlob ");
    checkStringIsDouble(endSetConfigBlob, "endSetConfigBlob ");
    checkStringIsDouble(stepSetConfigBlob, "stepSetConfigBlob ");
    return autogenContinuousHyperparameterSetConfigBlob;
  }

  private void checkStringIsDouble(
      AutogenHyperparameterValuesConfigBlob beginSetConfigBlob, String description)
      throws ModelDBException {
    if (beginSetConfigBlob != null && !beginSetConfigBlob.getStringValue().isEmpty()) {
      try {
        Double.parseDouble(beginSetConfigBlob.getStringValue());
      } catch (Exception ex) {
        throw new ModelDBException(
            description + HAS_A_STRING_VALUE_WHICH_IS_NOT_IN_A_VALID_NUMERIC_NOTATION,
            Code.INVALID_ARGUMENT);
      }
    }
  }

  @Override
  public AutogenDockerEnvironmentBlob postVisitAutogenDockerEnvironmentBlob(
      AutogenDockerEnvironmentBlob blob) throws ModelDBException {
    if (blob.getRepository().isEmpty()) {
      throw new ModelDBException(
          "Environment repository path should not be empty", Code.INVALID_ARGUMENT);
    }
    return blob;
  }

  @Override
  public List<AutogenEnvironmentVariablesBlob> postVisitDeepListOfAutogenEnvironmentVariablesBlob(
      List<AutogenEnvironmentVariablesBlob> lst) throws ModelDBException {
    if (lst == null) {
      return null;
    }
    Set<String> variableNames = new HashSet<>();
    for (AutogenEnvironmentVariablesBlob blob : lst) {
      blob.postVisitDeep(this);
      variableNames.add(blob.getName());
    }
    if (variableNames.size() != lst.size()) {
      throw new ModelDBException("There are recurring variables", Code.INVALID_ARGUMENT);
    }
    return lst;
  }

  private static final String PATTERN = "[a-zA-Z0-9_-]+";

  @Override
  public AutogenEnvironmentVariablesBlob postVisitAutogenEnvironmentVariablesBlob(
      AutogenEnvironmentVariablesBlob blob) throws ModelDBException {
    if (!Pattern.compile(PATTERN).matcher(blob.getName()).matches()) {
      throw new ModelDBException(
          "Environment variable name: "
              + blob.getName()
              + " should be not empty, should contain only alphanumeric or underscore",
          Code.INVALID_ARGUMENT);
    }
    return blob;
  }

  @Override
  public AutogenGitCodeBlob postVisitAutogenGitCodeBlob(AutogenGitCodeBlob blob)
      throws ModelDBException {
    if (blob.getRepo().isEmpty()) {
      throw new ModelDBException("Code repository path should not be empty", Code.INVALID_ARGUMENT);
    }
    return blob;
  }

  @Override
  public AutogenHyperparameterSetConfigBlob postVisitAutogenHyperparameterSetConfigBlob(
      AutogenHyperparameterSetConfigBlob blob) throws ModelDBException {
    final String name = blob.getName();
    if (name.isEmpty()) {
      throw new ModelDBException("Hyperparameter set name is empty", Code.INVALID_ARGUMENT);
    }
    return blob;
  }

  /*private static boolean isNull(AutogenHyperparameterValuesConfigBlob beginSetConfigBlob) {
    return beginSetConfigBlob == null
        || beginSetConfigBlob.getFloatValue() == null
            && beginSetConfigBlob.getIntValue() == null
            && beginSetConfigBlob.getStringValue() == null;
  }*/

  /*@Override
  public void postVisitDeepAutogenHyperparameterValuesConfigBlob(
      AutogenHyperparameterValuesConfigBlob blob) throws ModelDBException {
    if (isNull(blob)) {
      throw new ModelDBException("Hyperparameter value has unknown type");
    }
  }*/

  @Override
  public AutogenPathDatasetComponentBlob postVisitAutogenPathDatasetComponentBlob(
      AutogenPathDatasetComponentBlob blob) throws ModelDBException {
    if (blob.getPath().isEmpty()) {
      throw new ModelDBException("Dataset path is empty", Code.INVALID_ARGUMENT);
    }
    return blob;
  }

  @Override
  public AutogenPythonEnvironmentBlob postVisitAutogenPythonEnvironmentBlob(
      AutogenPythonEnvironmentBlob blob) throws ModelDBException {
    if (blob.getRequirements() != null) {
      Set<AutogenPythonRequirementEnvironmentBlob> pythonRequirementHash =
          new HashSet<>(blob.getRequirements());
      if (pythonRequirementHash.size() != blob.getRequirements().size()) {
        throw new ModelDBException("There are recurring requirements", Code.INVALID_ARGUMENT);
      }
    }
    if (blob.getConstraints() != null) {
      Set<AutogenPythonRequirementEnvironmentBlob> pythonConstraintHash =
          new HashSet<>(blob.getConstraints());
      if (pythonConstraintHash.size() != blob.getConstraints().size()) {
        throw new ModelDBException("There are recurring constraints", Code.INVALID_ARGUMENT);
      }
    }
    return blob;
  }

  @Override
  public AutogenPythonRequirementEnvironmentBlob postVisitAutogenPythonRequirementEnvironmentBlob(
      AutogenPythonRequirementEnvironmentBlob blob) throws ModelDBException {
    if (blob.getLibrary().isEmpty()) {
      throw new ModelDBException(
          "Requirement or constraint library name should not be empty", Code.INVALID_ARGUMENT);
    }
    return blob;
  }

  @Override
  public AutogenDiffStatusEnumDiffStatus postVisitDeepAutogenDiffStatusEnumDiffStatus(
      AutogenDiffStatusEnumDiffStatus blob) throws ModelDBException {
    if (blob == null) {
      throw new ModelDBException("Unknown status", Code.INVALID_ARGUMENT);
    }
    switch (blob.toProto()) {
      case DELETED:
      case ADDED:
      case MODIFIED:
        break;
      default:
        throw new ModelDBException("Unknown status specified", Code.INVALID_ARGUMENT);
    }
    return blob;
  }
}
