/*
 * 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.mapper.annotations;

import com.datastax.dse.driver.api.core.cql.reactive.ReactiveResultSet;
import com.datastax.dse.driver.api.mapper.reactive.MappedReactiveResultSet;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.MappedAsyncPagingIterable;
import com.datastax.oss.driver.api.core.PagingIterable;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.BoundStatement;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.session.Session;
import com.datastax.oss.driver.api.core.session.SessionBuilder;
import com.datastax.oss.driver.api.mapper.entity.saving.NullSavingStrategy;
import com.datastax.oss.driver.api.mapper.result.MapperResultProducer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.function.UnaryOperator;

/**
 * Annotates a {@link Dao} method that executes a user-provided query.
 *
 * <p>Example:
 *
 * <pre>
 * &#64;Dao
 * public interface SensorReadingDao {
 *   &#64;Query("SELECT count(*) FROM sensor_readings WHERE id = :id")
 *   long countById(int id);
 * }
 * </pre>
 *
 * This is the equivalent of what was called "accessor methods" in the driver 3 mapper.
 *
 * <h3>Parameters</h3>
 *
 * The query string provided in {@link #value()} will typically contain CQL placeholders. The
 * method's parameters must match those placeholders: same name and a compatible Java type.
 *
 * <pre>
 * &#64;Query("SELECT count(*) FROM sensor_readings WHERE id = :id AND year = :year")
 * long countByIdAndYear(int id, int year);
 * </pre>
 *
 * <p>A {@link Function Function&lt;BoundStatementBuilder, BoundStatementBuilder&gt;} or {@link
 * UnaryOperator UnaryOperator&lt;BoundStatementBuilder&gt;} can be added as the <b>last</b>
 * parameter. It will be applied to the statement before execution. This allows you to customize
 * certain aspects of the request (page size, timeout, etc) at runtime.
 *
 * <h3>Return type</h3>
 *
 * The method can return:
 *
 * <ul>
 *   <li>{@code void}.
 *   <li>a {@code boolean} or {@link Boolean}, which will be mapped to {@link
 *       ResultSet#wasApplied()}. This is intended for conditional queries.
 *   <li>a {@code long} or {@link Long}, which will be mapped to the first column of the first row,
 *       expecting CQL type {@code BIGINT}. This is intended for count queries. The method will fail
 *       if the result set is empty, or does not match the expected format.
 *   <li>a {@link Row}. This means the result is not converted, the mapper only extracts the first
 *       row of the result set and returns it. The method will return {@code null} if the result set
 *       is empty.
 *   <li>a single instance of an {@link Entity} class. The method will extract the first row and
 *       convert it, or return {@code null} if the result set is empty.
 *   <li>an {@link Optional} of an entity class. The method will extract the first row and convert
 *       it, or return {@code Optional.empty()} if the result set is empty.
 *   <li>a {@link ResultSet}. The method will return the raw query result, without any conversion.
 *   <li>a {@link BoundStatement}. This is intended for cases where you intend to execute this
 *       statement later or in a batch:
 *   <li>a {@link PagingIterable}. The method will convert each row into an entity instance.
 *   <li>a {@link CompletionStage} or {@link CompletableFuture} of any of the above. The method will
 *       execute the query asynchronously. Note that for result sets and iterables, you need to
 *       switch to the asynchronous equivalent {@link AsyncResultSet} and {@link
 *       MappedAsyncPagingIterable} respectively.
 *   <li>a {@link ReactiveResultSet}, or a {@link MappedReactiveResultSet} of the entity class.
 *   <li>a {@linkplain MapperResultProducer custom type}.
 * </ul>
 *
 * <h3>Target keyspace and table</h3>
 *
 * To avoid hard-coding the keyspace and table name, the query string supports 3 additional
 * placeholders: {@code ${keyspaceId}}, {@code ${tableId}} and {@code ${qualifiedTableId}}. They get
 * substituted at DAO initialization time, with the keyspace and table that the DAO was built with
 * (see {@link DaoFactory}).
 *
 * <p>For example, given the following:
 *
 * <pre>
 * &#64;Dao
 * public interface TestDao {
 *   &#64;Query("SELECT * FROM ${keyspaceId}.${tableId}")
 *   ResultSet queryFromKeyspaceAndTable();
 *
 *   &#64;Query("SELECT * FROM ${qualifiedTableId}")
 *   ResultSet queryFromQualifiedTable();
 * }
 *
 * &#64;Mapper
 * public interface TestMapper {
 *   &#64;DaoFactory
 *   TestDao dao(&#64;DaoKeyspace String keyspace, &#64;DaoTable String table);
 *
 *   &#64;DaoFactory
 *   TestDao dao(&#64;DaoTable String table);
 * }
 *
 * TestDao dao1 = mapper.dao("ks", "t");
 * TestDao dao2 = mapper.dao("t");
 * </pre>
 *
 * Then:
 *
 * <ul>
 *   <li>{@code dao1.queryFromKeyspaceAndTable()} and {@code dao1.queryFromQualifiedTable()} both
 *       execute {@code SELECT * FROM ks.t}.
 *   <li>{@code dao2.queryFromKeyspaceAndTable()} fails: no keyspace was specified for this DAO, so
 *       {@code ${keyspaceId}} can't be substituted.
 *   <li>{@code dao1.queryFromQualifiedTable()} executes {@code SELECT * FROM t}. In other words,
 *       {@code ${qualifiedTableId}} uses the keyspace if it is available, but resolves to the table
 *       name only if it isn't. Whether the query succeeds or not depends on whether the {@link
 *       Session} that the mapper was built with has a {@linkplain
 *       SessionBuilder#withKeyspace(CqlIdentifier) default keyspace}.
 * </ul>
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {

  /**
   * The query string to execute.
   *
   * <p>It can contain CQL placeholders (e.g. {@code :id}) that will be bound with the method's
   * parameters; and also special text placeholders {@code ${keyspaceId}}, {@code ${tableId}} and
   * {@code ${qualifiedTableId}} that will be substituted with the keyspace and table that the DAO
   * was built with. See the top-level javadocs of this class for more explanations.
   */
  String value();

  /**
   * How to handle null query parameters.
   *
   * <p>This defaults either to the {@link DefaultNullSavingStrategy DAO-level strategy} (if set),
   * or {@link NullSavingStrategy#DO_NOT_SET}.
   */
  NullSavingStrategy nullSavingStrategy() default NullSavingStrategy.DO_NOT_SET;
}
