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

import com.datastax.driver.core.utils.MoreObjects;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/** Describes a Column. */
public class ColumnMetadata {

  static final String COLUMN_NAME = "column_name";

  static final String VALIDATOR = "validator"; // v2 only
  static final String TYPE = "type"; // replaces validator, v3 onwards

  static final String COMPONENT_INDEX = "component_index"; // v2 only
  static final String POSITION = "position"; // replaces component_index, v3 onwards

  static final String KIND_V2 = "type"; // v2 only
  static final String KIND_V3 = "kind"; // replaces type, v3 onwards

  static final String CLUSTERING_ORDER = "clustering_order";
  static final String DESC = "desc";

  static final String INDEX_TYPE = "index_type";
  static final String INDEX_OPTIONS = "index_options";
  static final String INDEX_NAME = "index_name";

  private final AbstractTableMetadata parent;
  private final String name;
  private final DataType type;
  private final boolean isStatic;

  private ColumnMetadata(
      AbstractTableMetadata parent, String name, DataType type, boolean isStatic) {
    this.parent = parent;
    this.name = name;
    this.type = type;
    this.isStatic = isStatic;
  }

  static ColumnMetadata fromRaw(AbstractTableMetadata tm, Raw raw, DataType dataType) {
    return new ColumnMetadata(tm, raw.name, dataType, raw.kind == Raw.Kind.STATIC);
  }

  static ColumnMetadata forAlias(TableMetadata tm, String name, DataType type) {
    return new ColumnMetadata(tm, name, type, false);
  }

  /**
   * Returns the name of the column.
   *
   * @return the name of the column.
   */
  public String getName() {
    return name;
  }

  /**
   * Returns the parent object of this column. This can be a {@link TableMetadata} or a {@link
   * MaterializedViewMetadata} object.
   *
   * @return the parent object of this column.
   */
  public AbstractTableMetadata getParent() {
    return parent;
  }

  /**
   * Returns the type of the column.
   *
   * @return the type of the column.
   */
  public DataType getType() {
    return type;
  }

  /**
   * Whether this column is a static column.
   *
   * @return Whether this column is a static column or not.
   */
  public boolean isStatic() {
    return isStatic;
  }

  @Override
  public boolean equals(Object other) {
    if (other == this) return true;
    if (!(other instanceof ColumnMetadata)) return false;

    ColumnMetadata that = (ColumnMetadata) other;
    return this.name.equals(that.name)
        && this.isStatic == that.isStatic
        && this.type.equals(that.type);
  }

  @Override
  public int hashCode() {
    return MoreObjects.hashCode(name, isStatic, type);
  }

  @Override
  public String toString() {
    String str = Metadata.quoteIfNecessary(name) + ' ' + type;
    return isStatic ? str + " static" : str;
  }

  // Temporary class that is used to make building the schema easier. Not meant to be
  // exposed publicly at all.
  static class Raw {

    public enum Kind {
      PARTITION_KEY("PARTITION_KEY", "PARTITION_KEY"),
      CLUSTERING_COLUMN("CLUSTERING_KEY", "CLUSTERING"),
      REGULAR("REGULAR", "REGULAR"),
      COMPACT_VALUE("COMPACT_VALUE", ""), // v2 only
      STATIC("STATIC", "STATIC");

      final String v2;
      final String v3;

      Kind(String v2, String v3) {
        this.v2 = v2;
        this.v3 = v3;
      }

      static Kind fromStringV2(String s) {
        for (Kind kind : Kind.values()) {
          if (kind.v2.equalsIgnoreCase(s)) return kind;
        }
        throw new IllegalArgumentException(s);
      }

      static Kind fromStringV3(String s) {
        for (Kind kind : Kind.values()) {
          if (kind.v3.equalsIgnoreCase(s)) return kind;
        }
        throw new IllegalArgumentException(s);
      }
    }

    public final String name;
    public Kind kind;
    public final int position;
    public final String dataType;
    public final boolean isReversed;

    public final Map<String, String> indexColumns = new HashMap<String, String>();

    Raw(String name, Kind kind, int position, String dataType, boolean isReversed) {
      this.name = name;
      this.kind = kind;
      this.position = position;
      this.dataType = dataType;
      this.isReversed = isReversed;
    }

    static Raw fromRow(Row row, VersionNumber version) {
      String name = row.getString(COLUMN_NAME);

      Kind kind;
      if (version.getMajor() < 2) {
        kind = Kind.REGULAR;
      } else if (version.getMajor() < 3) {
        kind = row.isNull(KIND_V2) ? Kind.REGULAR : Kind.fromStringV2(row.getString(KIND_V2));
      } else {
        kind = row.isNull(KIND_V3) ? Kind.REGULAR : Kind.fromStringV3(row.getString(KIND_V3));
      }

      int position;
      if (version.getMajor() >= 3) {
        position =
            row.getInt(
                POSITION); // cannot be null, -1 is used as a special value instead of null to avoid
        // tombstones
        if (position == -1) position = 0;
      } else {
        position = row.isNull(COMPONENT_INDEX) ? 0 : row.getInt(COMPONENT_INDEX);
      }

      String dataType;
      boolean reversed;
      if (version.getMajor() >= 3) {
        dataType = row.getString(TYPE);
        String clusteringOrderStr = row.getString(CLUSTERING_ORDER);
        reversed = clusteringOrderStr.equals(DESC);
      } else {
        dataType = row.getString(VALIDATOR);
        reversed = DataTypeClassNameParser.isReversed(dataType);
      }

      Raw c = new Raw(name, kind, position, dataType, reversed);

      // secondary indexes (C* < 3.0.0)
      // from C* 3.0 onwards 2i are defined in a separate table
      if (version.getMajor() < 3) {
        for (String str : Arrays.asList(INDEX_TYPE, INDEX_NAME, INDEX_OPTIONS))
          if (row.getColumnDefinitions().contains(str) && !row.isNull(str))
            c.indexColumns.put(str, row.getString(str));
      }
      return c;
    }
  }
}
