package com.alibaba.druid.sql.repository;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr;
import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.util.FnvHash;
import com.alibaba.druid.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Schema {
   private String catalog;
   private String name;
   protected final Map<Long, SchemaObject> objects;
   protected final Map<Long, SchemaObject> functions;
   private SchemaRepository repository;

   protected Schema(SchemaRepository repository) {
      this(repository, null);
   }

   protected Schema(SchemaRepository repository, String name) {
      this.objects = new ConcurrentHashMap(16, 0.75F, 1);
      this.functions = new ConcurrentHashMap(16, 0.75F, 1);
      this.repository = repository;
      this.setName(name);
   }

   protected Schema(SchemaRepository repository, String catalog, String name) {
      this.objects = new ConcurrentHashMap(16, 0.75F, 1);
      this.functions = new ConcurrentHashMap(16, 0.75F, 1);
      this.repository = repository;
      this.catalog = catalog;
      this.name = name;
   }

   public SchemaRepository getRepository() {
      return this.repository;
   }

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

   public String getSimpleName() {
      if (this.name != null) {
         int p = this.name.indexOf(46);
         if (p != -1) {
            SQLExpr expr = SQLUtils.toSQLExpr(this.name, this.repository.dbType);
            return expr instanceof SQLPropertyExpr ? ((SQLPropertyExpr)expr).getSimpleName() : this.name.substring(p + 1);
         } else {
            return this.name;
         }
      } else {
         return null;
      }
   }

   public void setName(String name) {
      this.name = name;
      if (name != null && name.indexOf(46) != -1) {
         SQLExpr expr = SQLUtils.toSQLExpr(name, this.repository.dbType);
         if (expr instanceof SQLPropertyExpr) {
            this.catalog = ((SQLPropertyExpr)expr).getOwnernName();
         }
      }

   }

   public SchemaObject findTable(String tableName) {
      long hashCode64 = FnvHash.hashCode64(tableName);
      return this.findTable(hashCode64);
   }

   public SchemaObject findTable(long nameHashCode64) {
      SchemaObject object = (SchemaObject)this.objects.get(nameHashCode64);
      return object != null && object.getType() == SchemaObjectType.Table ? object : null;
   }

   public SchemaObject findView(String viewName) {
      long hashCode64 = FnvHash.hashCode64(viewName);
      return this.findView(hashCode64);
   }

   public SchemaObject findView(long nameHashCode64) {
      SchemaObject object = (SchemaObject)this.objects.get(nameHashCode64);
      return object != null && object.getType() == SchemaObjectType.View ? object : null;
   }

   public SchemaObject findTableOrView(String tableName) {
      long hashCode64 = FnvHash.hashCode64(tableName);
      return this.findTableOrView(hashCode64);
   }

   public SchemaObject findTableOrView(long hashCode64) {
      SchemaObject object = (SchemaObject)this.objects.get(hashCode64);
      if (object == null) {
         return null;
      } else {
         SchemaObjectType type = object.getType();
         return type != SchemaObjectType.Table && type != SchemaObjectType.View ? null : object;
      }
   }

   public SchemaObject findFunction(String functionName) {
      functionName = SQLUtils.normalize(functionName);
      String lowerName = functionName.toLowerCase();
      return (SchemaObject)this.functions.get(lowerName);
   }

   public boolean isSequence(String name) {
      long nameHashCode64 = FnvHash.hashCode64(name);
      SchemaObject object = (SchemaObject)this.objects.get(nameHashCode64);
      return object != null && object.getType() == SchemaObjectType.Sequence;
   }

   public SchemaObject findTable(SQLTableSource tableSource, String alias) {
      if (tableSource instanceof SQLExprTableSource) {
         if (StringUtils.equalsIgnoreCase(alias, tableSource.computeAlias())) {
            SQLExprTableSource exprTableSource = (SQLExprTableSource)tableSource;
            SchemaObject tableObject = exprTableSource.getSchemaObject();
            if (tableObject != null) {
               return tableObject;
            }

            SQLExpr expr = exprTableSource.getExpr();
            if (expr instanceof SQLIdentifierExpr) {
               long tableNameHashCode64 = ((SQLIdentifierExpr)expr).nameHashCode64();
               tableObject = this.findTable(tableNameHashCode64);
               if (tableObject != null) {
                  exprTableSource.setSchemaObject(tableObject);
               }

               return tableObject;
            }

            if (expr instanceof SQLPropertyExpr) {
               long tableNameHashCode64 = ((SQLPropertyExpr)expr).nameHashCode64();
               tableObject = this.findTable(tableNameHashCode64);
               if (tableObject != null) {
                  exprTableSource.setSchemaObject(tableObject);
               }

               return tableObject;
            }
         }

         return null;
      } else if (tableSource instanceof SQLJoinTableSource) {
         SQLJoinTableSource join = (SQLJoinTableSource)tableSource;
         SQLTableSource left = join.getLeft();
         SchemaObject tableObject = this.findTable(left, alias);
         if (tableObject != null) {
            return tableObject;
         } else {
            SQLTableSource right = join.getRight();
            tableObject = this.findTable(right, alias);
            return tableObject;
         }
      } else {
         return null;
      }
   }

   public SQLColumnDefinition findColumn(SQLTableSource tableSource, SQLSelectItem selectItem) {
      return selectItem == null ? null : this.findColumn(tableSource, selectItem.getExpr());
   }

   public SQLColumnDefinition findColumn(SQLTableSource tableSource, SQLExpr expr) {
      SchemaObject object = this.findTable(tableSource, expr);
      if (object != null) {
         if (expr instanceof SQLAggregateExpr) {
            SQLAggregateExpr aggregateExpr = (SQLAggregateExpr)expr;
            String function = aggregateExpr.getMethodName();
            if ("min".equalsIgnoreCase(function) || "max".equalsIgnoreCase(function)) {
               SQLExpr arg = (SQLExpr)aggregateExpr.getArguments().get(0);
               expr = arg;
            }
         }

         if (expr instanceof SQLName) {
            return object.findColumn(((SQLName)expr).getSimpleName());
         }
      }

      return null;
   }

   public SchemaObject findTable(SQLTableSource tableSource, SQLSelectItem selectItem) {
      return selectItem == null ? null : this.findTable(tableSource, selectItem.getExpr());
   }

   public SchemaObject findTable(SQLTableSource tableSource, SQLExpr expr) {
      if (expr instanceof SQLAggregateExpr) {
         SQLAggregateExpr aggregateExpr = (SQLAggregateExpr)expr;
         String function = aggregateExpr.getMethodName();
         if ("min".equalsIgnoreCase(function) || "max".equalsIgnoreCase(function)) {
            SQLExpr arg = (SQLExpr)aggregateExpr.getArguments().get(0);
            return this.findTable(tableSource, arg);
         }
      }

      if (expr instanceof SQLPropertyExpr) {
         String ownerName = ((SQLPropertyExpr)expr).getOwnernName();
         return this.findTable(tableSource, ownerName);
      } else if (!(expr instanceof SQLAllColumnExpr) && !(expr instanceof SQLIdentifierExpr)) {
         return null;
      } else if (tableSource instanceof SQLExprTableSource) {
         return this.findTable(tableSource, tableSource.computeAlias());
      } else if (tableSource instanceof SQLJoinTableSource) {
         SQLJoinTableSource join = (SQLJoinTableSource)tableSource;
         SchemaObject table = this.findTable(join.getLeft(), expr);
         if (table == null) {
            table = this.findTable(join.getRight(), expr);
         }

         return table;
      } else {
         return null;
      }
   }

   public Map<String, SchemaObject> getTables(SQLTableSource x) {
      Map<String, SchemaObject> tables = new LinkedHashMap();
      this.computeTables(x, tables);
      return tables;
   }

   protected void computeTables(SQLTableSource x, Map<String, SchemaObject> tables) {
      if (x != null) {
         if (x instanceof SQLExprTableSource) {
            SQLExprTableSource exprTableSource = (SQLExprTableSource)x;
            SQLExpr expr = exprTableSource.getExpr();
            if (expr instanceof SQLIdentifierExpr) {
               long tableNameHashCode64 = ((SQLIdentifierExpr)expr).nameHashCode64();
               String tableName = ((SQLIdentifierExpr)expr).getName();
               SchemaObject table = exprTableSource.getSchemaObject();
               if (table == null) {
                  table = this.findTable(tableNameHashCode64);
                  if (table != null) {
                     exprTableSource.setSchemaObject(table);
                  }
               }

               if (table != null) {
                  tables.put(tableName, table);
                  String alias = x.getAlias();
                  if (alias != null && !alias.equalsIgnoreCase(tableName)) {
                     tables.put(alias, table);
                  }
               }
            }

         } else {
            if (x instanceof SQLJoinTableSource) {
               SQLJoinTableSource join = (SQLJoinTableSource)x;
               this.computeTables(join.getLeft(), tables);
               this.computeTables(join.getRight(), tables);
            }

         }
      }
   }

   public int getTableCount() {
      int count = 0;

      for(SchemaObject object : this.objects.values()) {
         if (object.getType() == SchemaObjectType.Table) {
            ++count;
         }
      }

      return count;
   }

   public Collection<SchemaObject> getObjects() {
      return this.objects.values();
   }

   public int getViewCount() {
      int count = 0;

      for(SchemaObject object : this.objects.values()) {
         if (object.getType() == SchemaObjectType.View) {
            ++count;
         }
      }

      return count;
   }

   public List<String> showTables() {
      List<String> tables = new ArrayList(this.objects.size());

      for(SchemaObject object : this.objects.values()) {
         if (object.getType() == SchemaObjectType.Table) {
            tables.add(object.getName());
         }
      }

      Collections.sort(tables);
      return tables;
   }

   public String getCatalog() {
      return this.catalog;
   }
}
