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

import com.datastax.oss.driver.internal.core.util.Strings;
import com.datastax.oss.driver.shaded.guava.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Locale;
import net.jcip.annotations.Immutable;

/**
 * The identifier of CQL element (keyspace, table, column, etc).
 *
 * <p>It has two representations:
 *
 * <ul>
 *   <li>the "CQL" form, which is how you would type the identifier in a CQL query. It is
 *       case-insensitive unless enclosed in double quotation marks; in addition, identifiers that
 *       contain special characters (anything other than alphanumeric and underscore), or match CQL
 *       keywords, must be double-quoted (with inner double quotes escaped as {@code ""}).
 *   <li>the "internal" form, which is how the name is stored in Cassandra system tables. It is
 *       lower-case for case-sensitive identifiers, and in the exact case for case-sensitive
 *       identifiers.
 * </ul>
 *
 * Examples:
 *
 * <table summary="examples">
 *   <tr><th>Create statement</th><th>Case-sensitive?</th><th>CQL id</th><th>Internal id</th></tr>
 *   <tr><td>CREATE TABLE t(foo int PRIMARY KEY)</td><td>No</td><td>foo</td><td>foo</td></tr>
 *   <tr><td>CREATE TABLE t(Foo int PRIMARY KEY)</td><td>No</td><td>foo</td><td>foo</td></tr>
 *   <tr><td>CREATE TABLE t("Foo" int PRIMARY KEY)</td><td>Yes</td><td>"Foo"</td><td>Foo</td></tr>
 *   <tr><td>CREATE TABLE t("foo bar" int PRIMARY KEY)</td><td>Yes</td><td>"foo bar"</td><td>foo bar</td></tr>
 *   <tr><td>CREATE TABLE t("foo""bar" int PRIMARY KEY)</td><td>Yes</td><td>"foo""bar"</td><td>foo"bar</td></tr>
 *   <tr><td>CREATE TABLE t("create" int PRIMARY KEY)</td><td>Yes (reserved keyword)</td><td>"create"</td><td>create</td></tr>
 * </table>
 *
 * This class provides a common representation and avoids any ambiguity about which form the
 * identifier is in. Driver clients will generally want to create instances from the CQL form with
 * {@link #fromCql(String)}.
 *
 * <p>There is no internal caching; if you reuse the same identifiers often, consider caching them
 * in your application.
 */
@Immutable
public class CqlIdentifier implements Serializable {

  private static final long serialVersionUID = 1;

  // IMPLEMENTATION NOTES:
  // This is used internally, and for all API methods where the overhead of requiring the client to
  // create an instance is acceptable (metadata, statement.getKeyspace, etc.)
  // One exception is named getters, where we keep raw strings with the 3.x rules.

  /** Creates an identifier from its {@link CqlIdentifier CQL form}. */
  @NonNull
  public static CqlIdentifier fromCql(@NonNull String cql) {
    Preconditions.checkNotNull(cql, "cql must not be null");
    final String internal;
    if (Strings.isDoubleQuoted(cql)) {
      internal = Strings.unDoubleQuote(cql);
    } else {
      internal = cql.toLowerCase(Locale.ROOT);
      Preconditions.checkArgument(
          !Strings.needsDoubleQuotes(internal), "Invalid CQL form [%s]: needs double quotes", cql);
    }
    return fromInternal(internal);
  }

  /** Creates an identifier from its {@link CqlIdentifier internal form}. */
  @NonNull
  public static CqlIdentifier fromInternal(@NonNull String internal) {
    Preconditions.checkNotNull(internal, "internal must not be null");
    return new CqlIdentifier(internal);
  }

  /** @serial */
  private final String internal;

  private CqlIdentifier(String internal) {
    this.internal = internal;
  }

  /**
   * Returns the identifier in the "internal" format.
   *
   * @return the identifier in its exact case, unquoted.
   */
  @NonNull
  public String asInternal() {
    return this.internal;
  }

  /**
   * Returns the identifier in a format appropriate for concatenation in a CQL query.
   *
   * @param pretty if {@code true}, use the shortest possible representation: if the identifier is
   *     case-insensitive, an unquoted, lower-case string, otherwise the double-quoted form. If
   *     {@code false}, always use the double-quoted form (this is slightly more efficient since we
   *     don't need to inspect the string).
   */
  @NonNull
  public String asCql(boolean pretty) {
    if (pretty) {
      return Strings.needsDoubleQuotes(internal) ? Strings.doubleQuote(internal) : internal;
    } else {
      return Strings.doubleQuote(internal);
    }
  }

  @Override
  public boolean equals(Object other) {
    if (other == this) {
      return true;
    } else if (other instanceof CqlIdentifier) {
      CqlIdentifier that = (CqlIdentifier) other;
      return this.internal.equals(that.internal);
    } else {
      return false;
    }
  }

  @Override
  public int hashCode() {
    return internal.hashCode();
  }

  @Override
  public String toString() {
    return internal;
  }

  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    Preconditions.checkNotNull(internal, "internal must not be null");
  }
}
