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

import com.datastax.oss.driver.api.core.AllNodesFailedException;
import com.datastax.oss.driver.api.core.servererrors.QueryExecutionException;
import com.datastax.oss.driver.api.core.servererrors.QueryValidationException;
import com.datastax.oss.driver.api.core.servererrors.SyntaxError;
import com.datastax.oss.driver.api.core.session.Request;
import com.datastax.oss.driver.api.core.session.Session;
import com.datastax.oss.driver.internal.core.cql.DefaultPrepareRequest;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Map;
import java.util.Objects;

/**
 * A session that offers user-friendly methods to execute CQL requests synchronously.
 *
 * @since 4.4.0
 */
public interface SyncCqlSession extends Session {

  /**
   * Executes a CQL statement synchronously (the calling thread blocks until the result becomes
   * available).
   *
   * @param statement the CQL query to execute (that can be any {@link Statement}).
   * @return the result of the query. That result will never be null but can be empty (and will be
   *     for any non SELECT query).
   * @throws AllNodesFailedException if no host in the cluster can be contacted successfully to
   *     execute this query.
   * @throws QueryExecutionException if the query triggered an execution exception, i.e. an
   *     exception thrown by Cassandra when it cannot execute the query with the requested
   *     consistency level successfully.
   * @throws QueryValidationException if the query is invalid (syntax error, unauthorized or any
   *     other validation problem).
   */
  @NonNull
  default ResultSet execute(@NonNull Statement<?> statement) {
    return Objects.requireNonNull(
        execute(statement, Statement.SYNC), "The CQL processor should never return a null result");
  }

  /**
   * Executes a CQL statement synchronously (the calling thread blocks until the result becomes
   * available).
   *
   * <p>This is an alias for {@link #execute(Statement)
   * execute(SimpleStatement.newInstance(query))}.
   *
   * @param query the CQL query to execute.
   * @return the result of the query. That result will never be null but can be empty (and will be
   *     for any non SELECT query).
   * @throws AllNodesFailedException if no host in the cluster can be contacted successfully to
   *     execute this query.
   * @throws QueryExecutionException if the query triggered an execution exception, i.e. an
   *     exception thrown by Cassandra when it cannot execute the query with the requested
   *     consistency level successfully.
   * @throws QueryValidationException if the query if invalid (syntax error, unauthorized or any
   *     other validation problem).
   * @see SimpleStatement#newInstance(String)
   */
  @NonNull
  default ResultSet execute(@NonNull String query) {
    return execute(SimpleStatement.newInstance(query));
  }

  /**
   * Executes a CQL statement synchronously (the calling thread blocks until the result becomes
   * available).
   *
   * <p>This is an alias for {@link #execute(Statement) execute(SimpleStatement.newInstance(query,
   * values))}.
   *
   * @param query the CQL query to execute.
   * @param values the values for placeholders in the query string. Individual values can be {@code
   *     null}, but the vararg array itself can't.
   * @return the result of the query. That result will never be null but can be empty (and will be
   *     for any non SELECT query).
   * @throws AllNodesFailedException if no host in the cluster can be contacted successfully to
   *     execute this query.
   * @throws QueryExecutionException if the query triggered an execution exception, i.e. an
   *     exception thrown by Cassandra when it cannot execute the query with the requested
   *     consistency level successfully.
   * @throws QueryValidationException if the query if invalid (syntax error, unauthorized or any
   *     other validation problem).
   * @see SimpleStatement#newInstance(String, Object...)
   */
  @NonNull
  default ResultSet execute(@NonNull String query, @NonNull Object... values) {
    return execute(SimpleStatement.newInstance(query, values));
  }

  /**
   * Executes a CQL statement synchronously (the calling thread blocks until the result becomes
   * available).
   *
   * <p>This is an alias for {@link #execute(Statement) execute(SimpleStatement.newInstance(query,
   * values))}.
   *
   * @param query the CQL query to execute.
   * @param values the values for named placeholders in the query string. Individual values can be
   *     {@code null}, but the map itself can't.
   * @return the result of the query. That result will never be null but can be empty (and will be
   *     for any non SELECT query).
   * @throws AllNodesFailedException if no host in the cluster can be contacted successfully to
   *     execute this query.
   * @throws QueryExecutionException if the query triggered an execution exception, i.e. an
   *     exception thrown by Cassandra when it cannot execute the query with the requested
   *     consistency level successfully.
   * @throws QueryValidationException if the query if invalid (syntax error, unauthorized or any
   *     other validation problem).
   * @see SimpleStatement#newInstance(String, Map)
   */
  @NonNull
  default ResultSet execute(@NonNull String query, @NonNull Map<String, Object> values) {
    return execute(SimpleStatement.newInstance(query, values));
  }

  /**
   * Prepares a CQL statement synchronously (the calling thread blocks until the statement is
   * prepared).
   *
   * <p>Note that the bound statements created from the resulting prepared statement will inherit
   * some of the attributes of the provided simple statement. That is, given:
   *
   * <pre>{@code
   * SimpleStatement simpleStatement = SimpleStatement.newInstance("...");
   * PreparedStatement preparedStatement = session.prepare(simpleStatement);
   * BoundStatement boundStatement = preparedStatement.bind();
   * }</pre>
   *
   * Then:
   *
   * <ul>
   *   <li>the following methods return the same value as their counterpart on {@code
   *       simpleStatement}:
   *       <ul>
   *         <li>{@link Request#getExecutionProfileName() boundStatement.getExecutionProfileName()}
   *         <li>{@link Request#getExecutionProfile() boundStatement.getExecutionProfile()}
   *         <li>{@link Statement#getPagingState() boundStatement.getPagingState()}
   *         <li>{@link Request#getRoutingKey() boundStatement.getRoutingKey()}
   *         <li>{@link Request#getRoutingToken() boundStatement.getRoutingToken()}
   *         <li>{@link Request#getCustomPayload() boundStatement.getCustomPayload()}
   *         <li>{@link Request#isIdempotent() boundStatement.isIdempotent()}
   *         <li>{@link Request#getTimeout() boundStatement.getTimeout()}
   *         <li>{@link Statement#getPagingState() boundStatement.getPagingState()}
   *         <li>{@link Statement#getPageSize() boundStatement.getPageSize()}
   *         <li>{@link Statement#getConsistencyLevel() boundStatement.getConsistencyLevel()}
   *         <li>{@link Statement#getSerialConsistencyLevel()
   *             boundStatement.getSerialConsistencyLevel()}
   *         <li>{@link Statement#isTracing() boundStatement.isTracing()}
   *       </ul>
   *   <li>{@link Request#getRoutingKeyspace() boundStatement.getRoutingKeyspace()} is set from
   *       either {@link Request#getKeyspace() simpleStatement.getKeyspace()} (if it's not {@code
   *       null}), or {@code simpleStatement.getRoutingKeyspace()};
   *   <li>on the other hand, the following attributes are <b>not</b> propagated:
   *       <ul>
   *         <li>{@link Statement#getQueryTimestamp() boundStatement.getQueryTimestamp()} will be
   *             set to {@link Statement#NO_DEFAULT_TIMESTAMP}, meaning that the value will be
   *             assigned by the session's timestamp generator.
   *         <li>{@link Statement#getNode() boundStatement.getNode()} will always be {@code null}.
   *         <li>{@link Statement#getNowInSeconds()} boundStatement.getNowInSeconds()} will always
   *             be equal to {@link Statement#NO_NOW_IN_SECONDS}.
   *       </ul>
   * </ul>
   *
   * If you want to customize this behavior, you can write your own implementation of {@link
   * PrepareRequest} and pass it to {@link #prepare(PrepareRequest)}.
   *
   * <p>The result of this method is cached: if you call it twice with the same {@link
   * SimpleStatement}, you will get the same {@link PreparedStatement} instance. We still recommend
   * keeping a reference to it (for example by caching it as a field in a DAO); if that's not
   * possible (e.g. if query strings are generated dynamically), it's OK to call this method every
   * time: there will just be a small performance overhead to check the internal cache. Note that
   * caching is based on:
   *
   * <ul>
   *   <li>the query string exactly as you provided it: the driver does not perform any kind of
   *       trimming or sanitizing.
   *   <li>all other execution parameters: for example, preparing two statements with identical
   *       query strings but different {@linkplain SimpleStatement#getConsistencyLevel() consistency
   *       levels} will yield distinct prepared statements.
   * </ul>
   *
   * @param statement the CQL query to execute (that can be any {@link SimpleStatement}).
   * @return the prepared statement corresponding to {@code statement}.
   * @throws SyntaxError if the syntax of the query to prepare is not correct.
   */
  @NonNull
  default PreparedStatement prepare(@NonNull SimpleStatement statement) {
    return Objects.requireNonNull(
        execute(new DefaultPrepareRequest(statement), PrepareRequest.SYNC),
        "The CQL prepare processor should never return a null result");
  }

  /**
   * Prepares a CQL statement synchronously (the calling thread blocks until the statement is
   * prepared).
   *
   * <p>The result of this method is cached (see {@link #prepare(SimpleStatement)} for more
   * explanations).
   *
   * @param query the CQL string query to execute.
   * @return the prepared statement corresponding to {@code query}.
   * @throws SyntaxError if the syntax of the query to prepare is not correct.
   */
  @NonNull
  default PreparedStatement prepare(@NonNull String query) {
    return Objects.requireNonNull(
        execute(new DefaultPrepareRequest(query), PrepareRequest.SYNC),
        "The CQL prepare processor should never return a null result");
  }

  /**
   * Prepares a CQL statement synchronously (the calling thread blocks until the statement is
   * prepared).
   *
   * <p>This variant is exposed in case you use an ad hoc {@link PrepareRequest} implementation to
   * customize how attributes are propagated when you prepare a {@link SimpleStatement} (see {@link
   * #prepare(SimpleStatement)} for more explanations). Otherwise, you should rarely have to deal
   * with {@link PrepareRequest} directly.
   *
   * <p>The result of this method is cached (see {@link #prepare(SimpleStatement)} for more
   * explanations).
   *
   * @param request the {@code PrepareRequest} to execute.
   * @return the prepared statement corresponding to {@code request}.
   * @throws SyntaxError if the syntax of the query to prepare is not correct.
   */
  @NonNull
  default PreparedStatement prepare(@NonNull PrepareRequest request) {
    return Objects.requireNonNull(
        execute(request, PrepareRequest.SYNC),
        "The CQL prepare processor should never return a null result");
  }
}
