/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.schemabuilder;

import static com.datastax.driver.core.schemabuilder.SchemaStatement.validateNotEmpty;
import static com.datastax.driver.core.schemabuilder.SchemaStatement.validateNotKeyWord;

import com.datastax.driver.core.DataType;
import com.google.common.base.Optional;
import java.util.List;

/** An in-construction ALTER TABLE statement. */
public class Alter implements StatementStart {

  private Optional<String> keyspaceName = Optional.absent();
  private String tableName;

  Alter(String keyspaceName, String tableName) {
    validateNotEmpty(keyspaceName, "Keyspace name");
    validateNotEmpty(tableName, "Table name");
    validateNotKeyWord(
        keyspaceName,
        String.format(
            "The keyspace name '%s' is not allowed because it is a reserved keyword",
            keyspaceName));
    validateNotKeyWord(
        tableName,
        String.format(
            "The table name '%s' is not allowed because it is a reserved keyword", tableName));
    this.tableName = tableName;
    this.keyspaceName = Optional.fromNullable(keyspaceName);
  }

  Alter(String tableName) {
    validateNotEmpty(tableName, "Table name");
    validateNotKeyWord(
        tableName,
        String.format(
            "The table name '%s' is not allowed because it is a reserved keyword", tableName));
    this.tableName = tableName;
  }

  /**
   * Add an ALTER column clause (to change the column type) to this ALTER TABLE statement.
   *
   * @param columnName the name of the column to be altered.
   * @return a new {@link Alter.AlterColumn} instance.
   */
  public AlterColumn alterColumn(String columnName) {
    validateNotEmpty(columnName, "Column to be altered");
    validateNotKeyWord(
        columnName,
        String.format(
            "The altered column name '%s' is not allowed because it is a reserved keyword",
            columnName));
    return new AlterColumn(this, columnName);
  }

  /**
   * Add a new ADD column clause to this ALTER TABLE statement.
   *
   * @param columnName the name of the column to be added.
   * @return a new {@link Alter.AddColumn} instance.
   */
  public AddColumn addColumn(String columnName) {
    validateNotEmpty(columnName, "Added column");
    validateNotKeyWord(
        columnName,
        String.format(
            "The new column name '%s' is not allowed because it is a reserved keyword",
            columnName));
    return new AddColumn(this, columnName, false);
  }

  /**
   * Add a new ADD column clause to this ALTER TABLE statement, to add a static column.
   *
   * @param columnName the name of the column to be added.
   * @return a new {@link Alter.AddColumn} instance.
   */
  public AddColumn addStaticColumn(String columnName) {
    validateNotEmpty(columnName, "Added static column");
    validateNotKeyWord(
        columnName,
        String.format(
            "The new static column name '%s' is not allowed because it is a reserved keyword",
            columnName));
    return new AddColumn(this, columnName, true);
  }

  /**
   * Add a new DROP column clause to this ALTER TABLE statement.
   *
   * <p>Note that you cannot drop a column that is part of the primary key.
   *
   * @param columnName the name of the column to be dropped.
   * @return the final ALTER TABLE DROP COLUMN statement.
   */
  public SchemaStatement dropColumn(String columnName) {
    validateNotEmpty(columnName, "Column to be dropped");
    validateNotKeyWord(
        columnName,
        String.format(
            "The dropped column name '%s' is not allowed because it is a reserved keyword",
            columnName));
    return SchemaStatement.fromQueryString(buildInternal() + " DROP " + columnName);
  }

  /**
   * Add a new RENAME column clause to this ALTER TABLE statement.
   *
   * <p>Note that you can only rename a column that is part of the primary key.
   *
   * @param columnName the name of the column to be renamed.
   * @return a new {@link Alter.RenameColumn} instance.
   */
  public RenameColumn renameColumn(String columnName) {
    validateNotEmpty(columnName, "Column to be renamed");
    validateNotKeyWord(
        columnName,
        String.format(
            "The renamed column name '%s' is not allowed because it is a reserved keyword",
            columnName));
    return new RenameColumn(this, columnName);
  }

  /**
   * Add options (WITH clause) to this ALTER TABLE statement.
   *
   * @return a new {@link Alter.Options} instance.
   */
  public Options withOptions() {
    return new Options(this);
  }

  /** An ALTER column clause. */
  public static class AlterColumn {

    private final Alter alter;
    private final String columnName;

    AlterColumn(Alter alter, String columnName) {
      this.alter = alter;
      this.columnName = columnName;
    }

    /**
     * Define the new type of the altered column.
     *
     * @param type the new type of the altered column.
     * @return the final statement.
     */
    public SchemaStatement type(DataType type) {
      return SchemaStatement.fromQueryString(
          alter.buildInternal() + " ALTER " + columnName + " TYPE " + type.toString());
    }

    /**
     * Define the new type of the altered column, when that type contains a UDT.
     *
     * @param udtType the UDT type. Use {@link SchemaBuilder#frozen(String)} or {@link
     *     SchemaBuilder#udtLiteral(String)}.
     * @return the final statement.
     */
    public SchemaStatement udtType(UDTType udtType) {
      return SchemaStatement.fromQueryString(
          alter.buildInternal() + " ALTER " + columnName + " TYPE " + udtType.asCQLString());
    }
  }

  /** An ADD column clause. */
  public static class AddColumn {

    private final Alter alter;
    private final String columnName;
    private final boolean staticColumn;

    AddColumn(Alter alter, String columnName, boolean staticColumn) {
      this.alter = alter;
      this.columnName = columnName;
      this.staticColumn = staticColumn;
    }

    /**
     * Define the type of the added column.
     *
     * @param type the type of the added column.
     * @return the final statement.
     */
    public SchemaStatement type(DataType type) {
      return SchemaStatement.fromQueryString(
          alter.buildInternal()
              + " ADD "
              + columnName
              + " "
              + type.toString()
              + (staticColumn ? " static" : ""));
    }

    /**
     * Define the type of the added column, when that type contains a UDT.
     *
     * @param udtType the UDT type of the added column.
     * @return the final statement.
     */
    public SchemaStatement udtType(UDTType udtType) {
      return SchemaStatement.fromQueryString(
          alter.buildInternal()
              + " ADD "
              + columnName
              + " "
              + udtType.asCQLString()
              + (staticColumn ? " static" : ""));
    }
  }

  /** A RENAME column clause. */
  public static class RenameColumn {

    private final Alter alter;
    private final String columnName;

    RenameColumn(Alter alter, String columnName) {
      this.alter = alter;
      this.columnName = columnName;
    }

    /**
     * Define the new name of the column.
     *
     * @param newColumnName the new name of the column.
     * @return the final statement.
     */
    public SchemaStatement to(String newColumnName) {
      validateNotEmpty(newColumnName, "New column name");
      validateNotKeyWord(
          newColumnName,
          String.format(
              "The new column name '%s' is not allowed because it is a reserved keyword",
              newColumnName));
      return SchemaStatement.fromQueryString(
          alter.buildInternal() + " RENAME " + columnName + " TO " + newColumnName);
    }
  }

  /** The table options of an ALTER TABLE statement. */
  public static class Options extends TableOptions<Options> {

    Options(Alter alter) {
      super(alter);
    }

    @Override
    protected void addSpecificOptions(List<String> options) {
      // nothing to do (no specific options)
    }
  }

  @Override
  public String buildInternal() {
    String tableSpec = keyspaceName.isPresent() ? keyspaceName.get() + "." + tableName : tableName;

    return SchemaStatement.STATEMENT_START + "ALTER TABLE " + tableSpec;
  }
}
