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 org.bson.BsonString;
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.utils.Alias;

public class FieldVisitor implements MongoVisitor<Boolean> {
   private ParseResult result;
   private DataBase dataBase;
   private String schema;
   private final Alias alias = new Alias();
   protected List<ValueMemo> values = new ArrayList();

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

   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, Identifier.Filter.code);
      return true;
   }

   private void visitFilter(String collection, MongoNode filter, boolean isOutput, String key) {
      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) {
               if (!name.startsWith("$")) {
                  ValueMemo item = this.getValueMemo(collection, ((BsonBasicNode)filter).parent, name, bfilter, "=");
                  this.values.add(item);
               } else {
                  ValueMemo item = this.getValueMemo(collection, ((BsonBasicNode)filter).parent, ((BsonBasicNode)filter).parent.getName(), bfilter, name);
                  this.values.add(item);
               }
            }
         } else if (filter.type() == BsonNode.BsonNodeType.Array) {
            BsonArrayNode afilter = (BsonArrayNode)filter;

            for(MongoNode child : afilter.children) {
               if (child instanceof BsonBasicNode) {
                  String operator = "IN";
                  String itemKey = key;
                  if (key.startsWith("$")) {
                     operator = key;
                     itemKey = filter.getParent().getName();
                  }

                  ValueMemo item = this.getValueMemo(collection, afilter, itemKey, child, operator);
                  this.values.add(item);
               } else {
                  this.visitFilter(collection, child, isOutput, key);
               }
            }
         } else if ("$expr".equalsIgnoreCase(key)) {
            this.visitExpr(collection, filter, key);
         } 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, (String)entry.getKey());
            }
         }

      }
   }

   private void visitExpr(String collection, MongoNode filter, String key) {
      if (filter != null && !StringUtils.equals(filter.toString(), "{}")) {
         if (filter.type() != BsonNode.BsonNodeType.Basic) {
            if (filter.type() == BsonNode.BsonNodeType.Array) {
               BsonArrayNode afilter = (BsonArrayNode)filter;
               String operator = "=";
               String itemKey = null;
               MongoNode value = null;

               for(MongoNode child : afilter.children) {
                  if (child instanceof BsonBasicNode) {
                     if (((BsonBasicNode)child).getValue().isString()) {
                        BsonString bsonString = ((BsonBasicNode)child).getValue().asString();
                        if (bsonString.getValue().startsWith("$")) {
                           itemKey = bsonString.getValue().substring(1);
                           if (value != null) {
                              ValueMemo item = this.getValueMemo(collection, afilter, itemKey, value, operator);
                              this.values.add(item);
                              break;
                           }
                        } else {
                           if (itemKey != null) {
                              ValueMemo item = this.getValueMemo(collection, afilter, itemKey, child, operator);
                              this.values.add(item);
                              break;
                           }

                           value = child;
                        }
                     }
                  } else {
                     this.visitExpr(collection, child, key);
                  }
               }
            } 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.visitExpr(collection, (MongoNode)entry.getValue(), (String)entry.getKey());
               }
            }
         }

      }
   }

   public Boolean visitInsert(MongoNode 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();

      for(int i = 0; i < array.children.size() - 1; ++i) {
         BsonObjectNode mongoNode = (BsonObjectNode)array.children.get(i);

         for(Map.Entry<String, MongoNode> entry : mongoNode.children().entrySet()) {
            String key = (String)entry.getKey();
            if (!key.equalsIgnoreCase(Identifier.ID.code)) {
               this.visitFilter(collection, (MongoNode)entry.getValue(), false, key);
            }
         }
      }

      return true;
   }

   private void visitItem(String collection, BsonObjectNode mongoNode, Map.Entry<String, MongoNode> entry, String key) {
      MongoNode value = (MongoNode)entry.getValue();
      ValueMemo item = this.getValueMemo(collection, mongoNode, key, value, "SET");
      this.values.add(item);
   }

   private ValueMemo getValueMemo(String collection, MongoNode mongoNode, String key, MongoNode value, String operator) {
      FieldMemo field = this.getField(key);
      this.setTable(collection, field);
      ValueMemo item = new ValueMemo();
      item.field = field;
      item.refMongo = value;
      item.nodeMongo = mongoNode;
      item.value = value.value();
      item.isPreDefined = false;
      item.operator = operator;
      return item;
   }

   private FieldMemo getField(String key) {
      FieldMemo field = new FieldMemo();
      field.name = key;
      field.alias = key;
      return field;
   }

   private void setTable(String collection, FieldMemo field) {
      TableMemo tableMemo = new TableMemo();
      tableMemo.name = collection;
      tableMemo.nowName = collection;
      tableMemo.alias = collection;
      tableMemo.schema = this.schema;
      field.table = tableMemo;
   }

   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.Q.code);
      this.visitFilter(collection, filter, false, Identifier.Q.code);
      BsonObjectNode update = (BsonObjectNode)dataCtx.get(Identifier.U.code);
      this.visitFilter(collection, update, false, Identifier.U.code);
      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, Identifier.Q.code);
      }

      return true;
   }

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

   public Boolean visitFindAndModify(MongoNode ctx) {
      BsonObjectNode objCtx = (BsonObjectNode)ctx;
      MongoNode mongoNode = objCtx.get(Identifier.FindAndModify.code);
      String collection = mongoNode.value().toString();
      this.result.addCollection(collection);
      MongoNode query = objCtx.get(Identifier.Query.code);
      this.visitFilter(collection, query, false, Identifier.Query.code);
      MongoNode update = objCtx.get(Identifier.Update.code);
      this.visitFilter(collection, update, false, Identifier.Update.code);
      return true;
   }

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

   public Boolean visitAggregate(MongoNode context) {
      BsonObjectNode objNode = (BsonObjectNode)context;
      MongoNode collectionCtx = objNode.get(Identifier.Aggregate.code);
      String collection = collectionCtx.value().toString();
      BsonArrayNode pipeCtx = (BsonArrayNode)objNode.get(Identifier.Pipeline.code);
      if (pipeCtx != null) {
         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, Identifier.Match.code);
            }

            BsonObjectNode facetCtx = (BsonObjectNode)pCtx.get(Identifier.Facet.code);
            if (facetCtx != null) {
               Map<String, MongoNode> children = facetCtx.children();

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

                     for(MongoNode child : bsonArrayNode.children) {
                        if (child instanceof BsonObjectNode) {
                           BsonObjectNode bsonObjectNode = (BsonObjectNode)child;
                           BsonObjectNode match = (BsonObjectNode)bsonObjectNode.get(Identifier.Match.code);
                           if (match != null) {
                              this.visitFilter(collection, match, false, (String)entry.getKey());
                           }
                        }
                     }
                  }
               }
            }
         }
      }

      return true;
   }

   public Boolean visitDistinct(MongoNode context) {
      BsonObjectNode objNode = (BsonObjectNode)context;
      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, Identifier.Query.code);
      return true;
   }

   public Boolean visitDrop(MongoNode context) {
      return true;
   }

   public Boolean visitCreate(MongoNode context) {
      return true;
   }

   public Boolean visitCreateIndexes(MongoNode context) {
      return true;
   }

   public Boolean visitDropIndexes(MongoNode context) {
      return true;
   }

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

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

   public Boolean visitMapreduce(MongoNode ctxTemp) {
      return true;
   }

   public Boolean visitCreateUser(MongoNode context) {
      return true;
   }

   public Boolean visitSaslSupportedMechs(MongoNode context) {
      return true;
   }

   public Boolean visitUpdateUser(MongoNode context) {
      return true;
   }

   public Boolean visitUsersInfo(MongoNode context) {
      return true;
   }

   public Boolean visitGrantRolesToUser(MongoNode context) {
      return true;
   }

   public Boolean visitRevokeRolesFromUser(MongoNode context) {
      return true;
   }

   public Boolean visitDropUser(MongoNode context) {
      return true;
   }

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