package util.sqlparse.visitor.mongo;

import java.util.Map;
import org.bson.BsonArray;
import org.bson.BsonDbPointer;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.json.JsonWriterSettings;
import org.bson.types.Decimal128;
import org.bson.types.ObjectId;

public class Printer {
   private StringBuilder sb = new StringBuilder();

   public Printer write(char c) {
      this.sb.append(c);
      return this;
   }

   public Printer write(String data) {
      this.sb.append(data);
      return this;
   }

   public Printer write(MongoNode node) {
      if (node == null) {
         return this;
      } else {
         this.sb.append(this.bson2Json(node.data()));
         return this;
      }
   }

   public Printer keyValue(String name, String value) {
      this.sb.append(this.string(name)).append(":").append(value);
      return this;
   }

   public Printer map(Map<String, MongoNode> map) {
      if (map == null) {
         return this;
      } else {
         this.lbparen();
         int cnt = 0;

         for(Map.Entry<String, MongoNode> entry : map.entrySet()) {
            MongoNode value = (MongoNode)entry.getValue();
            if (value != null) {
               this.keyValue((String)entry.getKey(), (MongoNode)entry.getValue());
               this.comma();
               ++cnt;
            }
         }

         if (cnt > 0) {
            this.removeLast();
         }

         this.rbparen();
         return this;
      }
   }

   public int mapCount(Map<String, MongoNode> map) {
      if (map == null) {
         return 0;
      } else {
         int cnt = 0;

         for(Map.Entry<String, MongoNode> entry : map.entrySet()) {
            MongoNode value = (MongoNode)entry.getValue();
            if (value != null) {
               ++cnt;
            }
         }

         return cnt;
      }
   }

   public Printer keyValue(String name, MongoNode value) {
      this.sb.append(this.string(name)).append(":").append(this.bson2Json(value.data()));
      return this;
   }

   public Printer line(String data) {
      this.sb.append(data + "\n");
      return this;
   }

   public Printer db() {
      this.sb.append("db.");
      return this;
   }

   public Printer comma() {
      this.sb.append(',');
      return this;
   }

   public Printer dot() {
      this.sb.append('.');
      return this;
   }

   public Printer lbparen() {
      this.sb.append('{');
      return this;
   }

   public Printer rbparen() {
      this.sb.append('}');
      return this;
   }

   public Printer lparen() {
      this.sb.append('(');
      return this;
   }

   public Printer allParen() {
      this.sb.append("()");
      return this;
   }

   public Printer allParen(String str) {
      this.sb.append("(").append(str).append(")");
      return this;
   }

   public Printer rparen() {
      this.sb.append(')');
      return this;
   }

   public Printer array(MongoNode... nodes) {
      this.sb.append('[');

      for(MongoNode node : nodes) {
         this.sb.append(this.bson2Json(node.data()));
         this.sb.append(',');
      }

      if (nodes.length > 0) {
         this.removeLast();
      }

      this.sb.append(']');
      return this;
   }

   public Printer call(String method) {
      this.sb.append(method);
      this.sb.append('(');
      this.sb.append(')');
      return this;
   }

   public Printer call(String method, BsonValue... args) {
      this.sb.append(method);
      this.sb.append('(');
      int cnt = 0;

      for(BsonValue arg : args) {
         if (arg != null) {
            ++cnt;
            String json = this.bson2Json(arg);
            this.sb.append(json).append(',');
         }
      }

      if (cnt > 0) {
         this.removeLast();
      }

      this.sb.append(')');
      return this;
   }

   public Printer call(String method, String... args) {
      this.sb.append(method);
      this.sb.append('(');
      int cnt = 0;

      for(String arg : args) {
         if (arg != null && arg.length() != 0) {
            ++cnt;
            this.sb.append(arg).append(',');
         }
      }

      if (cnt > 0) {
         this.removeLast();
      }

      this.sb.append(')');
      return this;
   }

   public Printer call(String method, MongoNode... nodes) {
      this.sb.append(method);
      this.sb.append('(');
      int cnt = 0;

      for(MongoNode node : nodes) {
         if (node != null) {
            ++cnt;
            BsonValue value = node.data();
            String json = this.bson2Json(value);
            this.sb.append(json).append(',');
         }
      }

      if (cnt > 0) {
         this.removeLast();
      }

      this.sb.append(')');
      return this;
   }

   public Printer callInsert(String method, MongoNode param, MongoNode... nodes) {
      this.sb.append(method);
      this.sb.append('(');
      int cnt = 0;
      if (nodes != null && nodes.length > 1) {
         this.sb.append('[');
      }

      for(MongoNode node : nodes) {
         if (node != null) {
            ++cnt;
            BsonValue value = node.data();
            String json = this.bson2Json(value);
            this.sb.append(json).append(',');
         }
      }

      if (cnt > 0) {
         this.removeLast();
      }

      if (nodes != null && nodes.length > 1) {
         this.sb.append(']');
      }

      if (param != null) {
         this.sb.append(',').append(param);
      }

      this.sb.append(')');
      return this;
   }

   private void removeLast() {
      this.sb.deleteCharAt(this.sb.length() - 1);
   }

   private String bson2Json(BsonValue value) {
      if (value == null) {
         return "null";
      } else if (value.isDocument()) {
         JsonWriterSettings settings = new JsonWriterSettings();
         return value.asDocument().toJson(settings);
      } else if (!value.isArray()) {
         if (value.isNull()) {
            return "null";
         } else if (value.isString()) {
            return this.string(((BsonString)value).getValue());
         } else if (value.isInt32()) {
            return this.int32(value.asInt32().getValue());
         } else if (value.isInt64()) {
            return this.int64(value.asInt64().getValue());
         } else if (value.isDecimal128()) {
            return this.decimal128(value.asDecimal128().getValue());
         } else if (value.isTimestamp()) {
            return this.timestamp(value.asTimestamp().getValue());
         } else if (value.isDateTime()) {
            return this.date(value.asDateTime().getValue());
         } else if (value.isBoolean()) {
            return this.bool(value.asBoolean().getValue());
         } else if (value.isRegularExpression()) {
            return this.regex(value.asRegularExpression().getPattern());
         } else if (value.isBinary()) {
            return this.binary(value.asBinary().getData());
         } else if (value.isJavaScript()) {
            return value.asJavaScript().getCode();
         } else if (value.isJavaScriptWithScope()) {
            return value.asJavaScriptWithScope().getCode();
         } else if (value.isSymbol()) {
            return this.symbol(value.asSymbol().getSymbol());
         } else if (value.isObjectId()) {
            return this.objectId(value.asObjectId().getValue());
         } else if (value.isDBPointer()) {
            return this.dbpointer(value.asDBPointer());
         } else {
            return value.isDouble() ? this.doubles(value.asDouble().getValue()) : value.toString();
         }
      } else {
         StringBuilder s = new StringBuilder();
         s.append('[');
         BsonArray children = value.asArray();

         for(BsonValue child : children) {
            String v = this.bson2Json(child);
            s.append(v);
            s.append(',');
         }

         if (children.size() > 0) {
            s.deleteCharAt(s.length() - 1);
         }

         s.append(']');
         return s.toString();
      }
   }

   public String string(String value) {
      return "\"" + value + "\"";
   }

   private String bool(Boolean v) {
      return v + "";
   }

   private String int32(int v) {
      return v + "";
   }

   private String int64(long v) {
      return v + "";
   }

   private String doubles(Double d) {
      return d + "";
   }

   private String decimal128(Decimal128 v) {
      return v.toString();
   }

   private String timestamp(long value) {
      return "new Date(" + value + ")";
   }

   private String date(long value) {
      return "new Date(" + value + ")";
   }

   private String regex(String pattern) {
      return pattern;
   }

   private String binary(byte[] buffer) {
      StringBuilder s = new StringBuilder();
      s.append("[");

      for(byte b : buffer) {
         s.append(b + "");
      }

      s.append("]");
      return s.toString();
   }

   private String symbol(String name) {
      return "Symbol(\"" + name + "\")";
   }

   private String objectId(ObjectId oid) {
      return "ObjectId(\"" + oid.toHexString() + "\")";
   }

   private String dbpointer(BsonDbPointer dbp) {
      return "DBPointer(\"" + dbp.getNamespace() + "\"," + this.objectId(dbp.getId()) + "))";
   }

   public String toString() {
      return this.sb.toString();
   }
}
