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

import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.cql.Statement;
import com.datastax.oss.driver.api.querybuilder.BuildableQuery;
import com.datastax.oss.driver.api.querybuilder.CqlSnippet;
import com.datastax.oss.driver.api.querybuilder.term.Term;
import com.datastax.oss.driver.internal.core.CqlIdentifiers;
import com.datastax.oss.driver.internal.querybuilder.relation.CustomIndexRelation;
import com.datastax.oss.driver.internal.querybuilder.relation.DefaultColumnComponentRelationBuilder;
import com.datastax.oss.driver.internal.querybuilder.relation.DefaultColumnRelationBuilder;
import com.datastax.oss.driver.internal.querybuilder.relation.DefaultMultiColumnRelationBuilder;
import com.datastax.oss.driver.internal.querybuilder.relation.DefaultTokenRelationBuilder;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Arrays;

/**
 * A relation in a WHERE clause.
 *
 * <p>To build instances of this type, use the factory methods, such as {@link #column(String)
 * column}, {@link #token(String...) token}, etc.
 *
 * <p>They are used as arguments to the {@link OngoingWhereClause#where(Iterable) where} method, for
 * example:
 *
 * <pre>{@code
 * selectFrom("foo").all().where(Relation.column("k").isEqualTo(literal(1)))
 * // SELECT * FROM foo WHERE k=1
 * }</pre>
 *
 * There are also shortcuts in the fluent API when you build a statement, for example:
 *
 * <pre>{@code
 * selectFrom("foo").all().whereColumn("k").isEqualTo(literal(1))
 * // SELECT * FROM foo WHERE k=1
 * }</pre>
 */
public interface Relation extends CqlSnippet {

  /**
   * Builds a relation testing a column.
   *
   * <p>This must be chained with an operator call, for example:
   *
   * <pre>{@code
   * Relation r = Relation.column("k").isEqualTo(bindMarker());
   * }</pre>
   */
  @NonNull
  static ColumnRelationBuilder<Relation> column(@NonNull CqlIdentifier id) {
    return new DefaultColumnRelationBuilder(id);
  }

  /** Shortcut for {@link #column(CqlIdentifier) column(CqlIdentifier.fromCql(name))} */
  @NonNull
  static ColumnRelationBuilder<Relation> column(@NonNull String name) {
    return column(CqlIdentifier.fromCql(name));
  }

  /** Builds a relation testing a value in a map (Cassandra 4 and above). */
  @NonNull
  static ColumnComponentRelationBuilder<Relation> mapValue(
      @NonNull CqlIdentifier columnId, @NonNull Term index) {
    // The concept could easily be extended to list elements and tuple components, so use a generic
    // name internally, we'll add other shortcuts if necessary.
    return new DefaultColumnComponentRelationBuilder(columnId, index);
  }

  /**
   * Shortcut for {@link #mapValue(CqlIdentifier, Term) mapValue(CqlIdentifier.fromCql(columnName),
   * index)}
   */
  @NonNull
  static ColumnComponentRelationBuilder<Relation> mapValue(
      @NonNull String columnName, @NonNull Term index) {
    return mapValue(CqlIdentifier.fromCql(columnName), index);
  }

  /** Builds a relation testing a token generated from a set of columns. */
  @NonNull
  static TokenRelationBuilder<Relation> tokenFromIds(@NonNull Iterable<CqlIdentifier> identifiers) {
    return new DefaultTokenRelationBuilder(identifiers);
  }

  /** Var-arg equivalent of {@link #tokenFromIds(Iterable)}. */
  @NonNull
  static TokenRelationBuilder<Relation> token(@NonNull CqlIdentifier... identifiers) {
    return tokenFromIds(Arrays.asList(identifiers));
  }

  /**
   * Equivalent of {@link #tokenFromIds(Iterable)} with raw strings; the names are converted with
   * {@link CqlIdentifier#fromCql(String)}.
   */
  @NonNull
  static TokenRelationBuilder<Relation> token(@NonNull Iterable<String> names) {
    return tokenFromIds(CqlIdentifiers.wrap(names));
  }

  /** Var-arg equivalent of {@link #token(Iterable)}. */
  @NonNull
  static TokenRelationBuilder<Relation> token(@NonNull String... names) {
    return token(Arrays.asList(names));
  }

  /** Builds a multi-column relation, as in {@code WHERE (c1, c2, c3) IN ...}. */
  @NonNull
  static MultiColumnRelationBuilder<Relation> columnIds(
      @NonNull Iterable<CqlIdentifier> identifiers) {
    return new DefaultMultiColumnRelationBuilder(identifiers);
  }

  /** Var-arg equivalent of {@link #columnIds(Iterable)}. */
  @NonNull
  static MultiColumnRelationBuilder<Relation> columns(@NonNull CqlIdentifier... identifiers) {
    return columnIds(Arrays.asList(identifiers));
  }

  /**
   * Equivalent of {@link #columnIds(Iterable)} with raw strings; the names are converted with
   * {@link CqlIdentifier#fromCql(String)}.
   */
  @NonNull
  static MultiColumnRelationBuilder<Relation> columns(@NonNull Iterable<String> names) {
    return columnIds(CqlIdentifiers.wrap(names));
  }

  /** Var-arg equivalent of {@link #columns(Iterable)}. */
  @NonNull
  static MultiColumnRelationBuilder<Relation> columns(@NonNull String... names) {
    return columns(Arrays.asList(names));
  }

  /** Builds a relation on a custom index. */
  @NonNull
  static Relation customIndex(@NonNull CqlIdentifier indexId, @NonNull Term expression) {
    return new CustomIndexRelation(indexId, expression);
  }

  /**
   * Shortcut for {@link #customIndex(CqlIdentifier, Term)
   * customIndex(CqlIdentifier.fromCql(indexName), expression)}
   */
  @NonNull
  static Relation customIndex(@NonNull String indexName, @NonNull Term expression) {
    return customIndex(CqlIdentifier.fromCql(indexName), expression);
  }

  /**
   * Whether this relation is idempotent.
   *
   * <p>That is, whether it always selects the same rows when used multiple times. For example,
   * {@code WHERE c=1} is idempotent, {@code WHERE c=now()} isn't.
   *
   * <p>This is used internally by the query builder to compute the {@link Statement#isIdempotent()}
   * flag on the UPDATE and DELETE statements generated by {@link BuildableQuery#build()} (this is
   * not relevant for SELECT statement, which are always idempotent). If a term is ambiguous (for
   * example a raw snippet or a call to a user function in the right operands), the builder is
   * pessimistic and assumes the term is not idempotent.
   */
  boolean isIdempotent();
}
