package util.sqlparse.visitor.mongo;

import bean.DataBase;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import util.sqlparse.visitor.common.Objects;

public class WhereVisitor implements MongoVisitor<Boolean> {
   private DataBase dataBase;
   private String schema;
   private List<String> wheres = new ArrayList();

   public List<String> getWheres() {
      return this.wheres;
   }

   public Boolean visitFind(MongoNode context) {
      BsonObjectNode ctx = (BsonObjectNode)context;
      MongoNode find = ctx.get(Identifier.Find.code);
      String collection = find.value().toString();
      BsonObjectNode filter = (BsonObjectNode)ctx.get(Identifier.Filter.code);
      this.visitFilter(collection, filter, false);
      return true;
   }

   private void visitJsonValue(String collection, MongoNode json, boolean isOutput) {
      if (json != null && !StringUtils.equals(json.toString(), "{}")) {
         this.visitFilter("", json, false);
      }
   }

   private void visitFilter(String collection, MongoNode filter, boolean isOutput) {
      if (filter != null && !StringUtils.equals(filter.toString(), "{}")) {
         this.wheres.add(filter.toString());
      }
   }

   public Boolean visitInsert(MongoNode ctx) {
      return true;
   }

   public Boolean visitUpdate(MongoNode 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();
      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);
      MongoNode mergeObjects = this.matchNode(Identifier.MergeObjects.code, dataCtx);
      if (mergeObjects != null) {
         BsonObjectNode mergeCtx = (BsonObjectNode)mergeObjects;
         this.visitFilter(collection, mergeCtx, true);
      }

      return true;
   }

   public Boolean visitFindAndModify(MongoNode ctx) {
      BsonObjectNode objCtx = (BsonObjectNode)ctx;
      MongoNode mongoNode = objCtx.get(Identifier.FindAndModify.code);
      String collection = mongoNode.value().toString();
      BsonObjectNode filter = (BsonObjectNode)objCtx.get(Identifier.Query.code);
      if (Objects.nonNull(filter)) {
         this.visitFilter(collection, filter, false);
      }

      return true;
   }

   public Boolean visitDelete(MongoNode ctx) {
      BsonArrayNode array = (BsonArrayNode)ctx;
      BsonObjectNode collectionCtx = (BsonObjectNode)array.get(array.size() - 1);
      String collection = collectionCtx.get(Identifier.Delete.code).value().toString();

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

      return true;
   }

   public Boolean visitCommand(MongoNode ctx) {
      return null;
   }

   public Boolean visitCount(MongoNode ctx) {
      BsonObjectNode objCtx = (BsonObjectNode)ctx;
      MongoNode collectionCtx = objCtx.get(Identifier.Count.code);
      String collection = collectionCtx.value().toString();
      MongoNode mongoNode = objCtx.get(Identifier.Query.code);
      this.visitFilter(collection, mongoNode, false);
      return true;
   }

   public Boolean visitAggregate(MongoNode ctx) {
      BsonObjectNode objNode = (BsonObjectNode)ctx;
      MongoNode collectionCtx = objNode.get(Identifier.Aggregate.code);
      String collection = collectionCtx.value().toString();
      BsonArrayNode pipeCtx = (BsonArrayNode)objNode.get(Identifier.Pipeline.code);
      int allColumn = 1;
      this.visitPipeline(collection, pipeCtx, allColumn);
      return true;
   }

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

      return allColumn;
   }

   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();
      BsonArrayNode pipeCtx = (BsonArrayNode)map.get(Identifier.Pipeline.code);
      if (pipeCtx != null) {
         this.visitPipeline(collection, pipeCtx, allColumn);
      }

   }

   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();
      BsonArrayNode pipeCtx = (BsonArrayNode)lookupCtx.get(Identifier.Pipeline.code);
      if (pipeCtx != null) {
         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);
            }
         }
      }

   }

   public Boolean visitDistinct(MongoNode ctx) {
      BsonObjectNode objNode = (BsonObjectNode)ctx;
      MongoNode collectionCtx = objNode.get(Identifier.Distinct.code);
      String collection = collectionCtx.value().toString();
      BsonObjectNode queryCtx = (BsonObjectNode)objNode.get(Identifier.Query.code);
      this.visitFilter(collection, queryCtx, false);
      return true;
   }

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

   public Boolean visitDrop(MongoNode ctx) {
      return null;
   }

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

   public Boolean visitCreateIndexes(MongoNode ctx) {
      return null;
   }

   public Boolean visitDropIndexes(MongoNode ctx) {
      return null;
   }

   public Boolean visitReIndex(MongoNode ctx) {
      return null;
   }

   public Boolean visitRenameCollection(MongoNode ctx) {
      return null;
   }

   public Boolean visitMapreduce(MongoNode ctx) {
      return null;
   }

   public Boolean visitCreateUser(MongoNode ctx) {
      return null;
   }

   public Boolean visitSaslSupportedMechs(MongoNode ctx) {
      return null;
   }

   public Boolean visitUpdateUser(MongoNode ctx) {
      return null;
   }

   public Boolean visitUsersInfo(MongoNode ctx) {
      return null;
   }

   public Boolean visitGrantRolesToUser(MongoNode ctx) {
      return null;
   }

   public Boolean visitRevokeRolesFromUser(MongoNode ctx) {
      return null;
   }

   public Boolean visitDropUser(MongoNode ctx) {
      return null;
   }

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