package util.sqlparse.visitor.greenplum;

import bean.DataBase;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLAlterFunctionStatement;
import com.alibaba.druid.sql.ast.statement.SQLAlterIndexStatement;
import com.alibaba.druid.sql.ast.statement.SQLAlterProcedureStatement;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLAlterViewStatement;
import com.alibaba.druid.sql.ast.statement.SQLCallStatement;
import com.alibaba.druid.sql.ast.statement.SQLCreateFunctionStatement;
import com.alibaba.druid.sql.ast.statement.SQLCreateIndexStatement;
import com.alibaba.druid.sql.ast.statement.SQLCreateProcedureStatement;
import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLCreateTriggerStatement;
import com.alibaba.druid.sql.ast.statement.SQLCreateViewStatement;
import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement;
import com.alibaba.druid.sql.ast.statement.SQLDropEventStatement;
import com.alibaba.druid.sql.ast.statement.SQLDropFunctionStatement;
import com.alibaba.druid.sql.ast.statement.SQLDropIndexStatement;
import com.alibaba.druid.sql.ast.statement.SQLDropProcedureStatement;
import com.alibaba.druid.sql.ast.statement.SQLDropTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLDropTriggerStatement;
import com.alibaba.druid.sql.ast.statement.SQLDropViewStatement;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLGrantStatement;
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement;
import com.alibaba.druid.sql.ast.statement.SQLLoadIndexIntoCacheStatement;
import com.alibaba.druid.sql.ast.statement.SQLMergeStatement;
import com.alibaba.druid.sql.ast.statement.SQLReplaceStatement;
import com.alibaba.druid.sql.ast.statement.SQLRevokeStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLShowIndexesStatement;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTruncateStatement;
import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import util.sqlparse.visitor.common.Context;
import util.sqlparse.visitor.common.bean.FieldInfo;
import util.sqlparse.visitor.common.bean.SQLResult;
import util.sqlparse.visitor.common.bean.StatementType;
import util.sqlparse.visitor.common.bean.TableInfo;
import util.sqlparse.visitor.common.bean.ValueInfo;
import util.sqlparse.visitor.common.memo.FieldMemo;
import util.sqlparse.visitor.common.memo.TableMemo;
import util.sqlparse.visitor.common.memo.ValueMemo;
import util.sqlparse.visitor.common.scope.Scope;
import util.sqlparse.visitor.greenplum.visitor.FieldVisitor;
import util.sqlparse.visitor.greenplum.visitor.FormatVisitor;
import util.sqlparse.visitor.greenplum.visitor.GreenplumScopeDialector;
import util.sqlparse.visitor.greenplum.visitor.ScopeVisitor;
import util.sqlparse.visitor.greenplum.visitor.ValueReplacer;
import util.sqlparse.visitor.greenplum.visitor.WhereVisitor;

public class SQLParser {
   public static final String VIRTUAL_TABLE_TAG = "virtualTable";

   public SQLResult parse(String sql, DataBase dataBase, String schema) {
      List<SQLStatement> statements = SQLUtils.parseStatements(sql, "greenplum");
      Context context = new Context();
      context.initialize(dataBase, schema, "greenplum");
      Scope scope = new Scope();
      scope.dialector = new GreenplumScopeDialector(scope);
      scope.context = context;
      scope.isCheckIsolated = true;
      ScopeVisitor scopeVisitor = new ScopeVisitor(scope);
      scopeVisitor.perform(statements);
      FieldVisitor fieldVisitor = new FieldVisitor(scope);
      fieldVisitor.perform(statements);
      SQLResult result = new SQLResult();
      result.sqlType = context.sqlType;
      result.tables = context.getTables();
      Map<String, TableMemo> tables = new HashMap(6);
      this.getAllTables(scope, tables);
      Collection<TableMemo> tablesTemp = tables.values();
      List<TableMemo> tableMemoList = new ArrayList();

      for(TableMemo tableMemo : tablesTemp) {
         if (!tableMemo.topTable) {
            tableMemoList.add(tableMemo);
         }
      }

      result.tableMemos = tableMemoList;
      tables.clear();
      Map<String, FieldMemo> fields = new HashMap();
      Map<String, FieldInfo> fieldInfos = new HashMap();
      this.getAllFields(scope, fields, fieldInfos);
      result.fieldMemos = new ArrayList(fields.values());
      result.fields = new ArrayList(fieldInfos.values());
      fields.clear();
      List<FieldInfo> outputFields = new ArrayList();
      this.getSelectFields(scope, fieldInfos, fields, context.getTableMap(), outputFields);
      result.outputMemos = new ArrayList(fields.values());
      result.outputs = outputFields;
      fields.clear();
      List<ValueInfo> values = new ArrayList();
      this.getValues(scope, fieldInfos, values);
      result.values = values;
      result.statement = (SQLStatement)statements.get(0);
      result.statementType = getStatementType(result.statement);
      result.isCaseSensitive = scope.isCaseSensitive();
      return result;
   }

   private void getAllTables(Scope scope, Map<String, TableMemo> tableMap) {
      for(Scope child : scope.getChildren()) {
         for(Map.Entry<String, TableMemo> entry : child.getTableMap().entrySet()) {
            TableMemo value = (TableMemo)entry.getValue();
            this.trimVirtaulTable(value);
            tableMap.put(((TableMemo)entry.getValue()).getUniqueName(), entry.getValue());
         }

         this.getAllTables(child, tableMap);
      }

   }

   private boolean trimVirtaulTable(TableMemo value) {
      if (value == null) {
         return false;
      } else {
         SQLObject ref = value.ref;
         if (ref instanceof SQLExprTableSource) {
            SQLExprTableSource t = (SQLExprTableSource)ref;
            Object virtualTable = t.getAttribute("virtualTable");
            if (virtualTable != null) {
               SQLObject parent = t.getParent();
               if (parent instanceof SQLSelectQueryBlock) {
                  SQLSelectQueryBlock b = (SQLSelectQueryBlock)parent;
                  b.setFrom((SQLTableSource)null);
                  t.setParent((SQLObject)null);
                  t.removeAttribute("virtualTable");
                  return true;
               }
            }
         }

         return false;
      }
   }

   private void getAllFields(Scope scope, Map<String, FieldMemo> fieldMap, Map<String, FieldInfo> fields) {
      Map<String, TableInfo> tables = scope.context.getTableMap();

      for(Scope child : scope.getChildren()) {
         for(Map.Entry<String, FieldMemo> entry : child.getFieldMap().entrySet()) {
            fieldMap.put(((FieldMemo)entry.getValue()).getUniqueName(), entry.getValue());
            FieldMemo fieldMemo = (FieldMemo)entry.getValue();
            Map<String, TableInfo> tableMap = new HashMap();
            boolean isValid = true;
            if ((fieldMemo.children.size() == 0 && !fieldMemo.complex || fieldMemo.atom) && !fieldMemo.isConstant) {
               List<TableMemo> tableMemos = new ArrayList(fieldMemo.tables);
               if (fieldMemo.table != null) {
                  tableMemos.add(fieldMemo.table);
               }

               for(TableMemo table : tableMemos) {
                  TableInfo tableInfo = (TableInfo)tables.get(table.getAtomName());
                  if (tableInfo == null) {
                     isValid = false;
                     break;
                  }

                  tableMap.put(table.getAtomName(), tableInfo);
               }
            }

            if (tableMap.size() > 0 && isValid) {
               FieldInfo fieldInfo = new FieldInfo();
               fieldInfo.setName(fieldMemo.name);
               fieldInfo.setMemo(fieldMemo);
               fieldInfo.setAlias(fieldMemo.alias);
               fieldInfo.setFullName(fieldMemo.getAtomName());
               FieldInfo fieldInfoTmp = (FieldInfo)fields.get(fieldMemo.getAtomName());
               if (fieldInfoTmp == null) {
                  fields.put(fieldMemo.getAtomName(), fieldInfo);
                  fieldInfo.setTables(tableMap);
               }
            }
         }

         this.getAllFields(child, fieldMap, fields);
      }

   }

   private void getSelectFields(Scope scope, Map<String, FieldInfo> fields, Map<String, FieldMemo> fieldMap, Map<String, TableInfo> tables, List<FieldInfo> outputFields) {
      for(Scope child : scope.getChildren()) {
         if (child.fromField) {
            return;
         }

         if (!child.top) {
            if (!child.top && child.getFieldMap().size() > 0) {
               for(FieldMemo field : child.getFieldMap().orderValues()) {
                  if (field.isSelectItem) {
                     List<FieldMemo> childs = field.getChildren();
                     if (null != childs && childs.size() > 0) {
                        ListIterator<FieldMemo> listIterator = childs.listIterator();

                        while(listIterator.hasNext()) {
                           FieldMemo element = (FieldMemo)listIterator.next();
                           if (!element.isSelectItem) {
                              listIterator.remove();
                           }
                        }
                     }

                     if (!field.isConstant) {
                        FieldInfo info = new FieldInfo();
                        info.setFullName(field.ref == null ? field.name : field.ref.toString());
                        info.setName(field.name);
                        info.setAlias(field.alias);
                        info.setMemo(field);
                        Map<String, FieldInfo> relations = new HashMap();
                        this.getFieldInfos(field, fields, relations);
                        if (field.tables.size() > 0) {
                           this.getAtomTables(field.tables, tables, info.getTables());
                        }

                        if (relations.size() > 0) {
                           for(Map.Entry<String, FieldInfo> e : relations.entrySet()) {
                              info.getTables().putAll(((FieldInfo)e.getValue()).getTables());
                           }

                           info.setRelations(new ArrayList(relations.values()));
                        }

                        fieldMap.put(field.getUniqueName(), field);
                        outputFields.add(info);
                     }
                  }
               }
            } else {
               this.getSelectFields(child, fields, fieldMap, tables, outputFields);
            }
         }
      }

   }

   private void getFieldInfos(FieldMemo fieldMemo, Map<String, FieldInfo> fields, Map<String, FieldInfo> output) {
      if (fieldMemo.children != null && fieldMemo.children.size() != 0) {
         for(FieldMemo child : fieldMemo.children) {
            this.getFieldInfos(child, fields, output);
         }
      } else {
         String name = fieldMemo.getAtomName();
         if (fields.containsKey(name)) {
            FieldInfo fieldInfo = (FieldInfo)fields.get(name);
            fieldInfo.getMemos().add(fieldMemo);
            output.put(name, fieldInfo);
         }
      }

   }

   private void getAtomTables(List<TableMemo> tables, Map<String, TableInfo> tableMap, Map<String, TableInfo> atomTableMap) {
      if (tables != null && tables.size() != 0) {
         for(TableMemo table : tables) {
            if (table.getChildren().size() == 0) {
               TableInfo tableInfo = (TableInfo)tableMap.get(table.getAtomName());
               if (tableInfo != null) {
                  atomTableMap.put(tableInfo.getAtomName(), tableInfo);
               }
            } else {
               this.getAtomTables(table.getChildren(), tableMap, atomTableMap);
            }
         }

      }
   }

   private void getValues(Scope scope, Map<String, FieldInfo> fieldMap, List<ValueInfo> values) {
      for(ValueMemo value : scope.getValues()) {
         ValueInfo valueInfo = new ValueInfo(value);
         FieldMemo field = value.field;
         Map<String, FieldInfo> fields = new HashMap();
         this.getFieldInfos(field, fieldMap, fields);
         valueInfo.setFields(new ArrayList(fields.values()));
         values.add(valueInfo);
      }

   }

   public void replace(List<ValueInfo> values, Map<String, String> replaceValue) {
      ValueReplacer replacer = new ValueReplacer(replaceValue);
      replacer.replaceValues(values);
   }

   public void replace(List<ValueInfo> values, List<Map> whereList) {
      ValueReplacer replacer = new ValueReplacer(whereList);
      replacer.replaceValuesWithList(values);
   }

   public static StatementType getStatementType(SQLStatement statement) {
      if (statement instanceof SQLSelectStatement) {
         return StatementType.select;
      } else if (statement instanceof SQLUpdateStatement) {
         return StatementType.update;
      } else if (statement instanceof SQLDeleteStatement) {
         return StatementType.delete;
      } else if (statement instanceof SQLInsertStatement) {
         return StatementType.insert;
      } else if (statement instanceof SQLReplaceStatement) {
         return StatementType.replace;
      } else if (statement instanceof SQLMergeStatement) {
         return StatementType.merge;
      } else if (statement instanceof SQLTruncateStatement) {
         return StatementType.truncate;
      } else if (statement instanceof SQLCreateTableStatement) {
         return StatementType.createTable;
      } else if (statement instanceof SQLAlterTableStatement) {
         return StatementType.alterTable;
      } else if (statement instanceof SQLDropTableStatement) {
         return StatementType.dropTable;
      } else if (statement instanceof SQLCreateIndexStatement) {
         return StatementType.createIndex;
      } else if (statement instanceof SQLAlterIndexStatement) {
         return StatementType.alterIndex;
      } else if (statement instanceof SQLDropIndexStatement) {
         return StatementType.dropIndex;
      } else if (statement instanceof SQLCreateViewStatement) {
         return StatementType.createView;
      } else if (statement instanceof SQLAlterViewStatement) {
         return StatementType.alterView;
      } else if (statement instanceof SQLDropViewStatement) {
         return StatementType.dropView;
      } else if (statement instanceof SQLCreateProcedureStatement) {
         return StatementType.createProc;
      } else if (statement instanceof SQLAlterProcedureStatement) {
         return StatementType.alterProc;
      } else if (statement instanceof SQLDropProcedureStatement) {
         return StatementType.dropProc;
      } else if (statement instanceof SQLCreateFunctionStatement) {
         return StatementType.createFunction;
      } else if (statement instanceof SQLAlterFunctionStatement) {
         return StatementType.alterFunction;
      } else if (statement instanceof SQLDropFunctionStatement) {
         return StatementType.dropFunction;
      } else if (statement instanceof SQLCreateTriggerStatement) {
         return StatementType.createTrigger;
      } else if (statement instanceof SQLDropTriggerStatement) {
         return StatementType.dropTrigger;
      } else if (statement instanceof SQLDropEventStatement) {
         return StatementType.dropEvent;
      } else if (statement instanceof SQLRevokeStatement) {
         return StatementType.revoke;
      } else if (statement instanceof SQLGrantStatement) {
         return StatementType.grant;
      } else if (statement instanceof SQLCallStatement) {
         return StatementType.callProc;
      } else if (statement instanceof SQLShowIndexesStatement) {
         return StatementType.showIndex;
      } else {
         return statement instanceof SQLLoadIndexIntoCacheStatement ? StatementType.loadIndexIntoCache : StatementType.other;
      }
   }

   public List<String> where(String sql) {
      List<SQLStatement> statements = SQLUtils.parseStatements(sql, DbType.postgresql);
      WhereVisitor visitor = new WhereVisitor();
      visitor.perform(statements);
      return visitor.getWheres();
   }

   public String format(String sql) {
      List<SQLStatement> statements = SQLUtils.parseStatements(sql, DbType.postgresql);
      FormatVisitor visitor = new FormatVisitor();
      visitor.perform(statements);
      return ((SQLStatement)statements.get(0)).toString();
   }
}
