/*
 * Copyright 1999-2017 Alibaba Group Holding Ltd.
 *
 * 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.alibaba.druid.stat;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.druid.util.FnvHash;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TableStat {
    int selectCount;
    int updateCount;
    int deleteCount;
    int insertCount;
    int dropCount;
    int mergeCount;
    int createCount;
    int alterCount;
    int createIndexCount;
    int dropIndexCount;
    int referencedCount;
    int addCount; // odps add table
    int addPartitionCount; // odps add partition
    int analyzeCount; // odps analyze table

    public TableStat() {
    }

    public int getReferencedCount() {
        return referencedCount;
    }

    public void incrementReferencedCount() {
        referencedCount++;
    }

    public int getDropIndexCount() {
        return dropIndexCount;
    }

    public void incrementDropIndexCount() {
        this.dropIndexCount++;
    }

    public void incrementAddCount() {
        this.addCount++;
    }

    public int getAddCount() {
        return addCount;
    }

    public void incrementAddPartitionCount() {
        this.addPartitionCount++;
    }

    public int getAddPartitionCount() {
        return addPartitionCount;
    }

    public int getCreateIndexCount() {
        return createIndexCount;
    }

    public void incrementCreateIndexCount() {
        createIndexCount++;
    }

    public int getAlterCount() {
        return alterCount;
    }

    public void incrementAlterCount() {
        this.alterCount++;
    }

    public int getCreateCount() {
        return createCount;
    }

    public void incrementCreateCount() {
        this.createCount++;
    }

    public int getMergeCount() {
        return mergeCount;
    }

    public void incrementMergeCount() {
        this.mergeCount++;
    }

    public int getDropCount() {
        return dropCount;
    }

    public void incrementDropCount() {
        dropCount++;
    }

    public void setDropCount(int dropCount) {
        this.dropCount = dropCount;
    }

    public int getSelectCount() {
        return selectCount;
    }

    public void incrementSelectCount() {
        selectCount++;
    }

    public void setSelectCount(int selectCount) {
        this.selectCount = selectCount;
    }

    public int getUpdateCount() {
        return updateCount;
    }

    public void incrementUpdateCount() {
        updateCount++;
    }

    public void setUpdateCount(int updateCount) {
        this.updateCount = updateCount;
    }

    public int getDeleteCount() {
        return deleteCount;
    }

    public void incrementDeleteCount() {
        this.deleteCount++;
    }

    public void setDeleteCount(int deleteCount) {
        this.deleteCount = deleteCount;
    }

    public void incrementInsertCount() {
        this.insertCount++;
    }

    public int getInsertCount() {
        return insertCount;
    }

    public void setInsertCount(int insertCount) {
        this.insertCount = insertCount;
    }

    public int getAnalyzeCount() {
        return analyzeCount;
    }

    public void incrementAnalyzeCount() {
        this.analyzeCount++;
    }

    public String toString() {
        StringBuilder buf = new StringBuilder(4);
        if (mergeCount > 0) {
            buf.append("Merge");
        }
        if (insertCount > 0) {
            buf.append("Insert");
        }
        if (updateCount > 0) {
            buf.append("Update");
        }
        if (selectCount > 0) {
            buf.append("Select");
        }
        if (deleteCount > 0) {
            buf.append("Delete");
        }
        if (dropCount > 0) {
            buf.append("Drop");
        }
        if (createCount > 0) {
            buf.append("Create");
        }
        if (alterCount > 0) {
            buf.append("Alter");
        }
        if (createIndexCount > 0) {
            buf.append("CreateIndex");
        }
        if (dropIndexCount > 0) {
            buf.append("DropIndex");
        }
        if (addCount > 0) {
            buf.append("Add");
        }
        if (addPartitionCount > 0) {
            buf.append("AddPartition");
        }
        if (analyzeCount > 0) {
            buf.append("Analyze");
        }

        return buf.toString();
    }

    public static class Name {
        private final String name;
        private final long hashCode64;

        public Name(String name) {
            this(name, FnvHash.hashCode64(name));
        }

        public Name(String name, long hashCode64) {
            this.name = name;
            this.hashCode64 = hashCode64;
        }

        public String getName() {
            return this.name;
        }

        public int hashCode() {
            long value = hashCode64();
            return (int) (value ^ (value >>> 32));
        }

        public long hashCode64() {
            return hashCode64;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Name)) {
                return false;
            }

            Name other = (Name) o;
            return this.hashCode64 == other.hashCode64;
        }

        public String toString() {
            return SQLUtils.normalize(this.name);
        }
    }

    public static class Relationship {
        private Column left;
        private Column right;
        private String operator;

        public Relationship(Column left, Column right, String operator) {
            this.left = left;
            this.right = right;
            this.operator = operator;
        }

        public Column getLeft() {
            return left;
        }

        public Column getRight() {
            return right;
        }

        public String getOperator() {
            return operator;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((left == null) ? 0 : left.hashCode());
            result = prime * result + ((operator == null) ? 0 : operator.hashCode());
            result = prime * result + ((right == null) ? 0 : right.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            Relationship other = (Relationship) obj;
            if (left == null) {
                if (other.left != null) {
                    return false;
                }
            } else if (!left.equals(other.left)) {
                return false;
            }
            if (operator == null) {
                if (other.operator != null) {
                    return false;
                }
            } else if (!operator.equals(other.operator)) {
                return false;
            }
            if (right == null) {
                if (other.right != null) {
                    return false;
                }
            } else if (!right.equals(other.right)) {
                return false;
            }
            return true;
        }

        @Override
        public String toString() {
            return left + " " + operator + " " + right;
        }

    }

    public static class Condition {
        private final Column column;
        private final String operator;
        private final List<Object> values = new ArrayList<Object>();

        public Condition(Column column, String operator) {
            this.column = column;
            this.operator = operator;
        }

        public Column getColumn() {
            return column;
        }

        public String getOperator() {
            return operator;
        }

        public List<Object> getValues() {
            return values;
        }

        public void addValue(Object value) {
            this.values.add(value);
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((column == null) ? 0 : column.hashCode());
            result = prime * result + ((operator == null) ? 0 : operator.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            Condition other = (Condition) obj;
            if (column == null) {
                if (other.column != null) {
                    return false;
                }
            } else if (!column.equals(other.column)) {
                return false;
            }
            if (operator == null) {
                if (other.operator != null) {
                    return false;
                }
            } else if (!operator.equals(other.operator)) {
                return false;
            }
            return true;
        }

        public String toString() {
            StringBuilder buf = new StringBuilder();
            buf.append(this.column.toString());
            buf.append(' ');
            buf.append(this.operator);

            if (values.size() == 1) {
                buf.append(' ');
                buf.append(String.valueOf(this.values.get(0)));
            } else if (values.size() > 0) {
                buf.append(" (");
                for (int i = 0; i < values.size(); ++i) {
                    if (i != 0) {
                        buf.append(", ");
                    }
                    Object val = values.get(i);
                    if (val instanceof String) {
                        String jsonStr = JSONUtils.toJSONString(val);
                        buf.append(jsonStr);
                    } else {
                        buf.append(String.valueOf(val));
                    }
                }
                buf.append(")");
            }

            return buf.toString();
        }
    }

    public static class Column {
        private final String table;
        private final String name;
        private final long hashCode64;

        private boolean where;
        private boolean select;
        private boolean groupBy;
        private boolean having;
        private boolean join;
        private boolean primaryKey; // for ddl
        private boolean unique; //
        private boolean update;
        private Map<String, Object> attributes = new HashMap<String, Object>();
        private transient String fullName;

        /**
         * @since 1.0.20
         */
        private String dataType;

        public Column(String table, String name) {
            this(table, name, null);
        }

        public Column(String table, String name, DbType dbType) {
            this.table = table;
            this.name = name;

            int p = table.indexOf('.');
            if (p != -1) {
                if (dbType == null) {
                    if (table.indexOf('`') != -1) {
                        dbType = DbType.mysql;
                    } else if (table.indexOf('[') != -1) {
                        dbType = DbType.sqlserver;
                    } else if (table.indexOf('@') != -1) {
                        dbType = DbType.oracle;
                    }
                }
                SQLExpr owner = SQLUtils.toSQLExpr(table, dbType);
                hashCode64 = new SQLPropertyExpr(owner, name).hashCode64();
            } else {
                hashCode64 = FnvHash.hashCode64(table, name);
            }
        }

        public Column(String table, String name, long hashCode64) {
            this.table = table;
            this.name = name;
            this.hashCode64 = hashCode64;
        }

        public String getTable() {
            return table;
        }

        public String getFullName() {
            if (fullName == null) {
                if (table == null) {
                    fullName = name;
                } else {
                    fullName = table + '.' + name;
                }
            }

            return fullName;
        }

        public long hashCode64() {
            return hashCode64;
        }

        public boolean isWhere() {
            return where;
        }

        public void setWhere(boolean where) {
            this.where = where;
        }

        public boolean isSelect() {
            return select;
        }

        public void setSelec(boolean select) {
            this.select = select;
        }

        public boolean isGroupBy() {
            return groupBy;
        }

        public void setGroupBy(boolean groupBy) {
            this.groupBy = groupBy;
        }

        public boolean isHaving() {
            return having;
        }

        public boolean isJoin() {
            return join;
        }

        public void setJoin(boolean join) {
            this.join = join;
        }

        public void setHaving(boolean having) {
            this.having = having;
        }

        public boolean isPrimaryKey() {
            return primaryKey;
        }

        public void setPrimaryKey(boolean primaryKey) {
            this.primaryKey = primaryKey;
        }

        public boolean isUnique() {
            return unique;
        }

        public void setUnique(boolean unique) {
            this.unique = unique;
        }

        public boolean isUpdate() {
            return update;
        }

        public void setUpdate(boolean update) {
            this.update = update;
        }

        public String getName() {
            return name;
        }

        /**
         * @since 1.0.20
         */
        public String getDataType() {
            return dataType;
        }

        /**
         * @since 1.0.20
         */
        public void setDataType(String dataType) {
            this.dataType = dataType;
        }

        public Map<String, Object> getAttributes() {
            return attributes;
        }

        public void setAttributes(Map<String, Object> attributes) {
            this.attributes = attributes;
        }

        public int hashCode() {
            long hash = hashCode64();
            return (int) (hash ^ (hash >>> 32));
        }

        public String toString() {
            if (table != null) {
                return SQLUtils.normalize(table) + "." + SQLUtils.normalize(name);
            }

            return SQLUtils.normalize(name);
        }

        public boolean equals(Object obj) {
            if (!(obj instanceof Column)) {
                return false;
            }

            Column column = (Column) obj;
            return hashCode64 == column.hashCode64;
        }
    }

    public static enum Mode {
        Insert(1), //
        Update(2), //
        Delete(4), //
        Select(8), //
        Merge(16), //
        Truncate(32), //
        Alter(64), //
        Drop(128), //
        DropIndex(256), //
        CreateIndex(512), //
        Replace(1024),
        DESC(2048); //

        public final int mark;

        private Mode(int mark) {
            this.mark = mark;
        }
    }
}
