package com.avaje.ebeaninternal.server.type;


import com.avaje.ebean.text.json.EJson;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;

import javax.persistence.PersistenceException;
import java.io.IOException;
import java.lang.reflect.Type;
import java.sql.Array;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Type mapped for DB ARRAY type (Postgres only effectively).
 */
public class ScalarTypeArrayList extends ScalarTypeJsonCollection<List> {

  private static ScalarTypeArrayList UUID = new ScalarTypeArrayList("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID);

  private static ScalarTypeArrayList LONG = new ScalarTypeArrayList("bigint", DocPropertyType.LONG, ArrayElementConverter.LONG);

  private static ScalarTypeArrayList INTEGER = new ScalarTypeArrayList("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER);

  private static ScalarTypeArrayList STRING = new ScalarTypeArrayList("varchar", DocPropertyType.STRING, ArrayElementConverter.STRING);

  /**
   * Return the ScalarType to use based on the List's generic parameter type.
   */
  public static ScalarTypeArrayList typeFor(Type valueType) {
    if (valueType.equals(UUID.class)) {
      return UUID;
    }
    if (valueType.equals(Long.class)) {
      return LONG;
    }
    if (valueType.equals(Integer.class)) {
      return INTEGER;
    }
    if (valueType.equals(String.class)) {
      return STRING;
    }
    throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping");
  }

  private final String arrayType;

  private final ArrayElementConverter converter;

  public ScalarTypeArrayList(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) {
    super(List.class, Types.ARRAY, docPropertyType);
    this.arrayType = arrayType;
    this.converter = converter;
  }

  @Override
  public DocPropertyType getDocType() {
    return docPropertyType;
  }

  /**
   * Return the DB column definition for DDL generation.
   */
  public String getDbColumnDefn() {
    return arrayType + "[]";
  }

  @SuppressWarnings("unchecked")
  private List fromArray(Object[] array1) {
    List list = new ArrayList();
    for (Object element : array1) {
      list.add(converter.toElement(element));
    }
    return new ModifyAwareList(list);
  }

  private Object[] toArray(List value) {
    return value.toArray();
  }

  @Override
  public List read(DataReader reader) throws SQLException {
    Array array = reader.getArray();
    if (array == null) {
      return null;
    } else {
      return fromArray((Object[]) array.getArray());
    }
  }

  @Override
  public void bind(DataBind bind, List value) throws SQLException {
    if (value == null) {
      bind.setNull(Types.ARRAY);
    } else {
      bind.setArray(arrayType, toArray(value));
    }
  }

  @Override
  public String formatValue(List value) {
    try {
      return EJson.write(value);
    } catch (IOException e) {
      throw new PersistenceException("Failed to format List into JSON content", e);
    }
  }

  @Override
  public List parse(String value) {
    try {
      return EJson.parseList(value, false);
    } catch (IOException e) {
      throw new PersistenceException("Failed to parse JSON content as List: [" + value + "]", e);
    }
  }

  @Override
  public List jsonRead(JsonParser parser) throws IOException {
    return EJson.parseList(parser, parser.getCurrentToken());
  }

  @Override
  public void jsonWrite(JsonGenerator writer, List value) throws IOException {
    EJson.write(value, writer);
  }

}
