package util.sqlparse.visitor.mongo;

import bean.Column;
import bean.DataBase;
import bean.Schema;
import bean.Table;
import com.alibaba.druid.DbType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.bson.BsonDocument;
import org.bson.BsonDouble;
import util.StringJoin;
import util.sqlparse.visitor.common.Objects;
import util.sqlparse.visitor.common.names.NameWrapper;

public class ScopeVisitor implements MongoVisitor<ParseResult> {
   private ParseResult result;
   private DataBase dataBase;
   private String schema;

   public ParseResult getResult() {
      return this.result;
   }

   public ScopeVisitor(DataBase dataBase, String schema) {
      this.dataBase = dataBase;
      this.schema = schema;
      this.result = new ParseResult();
   }

   public ParseResult visitFind(MongoNode context) {
      this.loadSchema(context);
      this.result.sqlType = "select";
      BsonObjectNode ctx = (BsonObjectNode)context;
      MongoNode find = ctx.get(Identifier.Find.code);
      String collection = find.value().toString();
      if (!this.checkCollection(collection)) {
         return new ParseResult();
      } else {
         this.result.addCollection(collection);
         BsonObjectNode filter = (BsonObjectNode)ctx.get(Identifier.Filter.code);
         this.visitFilter(collection, filter, false);
         BsonObjectNode projection = (BsonObjectNode)ctx.get(Identifier.Projection.code);
         if (projection == null || projection.size() == 0) {
            this.expandProjection(collection, ctx);
            projection = (BsonObjectNode)ctx.get(Identifier.Projection.code);
         }

         this.visitProjection(collection, projection);
         BsonObjectNode sort = (BsonObjectNode)ctx.get(Identifier.Sort.code);
         this.visitSort(collection, sort);
         return this.result;
      }
   }

   private void expandProjection(String collection, BsonObjectNode ctx) {
      Table table = null;
      if (!this.dataBase.isRedis()) {
         Map<String, Table> tables = this.dataBase.simpleCache.getTables();
         String id = this.dataBase.simpleCache.getSchemaTableId(this.schema, collection);
         table = (Table)tables.get(id);
      } else {
         NameWrapper nameWrapper = NameWrapper.create(DbType.of(this.dataBase.getDbType()), this.dataBase.simpleCache);
         Schema schemaFromRedis = nameWrapper.getSchemaFromRedis(this.schema, this.dataBase.isCaseSensitive());
         table = nameWrapper.getTableFromRedis(schemaFromRedis, collection, this.dataBase.isCaseSensitive());
      }

      if (table != null && table.getColumnList() != null && table.getColumnList().size() > 0) {
         BsonObjectNode projection = new BsonObjectNode(Identifier.Projection.code, new BsonDocument());
         ctx.put(Identifier.Projection.code, (MongoNode)projection);
         BsonDouble data = new BsonDouble((double)1.0F);

         for(Column column : table.getColumnList()) {
            projection.put(column.getColumnName(), (MongoNode)(new BsonBasicNode(column.getColumnName(), data)));
         }
      }

   }

   private void visitSort(String collection, BsonObjectNode fields) {
      if (fields != null) {
         for(Map.Entry<String, MongoNode> entry : fields.children().entrySet()) {
            String key = (String)entry.getKey();
            MongoNode value = (MongoNode)entry.getValue();
            if (!value.isKeyword()) {
               this.result.addColumn(collection, key, false, value);
            }
         }

      }
   }

   private void visitProjection(String collection, BsonObjectNode fields) {
      if (fields != null) {
         this.result.removeCollectionAllColumn(collection, "true");
         int _idShow = 1;
         List<String> columnListTemp = new ArrayList();
         int columnShow = 1;
         List<Column> columnList = this.getColumns(collection);
         if (!CollectionUtils.isEmpty(columnList)) {
            for(Map.Entry<String, MongoNode> entry : fields.children().entrySet()) {
               String key = (String)entry.getKey();
               MongoNode value = (MongoNode)entry.getValue();
               if (!value.isKeyword()) {
                  if (value.value() instanceof Double) {
                     double show = (Double)value.value();
                     if (StringUtils.equals(key, "_id")) {
                        if (show == (double)0.0F) {
                           _idShow = 0;
                        }
                     } else {
                        if (show == (double)0.0F) {
                           columnShow = 0;
                        }

                        columnListTemp.add(key);
                     }
                  } else if (value.value() instanceof String) {
                     String name = value.value().toString();
                     if (name.substring(0, 1).equals("$")) {
                        name = name.substring(1);
                     }

                     columnListTemp.add(name);
                  }
               }
            }

            if (_idShow == 1 && columnShow == 1 || _idShow == 0 && columnShow == 0) {
               columnListTemp.add("_id");
            }

            if (columnShow == 1) {
               for(String columnKey : columnListTemp) {
                  this.result.addColumn(collection, columnKey, true, fields);
               }
            } else {
               List<Column> filteredColumns = new ArrayList();

               for(Column column : columnList) {
                  if (!columnListTemp.contains(column.getColumnName())) {
                     this.result.addColumn(collection, column.getColumnName(), true, fields);
                     filteredColumns.add(column);
                  }
               }
            }

         }
      }
   }

   private List<Column> getColumns(String collection) {
      Table table = null;
      List<Column> columnList = new ArrayList();
      if (!this.dataBase.isRedis()) {
         String id = this.dataBase.simpleCache.getSchemaTableId(this.schema, collection);
         table = (Table)this.dataBase.simpleCache.getTables().get(id);
      } else {
         NameWrapper nameWrapper = NameWrapper.create(DbType.of(this.dataBase.getDbType()), this.dataBase.simpleCache);
         Schema schemaFromRedis = nameWrapper.getSchemaFromRedis(this.schema, this.dataBase.isCaseSensitive());
         table = nameWrapper.getTableFromRedis(schemaFromRedis, collection, this.dataBase.isCaseSensitive());
      }

      if (Objects.nonNull(table) && CollectionUtils.isNotEmpty(table.getColumnList())) {
         columnList = table.getColumnList();
      }

      return columnList;
   }

   private boolean checkCollection(String collection) {
      Table table = null;
      if (!this.dataBase.isRedis()) {
         String id = this.dataBase.simpleCache.getSchemaTableId(this.schema, collection);
         table = (Table)this.dataBase.simpleCache.getTables().get(id);
      } else {
         NameWrapper nameWrapper = NameWrapper.create(DbType.of(this.dataBase.getDbType()), this.dataBase.simpleCache);
         Schema schemaFromRedis = nameWrapper.getSchemaFromRedis(this.schema, this.dataBase.isCaseSensitive());
         table = nameWrapper.getTableFromRedis(schemaFromRedis, collection, this.dataBase.isCaseSensitive());
      }

      return Objects.nonNull(table);
   }

   private int visitAggregateGroup(String collection, MongoNode json, boolean isOutput) {
      if (json != null && !StringUtils.equals(json.toString(), "{}")) {
         if (json.type() == BsonNode.BsonNodeType.Basic) {
            BsonBasicNode bfilter = (BsonBasicNode)json;
            if (bfilter.value() instanceof String) {
               String name = (String)bfilter.value();
               if (name != null && name.length() > 0) {
                  if ("$$ROOT".equals(name)) {
                     return 1;
                  }

                  if (name.substring(0, 1).equals("$")) {
                     name = name.substring(1);
                  }

                  this.result.addColumn(collection, name, isOutput, json);
               }
            }
         } else if (json.type() == BsonNode.BsonNodeType.Array) {
            BsonArrayNode afilter = (BsonArrayNode)json;

            for(MongoNode child : afilter.children) {
               this.visitAggregateGroup(collection, child, isOutput);
            }
         } else {
            BsonObjectNode ofilter = (BsonObjectNode)json;
            if (ofilter.size() == 0) {
               return 0;
            }

            Map<String, MongoNode> children = ofilter.children();

            for(Map.Entry<String, MongoNode> entry : children.entrySet()) {
               int i = this.visitAggregateGroup(collection, (MongoNode)entry.getValue(), isOutput);
               if (i == 1) {
                  return 1;
               }
            }
         }

         return 0;
      } else {
         return 0;
      }
   }

   private void visitJsonValue(String collection, MongoNode json, boolean isOutput) {
      if (json != null && !StringUtils.equals(json.toString(), "{}")) {
         if (json.type() == BsonNode.BsonNodeType.Basic) {
            BsonBasicNode bfilter = (BsonBasicNode)json;
            if (bfilter.value() instanceof String) {
               String name = (String)bfilter.value();
               if (StringUtils.isNotBlank(name) && name.length() > 0) {
                  if (name.substring(0, 1).equals("$")) {
                     name = name.substring(1);
                  }

                  if (this.checkColumn(collection, name)) {
                     this.result.addColumn(collection, name, isOutput, json);
                  }
               }
            }
         } else if (json.type() == BsonNode.BsonNodeType.Array) {
            BsonArrayNode afilter = (BsonArrayNode)json;

            for(MongoNode child : afilter.children) {
               this.visitJsonValue(collection, child, isOutput);
            }
         } else {
            BsonObjectNode ofilter = (BsonObjectNode)json;
            if (ofilter.size() == 0) {
               return;
            }

            Map<String, MongoNode> children = ofilter.children();

            for(Map.Entry<String, MongoNode> entry : children.entrySet()) {
               this.visitJsonValue(collection, (MongoNode)entry.getValue(), isOutput);
            }
         }

      }
   }

   private boolean checkColumn(String collection, String columnName) {
      List<Column> columns = this.getColumns(collection);
      if (CollectionUtils.isEmpty(columns)) {
         return false;
      } else {
         for(Column column : columns) {
            if (StringUtils.equals(columnName, column.getColumnName())) {
               return true;
            }
         }

         return false;
      }
   }

   private void visitProject(String collection, BsonObjectNode json, boolean isOutput) {
      if (json != null && !StringUtils.equals(json.toString(), "{}")) {
         this.visitProjection(collection, json);
      }
   }

   private void visitFilter(String collection, MongoNode filter, boolean isOutput) {
      if (filter != null && !StringUtils.equals(filter.toString(), "{}")) {
         if (filter.type() == BsonNode.BsonNodeType.Basic) {
            BsonBasicNode bfilter = (BsonBasicNode)filter;
            String name = bfilter.name;
            if (name != null && name.length() > 0 && !filter.isKeyword()) {
               this.result.addColumn(collection, name, isOutput, filter);
            }
         } else if (filter.type() == BsonNode.BsonNodeType.Array) {
            BsonArrayNode afilter = (BsonArrayNode)filter;

            for(MongoNode child : afilter.children) {
               this.visitFilter(collection, child, isOutput);
            }
         } else {
            BsonObjectNode ofilter = (BsonObjectNode)filter;
            if (ofilter.size() == 0) {
               return;
            }

            Map<String, MongoNode> children = ofilter.children();

            for(Map.Entry<String, MongoNode> entry : children.entrySet()) {
               this.visitFilter(collection, (MongoNode)entry.getValue(), isOutput);
            }
         }

      }
   }

   public ParseResult visitInsert(MongoNode ctx) {
      this.result.sqlType = "insert";
      this.loadSchema(ctx);
      BsonArrayNode array = (BsonArrayNode)ctx;
      BsonObjectNode insert = (BsonObjectNode)array.get(array.size() - 1);
      MongoNode collectionCtx = insert.get(Identifier.Insert.code);
      String collection = collectionCtx.value().toString();
      if (!this.checkCollection(collection)) {
         return new ParseResult();
      } else {
         this.result.addCollection(collection);

         for(int i = 0; i < array.children.size() - 1; ++i) {
            this.visitObject(collection, array.get(i), this.result, new ArrayList());
         }

         return this.result;
      }
   }

   private void visitObject(String collection, MongoNode node, ParseResult result, List<String> foots) {
      if (node != null) {
         if (node.type() == BsonNode.BsonNodeType.Basic) {
            String colName = StringJoin.join(foots, ".") + node.getName();
            result.addColumn(collection, colName, true, node);
         } else if (node.type() == BsonNode.BsonNodeType.Object) {
            BsonObjectNode obj = (BsonObjectNode)node;

            for(Map.Entry<String, MongoNode> entry : obj.children().entrySet()) {
               String name = (String)entry.getKey();
               MongoNode value = (MongoNode)entry.getValue();
               foots.add(name);
               if (value.type() != BsonNode.BsonNodeType.Basic) {
                  this.visitObject(collection, value, result, foots);
               }

               result.addColumn(collection, name, true, node);
               foots.remove(foots.size() - 1);
            }
         } else if (node.type() == BsonNode.BsonNodeType.Array) {
            BsonArrayNode array = (BsonArrayNode)node;
            if (array.children.size() > 0) {
               for(MongoNode child : array.children) {
                  if (child.type() == BsonNode.BsonNodeType.Basic) {
                     String colName = StringJoin.join(foots, ".");
                     result.addColumn(collection, colName, true, array);
                  } else {
                     this.visitObject(collection, child, result, foots);
                  }
               }
            }
         }

      }
   }

   public ParseResult visitUpdate(MongoNode ctx) {
      this.result.sqlType = "update";
      this.loadSchema(ctx);
      BsonArrayNode array = (BsonArrayNode)ctx;
      BsonObjectNode dataCtx = (BsonObjectNode)array.get(0);
      BsonObjectNode updateCtx = (BsonObjectNode)array.get(array.size() - 1);
      MongoNode collectionCtx = updateCtx.get(Identifier.Update.code);
      String collection = collectionCtx.value().toString();
      if (this.checkCollection(collection)) {
         this.result.addCollection(collection);
         BsonObjectNode filter = (BsonObjectNode)dataCtx.get(Identifier.Query.code);
         this.visitFilter(collection, filter, false);
         BsonObjectNode update = (BsonObjectNode)dataCtx.get(Identifier.U.code);
         this.visitFilter(collection, update, true);
         List sets = this.matchNodes(Identifier.Set.code, dataCtx);
         this.visitUpdateSets(collection, sets);
         MongoNode mergeObjects = this.matchNode(Identifier.MergeObjects.code, dataCtx);
         if (mergeObjects != null) {
            BsonObjectNode mergeCtx = (BsonObjectNode)mergeObjects;
            this.visitFilter(collection, mergeCtx, true);
         }

         return this.result;
      } else {
         return new ParseResult();
      }
   }

   public ParseResult visitFindAndModify(MongoNode ctx) {
      this.loadSchema(ctx);
      BsonObjectNode objCtx = (BsonObjectNode)ctx;
      MongoNode mongoNode = objCtx.get(Identifier.FindAndModify.code);
      String collection = mongoNode.value().toString();
      if (!this.checkCollection(collection)) {
         this.result.sqlType = "update";
         return new ParseResult();
      } else {
         this.result.addCollection(collection);
         MongoNode update = objCtx.get(Identifier.Update.code);
         MongoNode remove = objCtx.get(Identifier.Remove.code);
         MongoNode var7 = objCtx.get(Identifier.Upsert.code);
         if (update != null) {
            this.result.sqlType = "update";
            BsonObjectNode updateSet = (BsonObjectNode)update;
            List<MongoNode> sets = new ArrayList();

            for(Map.Entry<String, MongoNode> entry : updateSet.children().entrySet()) {
               sets.add(entry.getValue());
            }

            this.visitUpdateSets(collection, sets);
         }

         if (remove != null) {
            this.result.sqlType = "delete";
            this.expandProjection(collection, objCtx);
            this.visitProjection(collection, (BsonObjectNode)objCtx.get(Identifier.Projection.code));
         }

         BsonObjectNode filter = (BsonObjectNode)objCtx.get(Identifier.Query.code);
         if (Objects.nonNull(filter)) {
            this.visitFilter(collection, filter, false);
         }

         BsonObjectNode sortCtx = (BsonObjectNode)objCtx.get(Identifier.Sort.code);
         if (Objects.nonNull(sortCtx)) {
            this.visitSort(collection, sortCtx);
         }

         BsonObjectNode fieldsCtx = (BsonObjectNode)objCtx.get(Identifier.Fields.code);
         if (Objects.nonNull(fieldsCtx)) {
            this.visitFields(collection, fieldsCtx, true);
         } else {
            this.expandProjection(collection, objCtx);
            this.visitProjection(collection, (BsonObjectNode)objCtx.get(Identifier.Projection.code));
         }

         return this.result;
      }
   }

   private void visitFields(String collection, BsonObjectNode fieldsCtx, boolean isOutput) {
      for(Map.Entry<String, MongoNode> entry : fieldsCtx.children().entrySet()) {
         this.result.addColumn(collection, (String)entry.getKey(), isOutput, fieldsCtx);
      }

   }

   private void visitUpdateSets(String collection, List<MongoNode> sets) {
      if (sets != null && sets.size() != 0) {
         for(MongoNode set : sets) {
            if (set.type() == BsonNode.BsonNodeType.Object) {
               BsonObjectNode setCtx = (BsonObjectNode)set;

               for(Map.Entry<String, MongoNode> entry : setCtx.children().entrySet()) {
                  String key = (String)entry.getKey();
                  this.result.addColumn(collection, key, true, new BsonKeyNode(key, setCtx));
               }
            } else if (set.type() == BsonNode.BsonNodeType.Basic) {
               BsonBasicNode setCtx = (BsonBasicNode)set;
               String key = setCtx.name;
               this.result.addColumn(collection, key, true, (MongoNode)null);
            }
         }

      }
   }

   public ParseResult visitDelete(MongoNode ctx) {
      this.loadSchema(ctx);
      this.result.sqlType = "delete";
      BsonArrayNode array = (BsonArrayNode)ctx;
      BsonObjectNode collectionCtx = (BsonObjectNode)array.get(array.size() - 1);
      String collection = collectionCtx.get(Identifier.Delete.code).value().toString();
      if (!this.checkCollection(collection)) {
         return new ParseResult();
      } else {
         this.result.addCollection(collection);

         for(int i = 0; i < array.children.size() - 1; ++i) {
            BsonObjectNode dataCtx = (BsonObjectNode)array.get(i);
            BsonObjectNode filter = (BsonObjectNode)dataCtx.get("q");
            this.visitFilter(collection, filter, false);
         }

         Map<String, Table> tables = this.dataBase.simpleCache.getTables();
         String id = this.dataBase.simpleCache.getSchemaTableId(this.schema, collection);
         Table table = (Table)tables.get(id);
         if (table != null && table.getColumnList() != null && table.getColumnList().size() > 0) {
            for(Column column : table.getColumnList()) {
               this.result.addColumn(collection, column.getColumnName(), true, (MongoNode)null);
            }
         }

         return this.result;
      }
   }

   public ParseResult visitCount(MongoNode ctx) {
      this.loadSchema(ctx);
      this.result.sqlType = "select";
      BsonObjectNode objCtx = (BsonObjectNode)ctx;
      MongoNode collectionCtx = objCtx.get(Identifier.Count.code);
      String collection = collectionCtx.value().toString();
      if (this.checkCollection(collection)) {
         this.result.addCollection(collection);
         MongoNode mongoNode = objCtx.get(Identifier.Query.code);
         this.visitFilter(collection, mongoNode, false);
         return this.result;
      } else {
         return new ParseResult();
      }
   }

   public ParseResult visitAggregate(MongoNode ctx) {
      this.loadSchema(ctx);
      this.result.sqlType = "select";
      BsonObjectNode objNode = (BsonObjectNode)ctx;
      MongoNode collectionCtx = objNode.get(Identifier.Aggregate.code);
      String collection = collectionCtx.value().toString();
      if (this.checkCollection(collection)) {
         this.result.addCollection(collection);
         BsonArrayNode pipeCtx = (BsonArrayNode)objNode.get(Identifier.Pipeline.code);
         int allColumn = 1;
         allColumn = this.visitPipeline(collection, pipeCtx, allColumn);
         if (allColumn == 1) {
            this.expandProjection(collection, objNode);
            this.visitProjection(collection, (BsonObjectNode)objNode.get(Identifier.Projection.code));
         }

         return this.result;
      } else {
         return new ParseResult();
      }
   }

   private int visitPipeline(String collection, BsonArrayNode pipeCtx, int allColumn) {
      for(int i = 0; i < pipeCtx.children.size(); ++i) {
         BsonObjectNode pCtx = (BsonObjectNode)pipeCtx.get(i);
         BsonObjectNode facetCtx = (BsonObjectNode)pCtx.get(Identifier.Facet.code);
         if (facetCtx != null) {
            this.visitFacet(collection, facetCtx, true);
            allColumn = 0;
         } else {
            BsonObjectNode unionWithCtx = (BsonObjectNode)pCtx.get(Identifier.UnionWith.code);
            if (unionWithCtx != null) {
               this.visitUnionWith(unionWithCtx, true);
            } else {
               BsonObjectNode matchCtx = (BsonObjectNode)pCtx.get(Identifier.Match.code);
               if (matchCtx != null) {
                  this.visitFilter(collection, matchCtx, false);
               } else {
                  BsonObjectNode groupCtx = (BsonObjectNode)pCtx.get(Identifier.Group.code);
                  if (groupCtx != null) {
                     allColumn = this.visitAggregateGroup(collection, groupCtx, true);
                  } else {
                     BsonObjectNode bucketCtx = (BsonObjectNode)pCtx.get(Identifier.Bucket.code);
                     if (bucketCtx != null) {
                        this.visitBucket(collection, bucketCtx, true);
                        allColumn = 0;
                     } else {
                        BsonObjectNode bucketAuto = (BsonObjectNode)pCtx.get(Identifier.BucketAuto.code);
                        if (bucketAuto != null) {
                           this.visitBucketAuto(collection, bucketAuto, true);
                           allColumn = 0;
                        } else {
                           BsonObjectNode lookupCtx = (BsonObjectNode)pCtx.get(Identifier.Lookup.code);
                           if (lookupCtx != null) {
                              this.visitLookUp(lookupCtx, true);
                           } else {
                              BsonObjectNode projectCtx = (BsonObjectNode)pCtx.get(Identifier.Project.code);
                              if (projectCtx != null) {
                                 this.visitProject(collection, projectCtx, true);
                                 allColumn = 0;
                              } else {
                                 BsonObjectNode sortCtx = (BsonObjectNode)pCtx.get(Identifier.SORT.code);
                                 if (sortCtx != null) {
                                    this.visitSort(collection, sortCtx);
                                 } else {
                                    MongoNode sortByCountCtx = pCtx.get(Identifier.SortByCount.code);
                                    if (sortByCountCtx != null) {
                                       this.visitJsonValue(collection, sortByCountCtx, true);
                                       allColumn = 0;
                                    } else {
                                       MongoNode outCtx = pCtx.get(Identifier.Out.code);
                                       if (outCtx != null) {
                                          this.visitOut(outCtx, true);
                                       } else {
                                          MongoNode replaceRootCtx = pCtx.get(Identifier.ReplaceRoot.code);
                                          if (replaceRootCtx != null) {
                                             this.visitJsonValue(collection, replaceRootCtx, true);
                                             allColumn = 0;
                                          } else {
                                             MongoNode mergeCtx = pCtx.get(Identifier.Merge.code);
                                             if (mergeCtx != null) {
                                                this.visitMerge(collection, mergeCtx, true);
                                                allColumn = 0;
                                             } else {
                                                MongoNode unsetCtx = pCtx.get(Identifier.Unset.code);
                                                if (unsetCtx != null) {
                                                   this.visitUnset(collection, unsetCtx, true);
                                                   allColumn = 0;
                                                } else {
                                                   BsonObjectNode graphLookupCtx = (BsonObjectNode)pCtx.get(Identifier.GraphLookup.code);
                                                   if (graphLookupCtx != null) {
                                                      this.visitGraphLookupCtx(graphLookupCtx, true);
                                                   }
                                                }
                                             }
                                          }
                                       }
                                    }
                                 }
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }

      return allColumn;
   }

   private void visitGraphLookupCtx(BsonObjectNode graphLookupCtx, boolean isOutput) {
      MongoNode from = graphLookupCtx.get(Identifier.From.code);
      String collection = from.value().toString();
      if (this.checkCollection(collection)) {
         this.result.addCollection(collection);
         this.resultAddAllColumn(collection, isOutput);
      }
   }

   private void resultAddAllColumn(String collection, boolean isOutput) {
      if (!StringUtils.isEmpty(collection)) {
         for(Column column : this.getColumns(collection)) {
            this.result.addColumn(collection, column.getColumnName(), isOutput, (MongoNode)null);
         }

      }
   }

   private void visitUnset(String collection, MongoNode unsetCtx, boolean isOutput) {
      List<Column> columns = this.getColumns(collection);
      if (!CollectionUtils.isEmpty(columns)) {
         if (unsetCtx.type() == BsonNode.BsonNodeType.Basic) {
            BsonBasicNode columnBasic = (BsonBasicNode)unsetCtx;
            String columnName = columnBasic.value().toString();

            for(Column column : columns) {
               if (!StringUtils.equals(columnName, column.getColumnName())) {
                  this.result.addColumn(collection, column.getColumnName(), true, columnBasic);
               }
            }
         } else if (unsetCtx.type() == BsonNode.BsonNodeType.Array) {
            BsonArrayNode columnArray = (BsonArrayNode)unsetCtx;
            int size = columnArray.size();
            List<String> excludeList = new ArrayList();

            for(int i = 0; i < size; ++i) {
               BsonBasicNode mongoNode = (BsonBasicNode)columnArray.get(i);
               excludeList.add(mongoNode.value().toString());
            }

            for(Column column : columns) {
               if (!excludeList.contains(column.getColumnName())) {
                  this.result.addColumn(collection, column.getColumnName(), true, unsetCtx);
               }
            }
         }

      }
   }

   private void visitUnionWith(BsonObjectNode unionWithCtx, boolean isOutput) {
      int allColumn = 1;
      Map<String, MongoNode> map = unionWithCtx.children();
      MongoNode mongoNode = (MongoNode)map.get(Identifier.Coll.code);
      String collection = mongoNode.value().toString();
      if (this.checkCollection(collection)) {
         this.result.addCollection(mongoNode.value().toString());
      }

      BsonArrayNode pipeCtx = (BsonArrayNode)map.get(Identifier.Pipeline.code);
      if (pipeCtx != null) {
         allColumn = this.visitPipeline(collection, pipeCtx, allColumn);
      }

      if (allColumn == 1) {
         this.expandProjection(collection, unionWithCtx);
         this.visitProjection(collection, (BsonObjectNode)unionWithCtx.get(Identifier.Projection.code));
      }

   }

   private void visitMerge(String collection, MongoNode mergeCtx, boolean isOutput) {
      if (mergeCtx != null && !StringUtils.equals(mergeCtx.toString(), "{}")) {
         BsonObjectNode ofilter = (BsonObjectNode)mergeCtx;
         Map<String, MongoNode> map = ofilter.children();
         MongoNode mongoNode = (MongoNode)map.get(Identifier.Into.code);
         String newCollection = null;
         if (mongoNode.type() == BsonNode.BsonNodeType.Basic) {
            newCollection = mongoNode.value().toString();
            this.result.addCollection(newCollection);
         } else if (mongoNode.type() == BsonNode.BsonNodeType.Object) {
            BsonObjectNode value = (BsonObjectNode)mongoNode;
            Map<String, MongoNode> children = value.children();
            BsonBasicNode db = (BsonBasicNode)children.get(Identifier.Db.code);
            this.result.addSchema(db.value().toString());
            BsonBasicNode coll = (BsonBasicNode)children.get(Identifier.Coll.code);
            newCollection = coll.value().toString();
            this.result.addCollection(newCollection);
         }

         MongoNode on = (MongoNode)map.get(Identifier.On.code);
         if (on != null) {
            if (on.type() == BsonNode.BsonNodeType.Basic) {
               BsonBasicNode bfilter = (BsonBasicNode)on;
               String name = (String)bfilter.value();
               this.result.addColumn(newCollection, name, isOutput, bfilter);
            } else if (on.type() == BsonNode.BsonNodeType.Array) {
               BsonArrayNode afilter = (BsonArrayNode)on;

               for(MongoNode child : afilter.children) {
                  BsonBasicNode node = (BsonBasicNode)child;
                  String name = (String)node.value();
                  this.result.addColumn(newCollection, name, isOutput, node);
               }
            }
         }

      }
   }

   private void visitOut(MongoNode outCtx, boolean isOutput) {
      if (outCtx.type() == BsonNode.BsonNodeType.Basic) {
         BsonBasicNode mongoNodeCtx = (BsonBasicNode)outCtx;
         this.result.addCollection(mongoNodeCtx.value().toString());
      } else if (outCtx.type() == BsonNode.BsonNodeType.Object) {
         BsonObjectNode mongoNodeCtx = (BsonObjectNode)outCtx;
         Map<String, MongoNode> map = mongoNodeCtx.children();
         BsonBasicNode db = (BsonBasicNode)map.get(Identifier.Db.code);
         this.result.addSchema(db.value().toString());
         BsonBasicNode collection = (BsonBasicNode)map.get(Identifier.Coll.code);
         this.result.addCollection(collection.value().toString());
      }

   }

   private void visitFacet(String collection, BsonObjectNode facetCtx, boolean isOutput) {
      Map<String, MongoNode> children = facetCtx.children();

      for(Map.Entry<String, MongoNode> entry : children.entrySet()) {
         BsonArrayNode arrayNode = (BsonArrayNode)entry.getValue();

         for(MongoNode mongoNode : arrayNode.children()) {
            BsonObjectNode mongoNodeObject = (BsonObjectNode)mongoNode;
            Map<String, MongoNode> childrenNodeLast = mongoNodeObject.children();

            for(Map.Entry<String, MongoNode> nodeLast : childrenNodeLast.entrySet()) {
               MongoNode value = (MongoNode)nodeLast.getValue();
               if (value.type() == BsonNode.BsonNodeType.Basic) {
                  BsonBasicNode basicCtx = (BsonBasicNode)value;
                  this.visitJsonValue(collection, basicCtx, true);
               } else if (StringUtils.equals((String)nodeLast.getKey(), Identifier.Match.code)) {
                  this.visitFilter(collection, (BsonObjectNode)value, false);
               } else if (StringUtils.equals((String)nodeLast.getKey(), Identifier.BucketAuto.code)) {
                  this.visitBucketAuto(collection, (BsonObjectNode)value, true);
               } else if (StringUtils.equals((String)nodeLast.getKey(), Identifier.Bucket.code)) {
                  this.visitBucket(collection, (BsonObjectNode)value, true);
               }
            }
         }
      }

   }

   private void visitSet(String collection, BsonObjectNode setCtx, boolean isOutput) {
      this.visitJsonValue(collection, setCtx, true);
   }

   private void visitBucketAuto(String collection, BsonObjectNode bucketAutoCtx, boolean isOutput) {
      Map<String, MongoNode> children = bucketAutoCtx.children();
      BsonBasicNode groupByCtx = (BsonBasicNode)children.get(Identifier.GroupBy.code);
      if (groupByCtx != null) {
         this.visitJsonValue(collection, groupByCtx, true);
      }

      BsonObjectNode outputCtx = (BsonObjectNode)bucketAutoCtx.get(Identifier.Output.code);
      if (outputCtx != null) {
         this.visitBucketOutput(collection, outputCtx, true);
      }

   }

   private void visitBucket(String collection, BsonObjectNode bucketCtx, boolean isOutput) {
      Map<String, MongoNode> children = bucketCtx.children();
      BsonBasicNode groupByCtx = (BsonBasicNode)children.get(Identifier.GroupBy.code);
      if (groupByCtx != null) {
         this.visitFilter(collection, groupByCtx, false);
      }

      BsonObjectNode outputCtx = (BsonObjectNode)bucketCtx.get(Identifier.Output.code);
      if (outputCtx != null) {
         this.visitBucketOutput(collection, outputCtx, true);
      }

   }

   private void visitBucketOutput(String collection, BsonObjectNode outputCtx, boolean isOutput) {
      if (outputCtx != null && !StringUtils.equals(outputCtx.toString(), "{}")) {
         Map<String, MongoNode> children = outputCtx.children();

         for(Map.Entry<String, MongoNode> entry : children.entrySet()) {
            this.visitJsonValue(collection, (MongoNode)entry.getValue(), true);
         }

      }
   }

   private void visitLookUp(BsonObjectNode lookupCtx, boolean isOutput) {
      MongoNode from = lookupCtx.get(Identifier.From.code);
      String collection = from.value().toString();
      if (this.checkCollection(collection)) {
         this.result.addCollection(collection);
         BsonArrayNode pipeCtx = (BsonArrayNode)lookupCtx.get(Identifier.Pipeline.code);
         if (pipeCtx == null) {
            this.expandProjection(collection, lookupCtx);
            this.visitProjection(collection, (BsonObjectNode)lookupCtx.get(Identifier.Projection.code));
         } else {
            int allColumn = 1;

            for(int i = 0; i < pipeCtx.children.size(); ++i) {
               BsonObjectNode pCtx = (BsonObjectNode)pipeCtx.get(i);
               BsonObjectNode matchCtx = (BsonObjectNode)pCtx.get(Identifier.Match.code);
               if (matchCtx != null) {
                  this.visitFilter(collection, matchCtx, false);
               } else {
                  BsonObjectNode groupCtx = (BsonObjectNode)pCtx.get(Identifier.Group.code);
                  if (groupCtx != null) {
                     allColumn = this.visitAggregateGroup(collection, groupCtx, true);
                  } else {
                     BsonObjectNode projectCtx = (BsonObjectNode)pCtx.get(Identifier.Project.code);
                     if (projectCtx != null) {
                        allColumn = 0;
                        this.visitProject(collection, projectCtx, true);
                     } else {
                        BsonObjectNode sortCtx = (BsonObjectNode)pCtx.get(Identifier.SORT.code);
                        if (sortCtx != null) {
                           this.visitSort(collection, sortCtx);
                        }
                     }
                  }
               }
            }

            if (allColumn == 1) {
               this.expandProjection(collection, lookupCtx);
               this.visitProjection(collection, (BsonObjectNode)lookupCtx.get(Identifier.Projection.code));
            }
         }

      }
   }

   public ParseResult visitDistinct(MongoNode ctx) {
      this.loadSchema(ctx);
      this.result.sqlType = "select";
      BsonObjectNode objNode = (BsonObjectNode)ctx;
      MongoNode collectionCtx = objNode.get(Identifier.Distinct.code);
      String collection = collectionCtx.value().toString();
      if (this.checkCollection(collection)) {
         this.result.addCollection(collection);
         MongoNode keyCtx = objNode.get(Identifier.Key.code);
         if (keyCtx.type() == BsonNode.BsonNodeType.Basic) {
            this.result.addColumn(collection, keyCtx.value().toString(), true, keyCtx);
         }

         BsonObjectNode queryCtx = (BsonObjectNode)objNode.get(Identifier.Query.code);
         this.visitFilter(collection, queryCtx, false);
         return this.result;
      } else {
         return new ParseResult();
      }
   }

   public ParseResult visitDrop(MongoNode ctx) {
      this.loadSchema(ctx);
      this.result.sqlType = "drop";
      BsonObjectNode objNode = (BsonObjectNode)ctx;
      MongoNode collectionCtx = objNode.get(Identifier.Drop.code);
      String collection = collectionCtx.value().toString();
      if (this.checkCollection(collection)) {
         this.result.addCollection(collection);
         return this.result;
      } else {
         return new ParseResult();
      }
   }

   public ParseResult visitCreate(MongoNode ctx) {
      return null;
   }

   public ParseResult visitCreateIndexes(MongoNode ctx) {
      this.loadSchema(ctx);
      this.result.sqlType = "createIndexes";
      BsonObjectNode createIndexesCtx = (BsonObjectNode)ctx;
      MongoNode indexNode = createIndexesCtx.get(Identifier.CreateIndexes.code);
      String collection = indexNode.value().toString();
      if (!this.checkCollection(collection)) {
         return new ParseResult();
      } else {
         this.result.addCollection(collection);
         BsonArrayNode indexes = (BsonArrayNode)createIndexesCtx.get(Identifier.Indexes.code);

         for(int i = 0; i < indexes.size(); ++i) {
            BsonObjectNode mongoNode = (BsonObjectNode)indexes.get(i);
            this.visitIndexAddColumn(collection, mongoNode);
         }

         return this.result;
      }
   }

   private void visitIndexAddColumn(String collection, BsonObjectNode indexKey) {
      BsonObjectNode keys = (BsonObjectNode)indexKey.get(Identifier.Key.code);
      Map<String, MongoNode> children = keys.children();
      if (children.containsKey(Identifier.AllColumnsIndex.code)) {
         this.resultAddAllColumn(collection, false);
      } else {
         this.visitFilter(collection, keys, false);
      }

   }

   public ParseResult visitDropIndexes(MongoNode ctx) {
      this.loadSchema(ctx);
      this.result.sqlType = "dropIndexes";
      BsonObjectNode dropIndexesCtx = (BsonObjectNode)ctx;
      MongoNode indexNode = dropIndexesCtx.get(Identifier.DropIndexes.code);
      String collection = indexNode.value().toString();
      if (this.checkCollection(collection)) {
         this.result.addCollection(collection);
         MongoNode keys = dropIndexesCtx.get(Identifier.Index.code);
         if (keys.type() == BsonNode.BsonNodeType.Object) {
            this.visitFilter(collection, keys, false);
         }

         return this.result;
      } else {
         return new ParseResult();
      }
   }

   public ParseResult visitReIndex(MongoNode ctx) {
      this.loadSchema(ctx);
      this.result.sqlType = "reIndex";
      BsonObjectNode dropIndexesCtx = (BsonObjectNode)ctx;
      MongoNode indexNode = dropIndexesCtx.get(Identifier.ReIndex.code);
      String collection = indexNode.value().toString();
      if (this.checkCollection(collection)) {
         this.result.addCollection(collection);
         return this.result;
      } else {
         return new ParseResult();
      }
   }

   public ParseResult visitRenameCollection(MongoNode ctx) {
      this.result.sqlType = "renameCollection";
      BsonObjectNode renameCollectionCtx = (BsonObjectNode)ctx;
      MongoNode name = renameCollectionCtx.get(Identifier.RenameCollection.code);
      String dbAndCollection = name.value().toString();
      String[] split = dbAndCollection.split("\\.");
      String db = split[0];
      this.result.addSchema(db);
      String collection = split[1];
      if (this.checkCollection(collection)) {
         this.result.addCollection(collection);
         return this.result;
      } else {
         return new ParseResult();
      }
   }

   public ParseResult visitMapreduce(MongoNode ctx) {
      this.loadSchema(ctx);
      this.result.sqlType = "select";
      BsonObjectNode mapreduceCtx = (BsonObjectNode)ctx;
      MongoNode mapreduce = mapreduceCtx.get(Identifier.Mapreduce.code);
      String collection = mapreduce.value().toString();
      if (this.checkCollection(collection)) {
         this.result.addCollection(collection);
         BsonBasicNode map = (BsonBasicNode)mapreduceCtx.get(Identifier.Map.code);
         String mapString = map.getValue().toString();
         String[] split = mapString.split("this.");
         int length = split.length;
         String column = split[1].substring(0, split[1].indexOf(","));
         this.result.addColumn(collection, column, true, ctx);
         if (length == 3) {
            String columnTwo = split[2].substring(0, split[2].indexOf(")"));
            this.result.addColumn(collection, columnTwo, true, ctx);
         }

         BsonObjectNode query = (BsonObjectNode)mapreduceCtx.get(Identifier.Query.code);
         if (query != null) {
            this.visitFilter(collection, query, false);
         }

         BsonObjectNode sortCtx = (BsonObjectNode)mapreduceCtx.get(Identifier.Sort.code);
         if (sortCtx != null) {
            this.visitSort(collection, sortCtx);
         }

         return this.result;
      } else {
         return new ParseResult();
      }
   }

   public ParseResult visitCreateUser(MongoNode ctx) {
      this.result.sqlType = "user";
      BsonObjectNode createUserCtx = (BsonObjectNode)ctx;
      MongoNode createUser = createUserCtx.get(Identifier.CreateUser.code);
      String userName = createUser.value().toString();
      List<ParseResult.RoleDb> roleDbList = this.getRoleDbs(createUserCtx);
      this.result.addUser(userName, roleDbList);
      return this.result;
   }

   public ParseResult visitSaslSupportedMechs(MongoNode ctx) {
      this.result.sqlType = "user";
      BsonObjectNode loginCtx = (BsonObjectNode)ctx;
      MongoNode loginUser = loginCtx.get(Identifier.SaslSupportedMechs.code);
      String userName = loginUser.value().toString().split("\\.")[1];
      this.result.addUser(userName, (List)null);
      return this.result;
   }

   public ParseResult visitUpdateUser(MongoNode ctx) {
      this.result.sqlType = "user";
      BsonObjectNode loginCtx = (BsonObjectNode)ctx;
      MongoNode updateUser = loginCtx.get(Identifier.UpdateUser.code);
      String userName = updateUser.value().toString();
      List<ParseResult.RoleDb> roleDbList = this.getRoleDbs(loginCtx);
      if (CollectionUtils.isEmpty(roleDbList)) {
         this.result.addUser(userName, (List)null);
      } else {
         this.result.addUser(userName, roleDbList);
      }

      return this.result;
   }

   public ParseResult visitUsersInfo(MongoNode ctx) {
      this.result.sqlType = "user";
      BsonObjectNode loginCtx = (BsonObjectNode)ctx;
      MongoNode usersInfo = loginCtx.get(Identifier.UsersInfo.code);
      String userName = usersInfo.value().toString();
      this.result.addUser(userName, (List)null);
      return this.result;
   }

   public ParseResult visitGrantRolesToUser(MongoNode ctx) {
      this.result.sqlType = "user";
      BsonObjectNode grantCtx = (BsonObjectNode)ctx;
      MongoNode usersInfo = grantCtx.get(Identifier.GrantRolesToUser.code);
      String userName = usersInfo.value().toString();
      List<ParseResult.RoleDb> roleDbList = this.getRoleDbs(grantCtx);
      this.result.addUser(userName, roleDbList);
      return this.result;
   }

   public ParseResult visitRevokeRolesFromUser(MongoNode ctx) {
      this.result.sqlType = "user";
      BsonObjectNode grantCtx = (BsonObjectNode)ctx;
      MongoNode usersInfo = grantCtx.get(Identifier.RevokeRolesFromUser.code);
      String userName = usersInfo.value().toString();
      List<ParseResult.RoleDb> roleDbList = this.getRoleDbs(grantCtx);
      this.result.addUser(userName, roleDbList);
      return this.result;
   }

   public ParseResult visitDropUser(MongoNode ctx) {
      this.result.sqlType = "user";
      BsonObjectNode grantCtx = (BsonObjectNode)ctx;
      MongoNode usersInfo = grantCtx.get(Identifier.DropUser.code);
      String userName = usersInfo.value().toString();
      this.result.addUser(userName, (List)null);
      return this.result;
   }

   public ParseResult visitCreateCollection(MongoNode ctx) {
      return null;
   }

   private List<ParseResult.RoleDb> getRoleDbs(BsonObjectNode grantCtx) {
      BsonArrayNode roles = (BsonArrayNode)grantCtx.get(Identifier.Roles.code);
      List<ParseResult.RoleDb> roleDbList = new ArrayList();
      if (Objects.nonNull(roles)) {
         for(int i = 0; i < roles.size(); ++i) {
            if (roles.get(i).type() != BsonNode.BsonNodeType.Basic) {
               BsonObjectNode roleAndDb = (BsonObjectNode)roles.get(i);
               ParseResult.RoleDb roleDb = ParseResult.addRoleDb(roleAndDb.get(Identifier.Role.code).value().toString(), roleAndDb.get(Identifier.Db.code).value().toString());
               roleDbList.add(roleDb);
            }
         }
      }

      return roleDbList;
   }

   public ParseResult visitCommand(MongoNode ctx) {
      this.loadSchema(ctx);
      return null;
   }

   private MongoNode matchNode(String match, MongoNode from) {
      List<MongoNode> mongoNodes = this.matchNodes(match, from);
      return mongoNodes != null && mongoNodes.size() != 0 ? (MongoNode)mongoNodes.get(0) : null;
   }

   private List<MongoNode> matchNodes(String match, MongoNode from) {
      if (from == null) {
         return new ArrayList();
      } else {
         List<MongoNode> list = new ArrayList();
         if (from.type() == BsonNode.BsonNodeType.Basic) {
            return list;
         } else {
            if (from.type() == BsonNode.BsonNodeType.Object) {
               BsonObjectNode ctx = (BsonObjectNode)from;

               for(Map.Entry<String, MongoNode> entry : ctx.children().entrySet()) {
                  if (((String)entry.getKey()).equals(match)) {
                     list.add(entry.getValue());
                  }

                  list.addAll(this.matchNodes(match, (MongoNode)entry.getValue()));
               }
            } else if (from.type() == BsonNode.BsonNodeType.Array) {
               BsonArrayNode ctx = (BsonArrayNode)from;

               for(MongoNode child : ctx.children()) {
                  list.addAll(this.matchNodes(match, child));
               }
            }

            return list;
         }
      }
   }

   private void loadSchema(MongoNode ctx) {
      if (this.result.statement != null) {
         this.result.statement = ctx;
      }

      if (ctx.type() == BsonNode.BsonNodeType.Array) {
         BsonArrayNode array = (BsonArrayNode)ctx;
         MongoNode last = array.get(array.size() - 1);
         this.loadSchema(last);
      } else if (ctx.type() == BsonNode.BsonNodeType.Object) {
         BsonObjectNode objCtx = (BsonObjectNode)ctx;
         MongoNode node = objCtx.get(Identifier.DB.code);
         if (node != null) {
            this.schema = node.value().toString();
            this.result.schemas.add(this.schema);
         }
      }

   }
}
