package org.ananas.runner.steprunner.jdbc.derby;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.ananas.runner.steprunner.jdbc.DDL;
import org.ananas.runner.steprunner.jdbc.JDBCDataType;
import org.apache.beam.sdk.schemas.Schema;
import org.apache.beam.sdk.schemas.Schema.FieldType;

public enum DerbyDataTypes implements JDBCDataType, DDL {

  // See https://db.apache.org/derby/docs/10.2/ref/crefsqlj31068.html
  BIGINT("bigint", Schema.FieldType.INT64, true),
  BIGSERIAL("bigserial", Schema.FieldType.INT64, false),
  INT8("int8", Schema.FieldType.INT64, false),
  LONG("long", Schema.FieldType.INT64, true),
  SERIAL("serial", Schema.FieldType.INT64, false),
  SERIAL8("serial8", Schema.FieldType.INT64, false),

  INTEGER("integer", Schema.FieldType.INT32, true),
  INT("int", Schema.FieldType.INT32, false),
  INT4("integer", Schema.FieldType.INT32, false),
  SMALLINT("smallint", Schema.FieldType.INT32, false),

  DECIMAL00("decimal(29,2)", Schema.FieldType.DECIMAL, true),
  DECIMAL("decimal", Schema.FieldType.DECIMAL, false),
  NUMERIC("numeric", Schema.FieldType.DECIMAL, false),

  FLOAT("float", FieldType.FLOAT, true),
  REAL("real", Schema.FieldType.FLOAT, false),
  SMALLFLOAT("smallfloat", Schema.FieldType.FLOAT, false),

  DOUBLE("double", Schema.FieldType.DOUBLE, true),
  DOUBLE_PRECISION("double precision", Schema.FieldType.DOUBLE, false),

  BOOLEAN("boolean", Schema.FieldType.BOOLEAN.withNullable(true), true),
  BOOL("bool", Schema.FieldType.BOOLEAN, false),

  BYTE("byte", Schema.FieldType.BYTE, false),
  BYTES("bytes", Schema.FieldType.BYTE.withNullable(true), true),

  // As derby only used in dataview, map string to varchar(255) is enough
  VARCHAR255("varchar(255)", FieldType.STRING, true),
  VARCHAR("varchar", FieldType.STRING, false),
  BIT("bit", Schema.FieldType.STRING, false),
  BITVAR("bit varying", Schema.FieldType.STRING, false),
  CHAR("char", Schema.FieldType.STRING, false),
  BPCHAR("bpchar", Schema.FieldType.STRING, false),
  CHARVAR("character varying", Schema.FieldType.STRING, false),
  CHARACTER("character", Schema.FieldType.STRING, false),
  TEXT("text", Schema.FieldType.STRING, false),
  LONG_VARCHAR("long varchar", FieldType.STRING, false),
  XML("xml", Schema.FieldType.STRING, false),

  DATE_metadata("date", Schema.FieldType.DATETIME.withMetadata("subtype", "DATE"), true),
  TIME_metadata("time", Schema.FieldType.DATETIME.withMetadata("subtype", "TIME"), true),
  TIMESTAMP_metadata("timestamp", Schema.FieldType.DATETIME.withMetadata("subtype", "TS"), true),
  TIMESTAMP_WITHOUT_TS_metadata(
      "timestamp without time zone", Schema.FieldType.DATETIME.withMetadata("subtype", "TS"), true),
  TIMESTAMP_WITH_TIME_ZONE_metadata(
      "timestamp with time zone",
      Schema.FieldType.DATETIME.withMetadata("subtype", "TS_WITH_LOCAL_TZ"),
      true),
  TIMESTAMPZ_metadata(
      "timestamptz", Schema.FieldType.DATETIME.withMetadata("subtype", "TS_WITH_LOCAL_TZ"), false),
  TIMESTAMP("timestamp", Schema.FieldType.DATETIME, true),
  TIME("time", Schema.FieldType.DATETIME.withNullable(true), true),
  DATE("date", Schema.FieldType.DATETIME.withNullable(true), true);

  private static final Map<String, Schema.FieldType> dataTypes;

  static {
    dataTypes = new HashMap<>();
    for (JDBCDataType t : DerbyDataTypes.values()) {
      dataTypes.put(t.getDatatypeLiteral().toLowerCase(), t.getFieldType());
    }
  }

  private String datatypeLiteral;
  private Schema.FieldType fieldType;
  private boolean isDefault;

  DerbyDataTypes(String datatypeLiteral, Schema.FieldType fieldType, boolean isDefault) {
    this.datatypeLiteral = datatypeLiteral;
    this.fieldType = fieldType;
    this.isDefault = isDefault;
  }

  public String getDatatypeLiteral() {
    return this.datatypeLiteral;
  }

  public Schema.FieldType getFieldType() {
    return this.fieldType;
  }

  public boolean isDefault() {
    return this.isDefault;
  }

  @Override
  public JDBCDataType getDefaultDataType(Schema.FieldType type) {
    for (JDBCDataType t : DerbyDataTypes.values()) {
      if ((Objects.deepEquals(t.getFieldType().withNullable(false), type)
              || Objects.deepEquals(t.getFieldType().withNullable(true), type))
          && t.isDefault()) {
        return t;
      }
    }
    return null;
  }

  public Schema.FieldType getDefaultDataTypeLiteral(String datatypeLiteral) {
    return dataTypes.get(datatypeLiteral);
  }

  public String rewrite(String url) {
    return url;
  }
}
