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

import com.datastax.driver.core.exceptions.InvalidTypeException;
import com.datastax.driver.core.policies.RetryPolicy;

import java.nio.ByteBuffer;
import java.util.Map;

/**
 * Represents a prepared statement, a query with bound variables that has been
 * prepared (pre-parsed) by the database.
 * <p/>
 * A prepared statement can be executed once concrete values have been provided
 * for the bound variables. A prepared statement and the values for its
 * bound variables constitute a BoundStatement and can be executed (by
 * {@link Session#execute}).
 * <p/>
 * A {@code PreparedStatement} object allows you to define specific defaults
 * for the different properties of a {@link Statement} (Consistency level, tracing, ...),
 * in which case those properties will be inherited as default by every
 * BoundedStatement created from the {PreparedStatement}. The default for those
 * {@code PreparedStatement} properties is the same that in {@link Statement} if the
 * PreparedStatement is created by {@link Session#prepare(String)} but will inherit
 * of the properties of the {@link RegularStatement} used for the preparation if
 * {@link Session#prepare(RegularStatement)} is used.
 */
public interface PreparedStatement {

    /**
     * Returns metadata on the bounded variables of this prepared statement.
     *
     * @return the variables bounded in this prepared statement.
     */
    public ColumnDefinitions getVariables();

    /**
     * Creates a new BoundStatement object and bind its variables to the
     * provided values.
     * <p/>
     * While the number of {@code values} cannot be greater than the number of bound
     * variables, the number of {@code values} may be fewer than the number of bound
     * variables. In that case, the remaining variables will have to be bound
     * to values by another mean because the resulting {@code BoundStatement}
     * being executable.
     * <p/>
     * This method is a convenience for {@code new BoundStatement(this).bind(...)}.
     *
     * @param values the values to bind to the variables of the newly created
     *               BoundStatement.
     * @return the newly created {@code BoundStatement} with its variables
     * bound to {@code values}.
     * @throws IllegalArgumentException if more {@code values} are provided
     *                                  than there is of bound variables in this statement.
     * @throws InvalidTypeException     if any of the provided value is not of
     *                                  correct type to be bound to the corresponding bind variable.
     * @throws NullPointerException     if one of {@code values} is a collection
     *                                  (List, Set or Map) containing a null value. Nulls are not supported in
     *                                  collections by CQL.
     * @see BoundStatement#bind
     */
    public BoundStatement bind(Object... values);

    /**
     * Creates a new BoundStatement object for this prepared statement.
     * <p/>
     * This method do not bind any values to any of the prepared variables. Said
     * values need to be bound on the resulting statement using BoundStatement's
     * setters methods ({@link BoundStatement#setInt}, {@link BoundStatement#setLong}, ...).
     *
     * @return the newly created {@code BoundStatement}.
     */
    public BoundStatement bind();

    /**
     * Sets the routing key for this prepared statement.
     * <p/>
     * While you can provide a fixed routing key for all executions of this prepared
     * statement with this method, it is not mandatory to provide
     * one through this method. This method should only be used
     * if the partition key of the prepared query is not part of the prepared
     * variables (that is if the partition key is fixed).
     * <p/>
     * Note that if the partition key is part of the prepared variables, the
     * routing key will be automatically computed once those variables are bound.
     * <p/>
     * If the partition key is neither fixed nor part of the prepared variables (e.g.
     * a composite partition key where only some of the components are bound), the
     * routing key can also be set on each bound statement.
     *
     * @param routingKey the raw (binary) value to use as routing key.
     * @return this {@code PreparedStatement} object.
     * @see Statement#getRoutingKey
     * @see BoundStatement#getRoutingKey
     */
    public PreparedStatement setRoutingKey(ByteBuffer routingKey);

    /**
     * Sets the routing key for this query.
     * <p/>
     * See {@link #setRoutingKey(ByteBuffer)} for more information. This
     * method is a variant for when the query partition key is composite and
     * the routing key must be built from multiple values.
     *
     * @param routingKeyComponents the raw (binary) values to compose to obtain
     *                             the routing key.
     * @return this {@code PreparedStatement} object.
     * @see Statement#getRoutingKey
     */
    public PreparedStatement setRoutingKey(ByteBuffer... routingKeyComponents);

    /**
     * Returns the routing key set for this query.
     *
     * @return the routing key for this query or {@code null} if none has been
     * explicitly set on this PreparedStatement.
     */
    public ByteBuffer getRoutingKey();

    /**
     * Sets a default consistency level for all bound statements
     * created from this prepared statement.
     * <p/>
     * If no consistency level is set through this method, the bound statement
     * created from this object will use the default consistency level (ONE).
     * <p/>
     * Changing the default consistency level is not retroactive, it only
     * applies to BoundStatement created after the change.
     *
     * @param consistency the default consistency level to set.
     * @return this {@code PreparedStatement} object.
     */
    public PreparedStatement setConsistencyLevel(ConsistencyLevel consistency);

    /**
     * Returns the default consistency level set through {@link #setConsistencyLevel}.
     *
     * @return the default consistency level. Returns {@code null} if no
     * consistency level has been set through this object {@code setConsistencyLevel}
     * method.
     */
    public ConsistencyLevel getConsistencyLevel();

    /**
     * Sets a default serial consistency level for all bound statements
     * created from this prepared statement.
     * <p/>
     * If no serial consistency level is set through this method, the bound statement
     * created from this object will use the default serial consistency level (SERIAL).
     * <p/>
     * Changing the default serial consistency level is not retroactive, it only
     * applies to BoundStatement created after the change.
     *
     * @param serialConsistency the default serial consistency level to set.
     * @return this {@code PreparedStatement} object.
     * @throws IllegalArgumentException if {@code serialConsistency} is not one of
     *                                  {@code ConsistencyLevel.SERIAL} or {@code ConsistencyLevel.LOCAL_SERIAL}.
     */
    public PreparedStatement setSerialConsistencyLevel(ConsistencyLevel serialConsistency);

    /**
     * Returns the default serial consistency level set through {@link #setSerialConsistencyLevel}.
     *
     * @return the default serial consistency level. Returns {@code null} if no
     * consistency level has been set through this object {@code setSerialConsistencyLevel}
     * method.
     */
    public ConsistencyLevel getSerialConsistencyLevel();

    /**
     * Returns the string of the query that was prepared to yield this {@code
     * PreparedStatement}.
     * <p/>
     * Note that a CQL3 query may be implicitly applied to the current keyspace
     * (that is, if the keyspace is not explicitly qualified in the query
     * itself). For prepared queries, the current keyspace used is the one at
     * the time of the preparation, not the one at execution time. The current
     * keyspace at the time of the preparation can be retrieved through
     * {@link #getQueryKeyspace}.
     *
     * @return the query that was prepared to yield this
     * {@code PreparedStatement}.
     */
    public String getQueryString();

    /**
     * Returns the keyspace at the time that this prepared statement was prepared,
     * (that is the one on which this statement applies unless it specified a
     * keyspace explicitly).
     *
     * @return the keyspace at the time that this statement was prepared or
     * {@code null} if no keyspace was set when the query was prepared (which
     * is possible since keyspaces can be explicitly qualified in queries and
     * so may not require a current keyspace to be set).
     */
    public String getQueryKeyspace();

    /**
     * Convenience method to enables tracing for all bound statements created
     * from this prepared statement.
     *
     * @return this {@code Query} object.
     */
    public PreparedStatement enableTracing();

    /**
     * Convenience method to disable tracing for all bound statements created
     * from this prepared statement.
     *
     * @return this {@code PreparedStatement} object.
     */
    public PreparedStatement disableTracing();

    /**
     * Returns whether tracing is enabled for this prepared statement, i.e. if
     * BoundStatement created from it will use tracing by default.
     *
     * @return {@code true} if this prepared statement has tracing enabled,
     * {@code false} otherwise.
     */
    public boolean isTracing();

    /**
     * Convenience method to set a default retry policy for the {@code BoundStatement}
     * created from this prepared statement.
     * <p/>
     * Note that this method is completely optional. By default, the retry policy
     * used is the one returned {@link com.datastax.driver.core.policies.Policies#getRetryPolicy}
     * in the cluster configuration. This method is only useful if you want
     * to override this default policy for the {@code BoundStatement} created from
     * this {@code PreparedStatement}.
     * to punctually override the default policy for this request.
     *
     * @param policy the retry policy to use for this prepared statement.
     * @return this {@code PreparedStatement} object.
     */
    public PreparedStatement setRetryPolicy(RetryPolicy policy);

    /**
     * Returns the retry policy sets for this prepared statement, if any.
     *
     * @return the retry policy sets specifically for this prepared statement or
     * {@code null} if none have been set.
     */
    public RetryPolicy getRetryPolicy();

    /**
     * Returns the prepared Id for this statement.
     *
     * @return the PreparedId corresponding to this statement.
     */
    public PreparedId getPreparedId();

    /**
     * Return the incoming payload, that is, the payload that the server
     * sent back with its {@code PREPARED} response, if any,
     * or {@code null}, if the server did not include any custom payload.
     * <p/>
     * Note that if an incoming payload is present,
     * and if no outgoing payload has been {@link #setOutgoingPayload(Map) explicitly set}, then each time
     * a {@link BoundStatement} is created (with either {@link #bind()} or {@link #bind(Object...)}),
     * the resulting {@link BoundStatement} will inherit from this value
     * as its default outgoing payload.
     * <p/>
     * Implementors should return a read-only view of the original map, but even though,
     * its values would remain inherently mutable.
     * Callers should take care not to modify the returned map in any way.
     * <p/>
     * This feature is only available with {@link ProtocolVersion#V4} or above; with lower
     * versions, this method will always return {@code null}.
     *
     * @return the custom payload that the server sent back with its response, if any,
     * or {@code null}, if the server did not include any custom payload.
     * @since 2.2
     */
    public Map<String, ByteBuffer> getIncomingPayload();

    /**
     * Return the outgoing payload currently associated with this statement.
     * <p/>
     * If this is set to a non-null value, each time a {@link BoundStatement} is
     * created (with either {@link #bind()} or {@link #bind(Object...)}),
     * the resulting {@link BoundStatement} will inherit from this value
     * as its default outgoing payload.
     * <p/>
     * Implementors should return a read-only view of the original map, but even though,
     * its values would remain inherently mutable.
     * Callers should take care not to modify the returned map in any way.
     * <p/>
     * This feature is only available with {@link ProtocolVersion#V4} or above.
     *
     * @return this statement's outgoing payload, if any, or {@code null} if no outgoing payload is set
     * @since 2.2
     */
    public Map<String, ByteBuffer> getOutgoingPayload();

    /**
     * Associate the given payload with this prepared statement.
     * <p/>
     * If this is set to a non-null value, each time a {@link BoundStatement} is
     * created (with either {@link #bind()} or {@link #bind(Object...)}),
     * the resulting {@link BoundStatement} will inherit from this value
     * as its default outgoing payload.
     * <p/>
     * Implementors should make a defensive, thread-safe copy of the given map, but even though,
     * its values would remain inherently mutable.
     * Callers should take care not to modify the original map once it is passed to this method.
     * <p/>
     * This feature is only available with {@link ProtocolVersion#V4} or above.
     * Trying to include custom payloads in requests sent by the driver
     * under lower protocol versions will result in an
     * {@link com.datastax.driver.core.exceptions.UnsupportedFeatureException}
     * (wrapped in a {@link com.datastax.driver.core.exceptions.NoHostAvailableException}).
     *
     * @param payload the outgoing payload to associate with this statement,
     *                or {@code null} to clear any previously associated payload.
     * @return this {@link Statement} object.
     * @since 2.2
     */
    public PreparedStatement setOutgoingPayload(Map<String, ByteBuffer> payload);

    /**
     * Return the {@link CodecRegistry} instance associated with this prepared statement.
     * Implementations should never return {@code null}; instead, they should always return
     * the {@link CodecRegistry} instance registered with the {@link Cluster} instance
     * this prepared statement belongs to.
     *
     * @return the {@link CodecRegistry} instance associated with this prepared statement.
     */
    public CodecRegistry getCodecRegistry();

    /**
     * Sets whether this statement is idempotent.
     * <p/>
     * See {@link com.datastax.driver.core.Statement#isIdempotent} for more explanations about this property.
     *
     * @param idempotent the new value.
     * @return this {@code IdempotenceAwarePreparedStatement} object.
     */
    public PreparedStatement setIdempotent(Boolean idempotent);

    /**
     * Whether this statement is idempotent, i.e. whether it can be applied multiple times
     * without changing the result beyond the initial application.
     * <p/>
     * See {@link com.datastax.driver.core.Statement#isIdempotent} for more explanations about this property.
     * <p/>
     * Please note that idempotence will be propagated to all {@link BoundStatement}s created from this prepared statement.
     *
     * @return whether this statement is idempotent, or {@code null} to use
     * {@link QueryOptions#getDefaultIdempotence()}.
     */
    public Boolean isIdempotent();

}
