/*
 *      Copyright (C) 2012-2015 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 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 INDEX_TYPE = "index_type";
    static final String INDEX_OPTIONS = "index_options";
    static final String INDEX_NAME = "index_name";

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

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

    static ColumnMetadata fromRaw(TableOrView tm, Raw raw) {
        return new ColumnMetadata(tm, raw.name, raw.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 TableOrView 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 String toString() {
        String str = Metadata.escapeId(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 DataType dataType;
        public final boolean isReversed;

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

        Raw(String name, Kind kind, int position, DataType 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, ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
            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 dataTypeStr;
            if(version.getMajor() >= 3) {
                dataTypeStr = row.getString(TYPE);
            } else {
                dataTypeStr = row.getString(VALIDATOR);
            }
            DataType dataType = CassandraTypeParser.parseOne(dataTypeStr, protocolVersion, codecRegistry);
            boolean reversed = CassandraTypeParser.isReversed(dataTypeStr);

            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
            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;
        }
    }
}

@@ -1257,32 +1257,135 @@
 pe, v3 onwards%0A%0A
+    static final String CLUSTERING_ORDER = %22clustering_order%22;%0A    static final String DESC = %22desc%22;%0A%0A
     static final
@@ -5922,24 +5922,54 @@
 ataTypeStr;%0A
+            boolean reversed;%0A
             
@@ -6049,16 +6049,153 @@
 (TYPE);%0A
+                String clusteringOrderStr = row.getString(CLUSTERING_ORDER);%0A                reversed = clusteringOrderStr.equals(DESC);%0A
         
@@ -6259,24 +6259,96 @@
 VALIDATOR);%0A
+                reversed = CassandraTypeParser.isReversed(dataTypeStr);%0A
             
@@ -6459,84 +6459,8 @@
 ry);
-%0A            boolean reversed = CassandraTypeParser.isReversed(dataTypeStr);
 %0A%0A  

Support for new 'clustering_order' column.