package hiro.yoshioka.ast.sql.mongo.util;

import hiro.yoshioka.ast.sql.AbsSQLParser;
import hiro.yoshioka.ast.sql.AbsSimpleNode;
import hiro.yoshioka.ast.sql.mongo.MongoAstNode;
import hiro.yoshioka.ast.sql.mongo.MongoAstNodeType;
import hiro.yoshioka.ast.sql.mongo.MongoSqlType;
import hiro.yoshioka.ast.sql.mongo.MongoToken;
import hiro.yoshioka.ast.sql.oracle.SimpleNode;
import hiro.yoshioka.ast.sql.oracle.WolfSQLParser;
import hiro.yoshioka.ast.sql.oracle.util.WolfParserUtil;
import hiro.yoshioka.ast.sql.util.ASTFormatingInfo;
import hiro.yoshioka.ast.sql.util.BindInfo;
import hiro.yoshioka.ast.sql.util.ParserUtil;
import hiro.yoshioka.sql.resource.DBSchema;
import hiro.yoshioka.sql.resource.DBTable;
import hiro.yoshioka.sql.resource.IDBTable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.StringReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;

public class WolfMongoParserUtil extends ParserUtil {
	// mydb3.col1.find( {}, {_id:1,name:1,type:1,count:1,info:1} )
	static final Pattern FIND_PATTERN = Pattern
			.compile(
					"([^ .\t\n\f\r]+)\\.([^ .\t\n\f\r]+)\\.find\\s*\\(\\s*(\\{[^}]*})?\\s*,?\\s*(\\{[^}]*})?\\s*\\)(\\.sort\\(\\s*(\\{[^}]*})?\\s*)?",
					Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
	// mydb3.col1.insert( {"_id":1,"name":1,"type":1,"count":1,"info":1} )
	static final Pattern INSERT_PATTERN = Pattern
			.compile(
					"([^ .\t\n\f\r]+)\\.([^ .\t\n\f\r]+)\\.(insert|delete)\\s*\\(\\s*(\\{.*})?\\s*\\)",
					Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
	static final Pattern UPDATE_PATTERN = Pattern.compile(
			"([^ .\t\n\f\r]+)\\.([^ .\t\n\f\r]+)\\.update\\s*\\(\\s*[\r\n]?"
					+ "\\s*(\\{.*})\\s*,?[\r\n]" + "\\s*(\\{.*})?\\s*,?[\r\n]?"
					+ "\\s*(true|false)?\\s*,?[\r\n]?"
					+ "\\s*(true|false)?\\s*\\)", Pattern.CASE_INSENSITIVE);

	private MongoSqlType sqlType;
	private IDBTable firstTable;
	private Boolean upsert, multi;
	private BasicDBObject insertObject;
	private BasicDBObject updateObject;
	private BasicDBObject removeMustMatch;
	private BasicDBObject queryUsedToSearch;
	private BasicDBObject returnFields;
	private BasicDBObject orderBy;
	private MongoAstNode rootNode;

	@Override
	public AbsSimpleNode getRoot() {
		return rootNode;
	}

	public static void main(String[] args) {

		String test = "mydb3.col1.find(\n {}, { \"info\":1}).sort({\"hoge\":1})";
		// String test = "mydb3.col1.find( {}, {\"_id\":1,\"info.y\":1}";
		Matcher m = FIND_PATTERN.matcher(test);
		if (m.find()) {
			System.out.println("count:" + m.groupCount());
			System.out.println("DB:" + m.group(1));
			System.out.println("Collection:" + m.group(2));
			System.out.println("options:" + m.group(3));
			BasicDBObject o = (BasicDBObject) JSON.parse(m.group(3));
			System.out.println("o=" + o.getClass() + "//" + o);
			System.out.println("options:" + m.group(4));
			o = (BasicDBObject) JSON.parse(m.group(4));
			System.out.println("o=" + o.getClass() + "//" + o);
			System.out.println("sort:" + m.group(5));
			System.out.println("sort:" + m.group(6));
		}
		System.out.println("=============");
		test = "mydb3.col1.find(   )";
		m = FIND_PATTERN.matcher(test);
		if (m.find()) {
			System.out.println("count:" + m.groupCount());
			System.out.println("DB:" + m.group(1));
			System.out.println("Collection:" + m.group(2));
			String options3 = m.group(3);
			if (options3 != null) {
				System.out.println("options:" + m.group(3));
				BasicDBObject o = (BasicDBObject) JSON.parse(m.group(3));
				System.out.println("o=" + o.getClass() + "//" + o);
				System.out.println("options:" + m.group(4));
			}
		}
		System.out.println("=============");
		test = "mydb3.col1.insert(\n   { \"info\":1, \"_id\":\"hoe\"})";
		// String test = "mydb3.col1.find( {}, {\"_id\":1,\"info.y\":1}";
		m = INSERT_PATTERN.matcher(test);
		if (m.find()) {
			System.out.println("count:" + m.groupCount());
			System.out.println("DB:" + m.group(1));
			System.out.println("Collection:" + m.group(2));
			System.out.println("options:" + m.group(3));
			BasicDBObject o = (BasicDBObject) JSON.parse(m.group(4));
			System.out.println("o=" + o.getClass() + "//" + o);

		}
		System.out.println("=============");
		test = "mydb3.col1.update(  { \"name\": { \"first\":\"Hiro\", \"last\":\"yon\" }, \"eye\": 2 },\n { $inc : { \"age\" : 1 } },\ntrue\ntrue )";
		// String test = "mydb3.col1.find( {}, {\"_id\":1,\"info.y\":1}";
		m = UPDATE_PATTERN.matcher(test);
		if (m.find()) {
			System.out.println("count:" + m.groupCount());
			System.out.println("DB:" + m.group(1));
			System.out.println("Collection:" + m.group(2));
			System.out.println("options:" + m.group(3));
			BasicDBObject o = (BasicDBObject) JSON.parse(m.group(3));
			System.out.println("o=" + o.getClass() + "//" + o);
			System.out.println("options4:" + m.group(4));
			System.out.println("options5:" + m.group(5));
			System.out.println("options6:" + m.group(6));

		}
		System.out.println("======  no upsert =======");
		test = "mydb3.col1.update(  { \"name\": { \"first\":\"Hiro\", \"last\":\"yon\" }, \"eye\": 2 },\n { $set:{ \"age\" : 1 } } )";
		// String test = "mydb3.col1.find( {}, {\"_id\":1,\"info.y\":1}";
		m = UPDATE_PATTERN.matcher(test);
		if (m.find()) {
			System.out.println("count:" + m.groupCount());
			System.out.println("DB:" + m.group(1));
			System.out.println("Collection:" + m.group(2));
			System.out.println("toSearch3:" + m.group(3));
			BasicDBObject o = (BasicDBObject) JSON.parse(m.group(3));
			System.out.println("o=" + o.getClass() + "//" + o);
			System.out.println("options4:" + m.group(4));
			// o = (BasicDBObject) JSON.parse(m.group(4));
			System.out.println("o=" + o.getClass() + "//" + o);
			System.out.println("options5:" + m.group(5));
			System.out.println("options6:" + m.group(6));

		}
		BasicDBObject updObj0 = (BasicDBObject) JSON
				.parse("{ \"$set\":{ \"age\" : 1 } }");
		System.out.println(updObj0);
	}

	public WolfMongoParserUtil(File file) throws FileNotFoundException {
		super(file);
	}

	public WolfMongoParserUtil(String string) {
		super(string);
	}

	@Override
	public IDBTable getFirstTable() {
		return firstTable;
	}

	public MongoSqlType getSqlType() {
		return sqlType;
	}

	@Override
	public boolean parse() {
		rootNode = new MongoAstNode(null, MongoAstNodeType.Root);
		if (!doneParse) {
			doneParse = true;
			try {
				Matcher m = FIND_PATTERN.matcher(getSQLStatement());
				if (m.find()) {
					queryUsedToSearch = null;
					returnFields = null;
					orderBy = null;
					DBSchema schema = new DBSchema(null);
					MongoAstNode node = new MongoAstNode(rootNode,
							MongoAstNodeType.Schema);
					node.setFirstToken(new MongoToken(m, 1));
					schema.setName(m.group(1));
					firstTable = new DBTable(schema);
					firstTable.setName(m.group(2));
					node = new MongoAstNode(rootNode, MongoAstNodeType.Table);
					node.setFirstToken(new MongoToken(m, 2));
					if (m.start(3) > 0) {
						node = new MongoAstNode(rootNode,
								MongoAstNodeType.QueryUsedToSearch);
						node.setFirstToken(new MongoToken(m, 3));
						queryUsedToSearch = (BasicDBObject) JSON.parse(m
								.group(3));

						if (m.start(4) > 0) {
							node = new MongoAstNode(rootNode,
									MongoAstNodeType.ReturnFields);
							node.setFirstToken(new MongoToken(m, 4));
							returnFields = (BasicDBObject) JSON.parse(m
									.group(4));
						}
					}
					if (m.start(6) > 0) {
						node = new MongoAstNode(rootNode,
								MongoAstNodeType.OrderBy);
						node.setFirstToken(new MongoToken(m, 6));
						orderBy = (BasicDBObject) JSON.parse(m.group(6));
					}
					sqlType = MongoSqlType.Find;
					flg = true;
				} else {
					m = INSERT_PATTERN.matcher(getSQLStatement());
					if (m.find()) {
						DBSchema schema = new DBSchema(null);
						MongoAstNode node = new MongoAstNode(rootNode,
								MongoAstNodeType.Schema);
						node.setFirstToken(new MongoToken(m, 1));
						schema.setName(m.group(1));
						firstTable = new DBTable(schema);
						node = new MongoAstNode(rootNode,
								MongoAstNodeType.Table);
						node.setFirstToken(new MongoToken(m, 2));
						firstTable.setName(m.group(2));
						node = new MongoAstNode(rootNode,
								MongoAstNodeType.InsertObject);
						node.setFirstToken(new MongoToken(m, 4));
						insertObject = (BasicDBObject) JSON.parse(m.group(4));
						sqlType = MongoSqlType.parse(m.group(3));
						flg = true;
					} else {
						m = UPDATE_PATTERN.matcher(getSQLStatement());
						if (m.find()) {
							MongoAstNode node = new MongoAstNode(rootNode,
									MongoAstNodeType.Schema);
							node.setFirstToken(new MongoToken(m, 1));
							DBSchema schema = new DBSchema(null);
							schema.setName(m.group(1));
							node = new MongoAstNode(rootNode,
									MongoAstNodeType.Table);
							node.setFirstToken(new MongoToken(m, 2));
							firstTable = new DBTable(schema);
							firstTable.setName(m.group(2));
							node = new MongoAstNode(rootNode,
									MongoAstNodeType.QueryUsedToSearch);
							node.setFirstToken(new MongoToken(m, 3));
							queryUsedToSearch = (BasicDBObject) JSON.parse(m
									.group(3));
							node = new MongoAstNode(rootNode,
									MongoAstNodeType.UpdateObject);
							node.setFirstToken(new MongoToken(m, 4));
							updateObject = (BasicDBObject) JSON.parse(m
									.group(4));
							if (m.group(5) != null) {
								node = new MongoAstNode(rootNode,
										MongoAstNodeType.Upsert);
								node.setFirstToken(new MongoToken(m, 5));
								upsert = Boolean.parseBoolean(m.group(5));
								if (m.group(6) != null) {
									node = new MongoAstNode(rootNode,
											MongoAstNodeType.Multi);
									node.setFirstToken(new MongoToken(m, 6));
									multi = Boolean.parseBoolean(m.group(6));
								}
							}
							sqlType = MongoSqlType.Update;
							flg = true;
						} else {
							if (getSQLStatement().trim().toLowerCase()
									.startsWith("select ")) {
								String sql = getSQLStatement();
								WolfParserUtil pu = new WolfParserUtil(sql);
								if (pu.parse()) {
									Sql2MongoVisitor visitor = new Sql2MongoVisitor(
											sql);
									visitor.visit((SimpleNode) pu.getRoot(),
											null);
									firstTable = visitor.getFirstTable();
									System.out.println("needs?"
											+ visitor.needsRetry());
									if (visitor.needsRetry()) {
										sql = sql.replaceAll(visitor
												.getCollection(),
												visitor.getCollection()
														.replace('.', '_'));
										pu = new WolfParserUtil(sql);
										pu.parse();
										visitor = new Sql2MongoVisitor(sql);
										visitor.visit(
												(SimpleNode) pu.getRoot(), null);
									}
									returnFields = visitor.getReturnFields();
									queryUsedToSearch = visitor
											.getQueryUsedToSearch();
									orderBy = visitor.getOrderBy();
									sqlType = MongoSqlType.Find;
									flg = true;
								} else {
									flg = false;
								}
							} else {
								flg = false;
							}
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
				flg = false;
			} catch (Error e) {
				flg = false;
			}
		}
		fLogger.info("--- Parsing result[" + flg + "] ---");
		// if (queryUsedToSearch == null) {
		// queryUsedToSearch = new BasicDBObject();
		// }
		// if (returnFields == null) {
		// returnFields = new BasicDBObject();
		// }
		// if (orderBy == null) {
		// orderBy = new BasicDBObject();
		// }
		return flg;
	}

	@Override
	public boolean doQuery() {
		return MongoSqlType.Find.equals(sqlType);

	}

	public AbsSQLParser createParser(StringReader reader) {
		return new WolfSQLParser(reader);
	}

	public AbsSQLParser createParser(FileInputStream stream) {
		return new WolfSQLParser(stream);
	}

	public String getWhereStatement() {
		return null;
	}

	@Override
	public BindInfo[] binds() {
		return BindInfo.EMPTY_ARRAYS;
	}

	@Override
	public AbsSimpleNode[] getFoldingNodes() {
		return null;
	}

	public BasicDBObject getUpdateObject() {
		return updateObject;
	}

	public Boolean isMulti() {
		return multi;
	}

	public Boolean isUpsert() {
		return upsert;
	}

	public BasicDBObject getRemoveMustMatch() {
		return removeMustMatch;
	}

	public BasicDBObject getInsertObject() {
		return insertObject;
	}

	public DBObject getQueryUsesedToSearch() {
		return queryUsedToSearch;
	}

	public DBObject getReturnFields() {
		return returnFields;
	}

	public DBObject getOrderBy() {
		return orderBy;
	}

	@Override
	public String getFormattedString(ASTFormatingInfo info) {
		if (!doneParse) {
			doneParse = true;
			try {
				parse();
			} catch (Exception e) {
				e.printStackTrace();
				flg = false;
			} catch (Error e) {
				flg = false;
			}
		}
		StringBuilder ret = new StringBuilder();
		switch (getSqlType()) {
		case Find:
			ret.append(String.format("%s.%s.find ( %n", firstTable.getParent()
					.getName(), firstTable.getName()));
			if (queryUsedToSearch == null) {
				ret.append(String.format("  {},%n"));
			} else {
				ret.append(String.format("  %s,%n",
						queryUsedToSearch.toString()));
			}
			if (returnFields == null) {
				ret.append(String.format("  {}%n"));
			} else {
				ret.append(String.format("  %s%n", returnFields.toString()));
			}
			if (orderBy == null) {
				ret.append(String.format(")%n"));
			} else {
				ret.append(String.format(").sort( %s )%n", orderBy.toString()));
			}
			break;
		case Insert:
			ret.append(String.format("%s.%s.insert ( %n", firstTable
					.getParent().getName(), firstTable.getName()));
			if (queryUsedToSearch == null) {
				ret.append(String.format("  {},%n"));
			} else {
				ret.append(String.format("  %s,%n", insertObject.toString()));
			}
			ret.append(String.format(")%n"));
			break;
		case Update:
			ret.append(String.format("%s.%s.update ( %n", firstTable
					.getParent().getName(), firstTable.getName()));
			if (queryUsedToSearch == null) {
				ret.append(String.format("  {},%n"));
			} else {
				ret.append(String.format("  %s,%n",
						queryUsedToSearch.toString()));
			}
			if (updateObject == null) {
				ret.append(String.format("  {}%n"));
			} else {
				ret.append(String.format("  %s%n", updateObject.toString()));
			}
			if (upsert != null && multi != null) {
				ret.append(String.format("  %b,%b %n", upsert, multi));
			}
			ret.append(String.format(")%n"));
			break;
		case Delete:
			ret.append(String.format("%s.%s.delete ( %n", firstTable
					.getParent().getName(), firstTable.getName()));

			ret.append(String.format(")%n"));
			break;
		}

		return ret.toString();
	}
}
