/*
 * Copyright DataStax, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.datastax.oss.driver.api.core.type.codec.registry;

import com.datastax.oss.driver.api.core.cql.PreparedStatement;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.data.GettableByIndex;
import com.datastax.oss.driver.api.core.data.TupleValue;
import com.datastax.oss.driver.api.core.type.DataType;
import com.datastax.oss.driver.api.core.type.DataTypes;
import com.datastax.oss.driver.api.core.type.TupleType;
import com.datastax.oss.driver.api.core.type.UserDefinedType;
import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException;
import com.datastax.oss.driver.api.core.type.codec.TypeCodec;
import com.datastax.oss.driver.api.core.type.reflect.GenericType;
import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.List;

/**
 * Provides codecs to convert CQL types to their Java equivalent, and vice-versa.
 *
 * <p>Implementations <b>MUST</b> provide a default mapping for all CQL types (primitive types, and
 * all the collections, tuples or user-defined types that can recursively be built from them &mdash;
 * see {@link DataTypes}).
 *
 * <p>They may also provide additional mappings to other Java types (for use with methods such as
 * {@link Row#get(int, Class)}, {@link TupleValue#set(int, Object, Class)}, etc.)
 *
 * <p>The default implementation returned by the driver also implements {@link
 * MutableCodecRegistry}, and we strongly recommend that custom implementations do as well. The two
 * interfaces are only separate for backward compatibility, because mutability was introduced in
 * 4.3.0.
 */
public interface CodecRegistry {
  /**
   * An immutable instance, that only handles built-in driver types (that is, primitive types, and
   * collections, tuples, and user defined types thereof).
   *
   * <p>Note that, due to implementation details, this instance is a {@link MutableCodecRegistry},
   * but any attempt to {@linkplain MutableCodecRegistry#register(TypeCodec) register new codecs}
   * will throw {@link UnsupportedOperationException}.
   */
  CodecRegistry DEFAULT =
      new DefaultCodecRegistry("default") {
        @Override
        public void register(TypeCodec<?> newCodec) {
          throw new UnsupportedOperationException("CodecRegistry.DEFAULT is immutable");
        }
      };

  /**
   * Returns a codec to handle the conversion between the given types.
   *
   * <p>This is used internally by the driver, in cases where both types are known, for example
   * {@link GettableByIndex#getString(int) row.getString(0)} (Java type inferred from the method,
   * CQL type known from the row metadata).
   *
   * <p>The driver's default registry implementation is <em>invariant</em> with regard to the Java
   * type: for example, if {@code B extends A} and an {@code A<=>int} codec is registered, {@code
   * codecFor(DataTypes.INT, B.class)} <b>will not</b> find that codec. This is because this method
   * is used internally both for encoding and decoding, and covariance wouldn't work when decoding.
   *
   * @throws CodecNotFoundException if there is no such codec.
   */
  @NonNull
  <JavaTypeT> TypeCodec<JavaTypeT> codecFor(
      @NonNull DataType cqlType, @NonNull GenericType<JavaTypeT> javaType);

  /**
   * Shortcut for {@link #codecFor(DataType, GenericType) codecFor(cqlType,
   * GenericType.of(javaType))}.
   *
   * <p>Implementations may decide to override this method for performance reasons, if they have a
   * way to avoid the overhead of wrapping.
   *
   * @throws CodecNotFoundException if there is no such codec.
   */
  @NonNull
  default <JavaTypeT> TypeCodec<JavaTypeT> codecFor(
      @NonNull DataType cqlType, @NonNull Class<JavaTypeT> javaType) {
    return codecFor(cqlType, GenericType.of(javaType));
  }

  /**
   * Returns a codec to convert the given CQL type to the Java type deemed most appropriate to
   * represent it.
   *
   * <p>This is used internally by the driver, in cases where the Java type is not explicitly
   * provided, for example {@link GettableByIndex#getObject(int) row.getObject(0)} (CQL type known
   * from the row metadata, Java type unspecified).
   *
   * <p>The definition of "most appropriate" is left to the appreciation of the registry
   * implementor.
   *
   * @throws CodecNotFoundException if there is no such codec.
   */
  @NonNull
  <JavaTypeT> TypeCodec<JavaTypeT> codecFor(@NonNull DataType cqlType);

  /**
   * Returns a codec to convert the given Java type to the CQL type deemed most appropriate to
   * represent it.
   *
   * <p>The driver does not use this method. It is provided as a convenience for third-party usage,
   * for example if you were to generate a schema based on a set of Java classes.
   *
   * <p>The driver's default registry implementation is <em>invariant</em> with regard to the Java
   * type: for example, if {@code B extends A} and an {@code A<=>int} codec is registered, {@code
   * codecFor(DataTypes.INT, B.class)} <b>will not</b> find that codec. This is because we don't
   * know whether this method will be used for encoding, decoding, or both.
   *
   * @throws CodecNotFoundException if there is no such codec.
   */
  @NonNull
  <JavaTypeT> TypeCodec<JavaTypeT> codecFor(@NonNull GenericType<JavaTypeT> javaType);

  /**
   * Shortcut for {@link #codecFor(GenericType) codecFor(GenericType.of(javaType))}.
   *
   * <p>Implementations may decide to override this method for performance reasons, if they have a
   * way to avoid the overhead of wrapping.
   *
   * @throws CodecNotFoundException if there is no such codec.
   */
  @NonNull
  default <JavaTypeT> TypeCodec<JavaTypeT> codecFor(@NonNull Class<JavaTypeT> javaType) {
    return codecFor(GenericType.of(javaType));
  }

  /**
   * Returns a codec to convert the given Java object to the given CQL type.
   *
   * <p>This is used internally by the driver when you bulk-set values in a {@link
   * PreparedStatement#bind(Object...) bound statement}, {@link UserDefinedType#newValue(Object...)
   * UDT} or {@link TupleType#newValue(Object...) tuple}.
   *
   * <p>Unlike other methods, the driver's default registry implementation is <em>covariant</em>
   * with regard to the Java type: for example, if {@code B extends A} and an {@code A<=>int} codec
   * is registered, {@code codecFor(DataTypes.INT, someB)} <b>will</b> find that codec. This is
   * because this method is always used in encoding scenarios; if a bound statement has a value with
   * a runtime type of {@code ArrayList<String>}, it should be possible to encode it with a codec
   * that accepts a {@code List<String>}.
   *
   * @throws CodecNotFoundException if there is no such codec.
   */
  @NonNull
  <JavaTypeT> TypeCodec<JavaTypeT> codecFor(@NonNull DataType cqlType, @NonNull JavaTypeT value);

  /**
   * Returns a codec to convert the given Java object to the CQL type deemed most appropriate to
   * represent it.
   *
   * <p>This is used internally by the driver, in cases where the CQL type is unknown, for example
   * for {@linkplain SimpleStatement#setPositionalValues(List) simple statement variables} (simple
   * statements don't have access to schema metadata).
   *
   * <p>Unlike other methods, the driver's default registry implementation is <em>covariant</em>
   * with regard to the Java type: for example, if {@code B extends A} and an {@code A<=>int} codec
   * is registered, {@code codecFor(someB)} <b>will</b> find that codec. This is because this method
   * is always used in encoding scenarios; if a simple statement has a value with a runtime type of
   * {@code ArrayList<String>}, it should be possible to encode it with a codec that accepts a
   * {@code List<String>}.
   *
   * <p>Note that, if {@code value} is an empty collection, this method may return a codec that
   * won't accept {@code JavaTypeT}; but it will encode {@code value} correctly.
   *
   * @throws CodecNotFoundException if there is no such codec.
   */
  @NonNull
  <JavaTypeT> TypeCodec<JavaTypeT> codecFor(@NonNull JavaTypeT value);
}
