package hiro.yoshioka.sql.mongo;

import hiro.yoshioka.ast.sql.mongo.util.WolfMongoParserUtil;
import hiro.yoshioka.sdh.DatabaseType;
import hiro.yoshioka.sdh.StringRecordData;
import hiro.yoshioka.sdh2.ResultSetDataHolder2;
import hiro.yoshioka.sql.AbsNoSQL;
import hiro.yoshioka.sql.ITransactionSQL;
import hiro.yoshioka.sql.engine.GettingResourceRequest;
import hiro.yoshioka.sql.engine.MirroringRequest;
import hiro.yoshioka.sql.engine.MongoTransactionRequest;
import hiro.yoshioka.sql.engine.Request;
import hiro.yoshioka.sql.engine.SQLOperationType;
import hiro.yoshioka.sql.engine.TransactionRequest;
import hiro.yoshioka.sql.params.ConnectionProperties;
import hiro.yoshioka.sql.params.DBUserPass;
import hiro.yoshioka.sql.resource.DBColumn;
import hiro.yoshioka.sql.resource.DBCrossRefference;
import hiro.yoshioka.sql.resource.DBRoot;
import hiro.yoshioka.sql.resource.DBSchema;
import hiro.yoshioka.sql.resource.DBTable;
import hiro.yoshioka.sql.resource.IDBColumn;
import hiro.yoshioka.sql.resource.IDBResource;
import hiro.yoshioka.sql.resource.IDBSchema;
import hiro.yoshioka.sql.resource.IDBSequence;
import hiro.yoshioka.sql.resource.IDBTable;
import hiro.yoshioka.sql.resource.INameFamily;
import hiro.yoshioka.util.FileUtil;
import hiro.yoshioka.util.SQLDataType;
import hiro.yoshioka.util.StringUtil;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.net.UnknownHostException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.MongoInternalException;
import com.mongodb.MongoOptions;
import com.mongodb.WriteResult;
import com.mongodb.util.JSON;

public class MongoSQL extends AbsNoSQL {
	boolean needsAuth;
	Mongo mongo;
	String host;
	MongoOptions options;
	Map<String, DBUserPass> dbPassWordMap = new LinkedHashMap<String, DBUserPass>();
	Map<String, DB> dbMap = new LinkedHashMap<String, DB>();
	private static int SAMPLING_COUNT = 10;
	private IDBSchema defaultSchema;
	private static final String ADMIN = "admin";

	Version version;

	static class Version {
		static final Pattern p = Pattern.compile("(\\d+)\\.(\\d+)(\\.(\\d+))?");
		int majarVersion;
		int minorVersion;
		int minorVersion2;

		public Version(String versionString) {
			Matcher m = p.matcher(versionString);
			if (m.find()) {
				majarVersion = Integer.parseInt(m.group(1));
				minorVersion = Integer.parseInt(m.group(2));
				if (m.group(4) != null) {
					minorVersion2 = Integer.parseInt(m.group(4));
				}
				System.err.println("  pattern [" + m.group(0) + "]");
			} else {
				System.err.println("whats pattern?[" + versionString + "]");
			}
		}

		public boolean isHigherEqualVersion(int majarVersion, int minorVersion,
				int minorVersion2) {
			if (this.majarVersion < majarVersion) {
				return false;
			}
			if (this.minorVersion < minorVersion) {
				return false;
			}
			if (this.minorVersion2 < minorVersion2) {
				return false;
			}
			return true;
		}

	}

	public MongoSQL() {
	}

	public DatabaseType getDatabaseType() {
		return DatabaseType.MONGO;
	}

	protected DBRoot getMetaData(GettingResourceRequest request) {
		IDBSchema schema = null;
		DBRoot root = getRoot();
		try {
			this.capturing = true;

			if (request.canceld()) {
				return null;
			}
			if (!request.targetType.isOnlyTable()) {
				root = new DBRoot("Mongo");
				setRoot(root);
				root.setPropertyValue("DatabaseProductName", "Mongo");
				fLogger.info("new DBRoot");

				List<String> list = getDatabaseNames();
				String currentName = ADMIN;
				if (this.defaultSchema != null) {
					currentName = this.defaultSchema.getName();
				}
				if (list.size() == 0) {
					schema = new DBSchema(root);
					schema.setName(currentName);
					root.putResource(schema.getName(), schema);
					root.setDefaultSchema(schema);
					root.setCurrentSchema(schema);
					System.out
							.println("dumpAllCollectionNames => No collection.");
				} else {
					for (String name : list) {
						schema = new DBSchema(root);
						schema.setName(name);
						root.putResource(schema.getName(), schema);
						if (currentName.equalsIgnoreCase(name)) {
							root.setDefaultSchema(schema);
							root.setCurrentSchema(schema);
						}

						DB db = getDBWithAuth(name);

						if (ADMIN.equals(name)) {
							System.out
									.println("2222222222222:::::hasAdminAuthenticate()="
											+ hasAdminAuthenticate());
							// if (!hasAdminAuthenticate()) {
							// continue;
							// }
						}
						Set<String> colls = db.getCollectionNames();
						for (String s : colls) {
							if ("system.indexes".equalsIgnoreCase(s)) {
								continue;
							}
							DBTable table = new DBTable(schema);
							table.setName(s);
							table.setTableType("TABLE");
							schema.putTable(table);
							if (doCaptureColumn(root, table, request)) {
								createColumnDefs(table, request
										.getConnectionProperties()
										.getRecursiveSearchDepth());

							}
							for (DBObject dobj : db.getCollection(s)
									.getIndexInfo()) {
								System.out.println("index???" + dobj);

							}
						}
					}
				}
			} else {
				IDBTable table = (IDBTable) request.selectionResource;
				table.removeAllColumns();
				createColumnDefs(table, request.getConnectionProperties()
						.getRecursiveSearchDepth());
			}
		} catch (Throwable e) {
			fLogger.error(StringUtil.EMPTY_STRING, e);
			return null;
		} finally {
			this.capturing = false;
		}
		return root;
	}

	private void createColumnDefs(IDBTable table, int recursiveSearchDepth) {

		DB db = getDBWithAuth(table.getParent().getName());
		DBCollection collection = db.getCollection(table.getName());
		DBCursor cur = collection.find();
		cur.limit(SAMPLING_COUNT);
		Map<IDBColumn, TmpType> typeMap = new java.util.HashMap<IDBColumn, MongoSQL.TmpType>();
		while (cur.hasNext()) {
			DBObject dobj = cur.next();
			recurseCreateDBColumn(table, dobj, 0, recursiveSearchDepth, typeMap);
		}
		for (IDBColumn col : typeMap.keySet()) {
			DBColumn column = (DBColumn) col;
			TmpType tmp = typeMap.get(column);
			column.setDataTypeString(tmp.getDataTypeString());
			column.setDataType(SQLDataType.parse(tmp.getDataType()));
			column.setSize(tmp.getSize());
		}
		typeMap.clear();
		typeMap = null;
	}

	private void recurseCreateDBColumn(IDBResource res, DBObject dobj,
			int depth, int maxDepth, Map<IDBColumn, TmpType> typeMap) {
		for (String ss : dobj.keySet()) {
			DBColumn column = null;

			IDBResource child = res.getResource(ss);
			if (child == null || !(child instanceof IDBColumn)) {
				if (res instanceof IDBTable) {
					column = new DBColumn((IDBTable) res);
				} else if (res instanceof IDBColumn) {
					column = new DBColumn((IDBColumn) res);
				}
				column.setName(ss);

				if ("_id".equals(ss) && depth == 0) {
					column.setPKey(true);
					column.setDataType(SQLDataType.CHAR);
					column.setDataTypeString("ObjectId");
					column.setSize(24);
				} else {
					Object o = dobj.get(ss);
					TmpType t = typeMap.get(column);
					if (t == null) {
						t = new TmpType(column.getName());
						typeMap.put(column, t);
					}
					t.add(o);
					if (o != null && o instanceof BasicDBObject) {
						BasicDBObject bobj = (BasicDBObject) o;
						column.setDataType(SQLDataType.STRUCT);
						if (depth < maxDepth) {
							recurseCreateDBColumn(column, bobj, depth + 1,
									maxDepth, typeMap);
						}
					}
				}
				res.putResource(ss, column);
			} else {
				if ("_id".equals(ss) && depth == 0) {
					continue;
				}
				column = (DBColumn) child;
				Object o = dobj.get(ss);

				TmpType t = typeMap.get(column);
				if (t == null) {
					t = new TmpType(column.getName());
					typeMap.put(column, t);
				}
				t.add(o);

				if (o != null && o instanceof BasicDBObject) {
					BasicDBObject bobj = (BasicDBObject) o;
					column.setDataType(SQLDataType.STRUCT);
					if (depth < maxDepth) {
						recurseCreateDBColumn(column, bobj, depth + 1,
								maxDepth, typeMap);
					}
				}
			}
		}
	}

	class TmpType {
		String name;
		int maxLength;
		Map<Integer, Integer> lengthCountMap = new TreeMap<Integer, Integer>();
		Map<Class, Integer> classCountMap = new java.util.HashMap<Class, Integer>();

		public TmpType(String name) {
			this.name = name;
		}

		public int getSize() {
			return maxLength;
		}

		public String getDataTypeString() {
			String ret = StringUtil.EMPTY_STRING;
			for (Class clazz : classCountMap.keySet()) {
				if (clazz.equals(BasicDBObject.class)) {
					return clazz.getSimpleName();

				} else {
					ret = clazz.getSimpleName();
				}
			}
			return ret;
		}

		public int getDataType() {
			int beforeType = Types.NULL;
			int type = Types.NULL;
			int maxCount = 0;

			for (Class clazz : classCountMap.keySet()) {

				if (clazz.equals(BasicDBObject.class)) {
					type = Types.STRUCT;
				} else if (clazz.equals(Date.class)
						|| Date.class.isAssignableFrom(clazz)) {
					type = Types.DATE;
				} else if (clazz.equals(Number.class)
						|| Number.class.isAssignableFrom(clazz)) {
					if (clazz.equals(Integer.class)) {
						type = Types.INTEGER;
					} else if (clazz.equals(Double.class)) {
						type = Types.DOUBLE;
					} else if (clazz.equals(Float.class)) {
						type = Types.FLOAT;
					} else if (clazz.equals(BigInteger.class)) {
						type = Types.BIGINT;
					} else if (clazz.equals(Short.class)) {
						type = Types.SMALLINT;
					} else if (clazz.equals(Byte.class)) {
						type = Types.TINYINT;
					} else {
						type = Types.NUMERIC;
					}
				} else {
					type = Types.VARCHAR;
					if (maxLength > 0 && lengthCountMap.size() == 1) {
						type = Types.CHAR;
					}
				}
			}
			return type;
		}

		public void add(Object o) {
			if (o == null) {
				Integer c = classCountMap.get(null);
				if (c == null) {
					c = new Integer(1);
					classCountMap.put(null, c);
				} else {
					classCountMap.put(null, c + 1);
				}
			} else {
				Integer c = classCountMap.get(o.getClass());
				if (c == null) {
					c = new Integer(1);
					classCountMap.put(o.getClass(), c);
				} else {
					classCountMap.put(o.getClass(), c + 1);
				}
				int len = o.toString().length();
				if (maxLength < len) {
					maxLength = len;
				}
			}
		}
	}

	public void addAuthenticate(String dbName, String user, String pass) {
		addAuthenticate(new DBUserPass(dbName, user, pass));
	}

	public void addAuthenticate(DBUserPass dup) {
		if (dup == null) {
			fLogger.warn("Nothing authenticate informain...");
		} else {
			dbPassWordMap.put(dup.db, dup);
			DB db = dbMap.get(dup.db);
			if (db != null) {
				db.authenticate(dup.user, dup.pass.toCharArray());
			}

		}
	}

	public List<String> getDatabaseNames() {
		try {
			return mongo.getDatabaseNames();
		} catch (MongoException e) {
			System.out.println("code=" + e.getCode());
			System.out.println("mes=" + e.getLocalizedMessage());
			if (e.getCode() == -3) {
				List<String> retList = new ArrayList<String>();
				for (String dbname : dbPassWordMap.keySet()) {
					DB ret = mongo.getDB(dbname);
					if (ret != null) {
						retList.add(dbname);
					}
				}
				return retList;
			} else {
				e.printStackTrace();
			}
		}
		return Collections.EMPTY_LIST;
	}

	private boolean hasAdminAuthenticate() {
		System.out.println(dbPassWordMap);
		return dbPassWordMap.containsKey(ADMIN);
	}

	public void dumpAllCollectionNames() {
		List<String> list = getDatabaseNames();
		if (list.size() == 0) {
			System.out.println("dumpAllCollectionNames => No collection.");
		} else {
			for (String name : list) {
				System.out.println("name=" + name);
				if (ADMIN.equals(name)) {
					System.out.println("hasAdminAuthenticate()="
							+ hasAdminAuthenticate());
					if (!hasAdminAuthenticate()) {
						continue;
					}
				}
				DB db = mongo.getDB(name);

				System.out.println(db);
				Set<String> colls = db.getCollectionNames();

				for (String s : colls) {
					System.out.println("  " + s);
				}
			}
		}
	}

	public void addUser(String dbname, String username, String passwd) {
		mongo.getDB(dbname).addUser(username, passwd.toCharArray());
	}

	public ResultSetDataHolder2 curso2Rdh(DBCursor cur) {

		Set<String> allKeySets = new LinkedHashSet<String>();
		List<DBObject> curList = new ArrayList<DBObject>();
		while (cur.hasNext()) {
			DBObject obj = cur.next();
			curList.add(obj);
			allKeySets.addAll(obj.keySet());
		}
		ResultSetDataHolder2 rdh = new ResultSetDataHolder2(
				allKeySets.toArray(new String[allKeySets.size()]), null,
				getDatabaseType());
		for (DBObject obj : curList) {
			StringRecordData[] record = new StringRecordData[allKeySets.size()];
			int iCol = 0;
			for (String key : allKeySets) {
				Object o = obj.get(key);
				if (o != null) {
					System.out.println(o.getClass());
				}
				if (obj.containsField(key)) {
					if (isMakeBlobData() && o instanceof byte[]) {
						BufferedInputStream in = new BufferedInputStream(
								new ByteArrayInputStream((byte[]) o));
						String path = rsUtil.makeBinary(key, in);
						record[iCol] = new StringRecordData(
								StringUtil.nvl(path));
					} else {
						record[iCol] = new StringRecordData(StringUtil.nvl(o));
					}
				} else {
					record[iCol] = new StringRecordData(null);
				}
				iCol++;
			}
			rdh.addRow(record);
		}
		return rdh;
	}

	private void nullClear() {
		if (mongo != null) {
			mongo.close();
		}
		mongo = null;
		defaultSchema = null;
		dbMap.clear();
	}

	public void init(ConnectionProperties connectionProperties)
			throws SQLException {
		fLogger.info("host:" + host + " options:" + options);
		nullClear();
		this.host = connectionProperties.getHost();
		this.options = connectionProperties.getMongoOptions();
		try {
			if (StringUtil.isEmpty(host)) {
				mongo = new Mongo();
			} else {
				if (options == null) {
					mongo = new Mongo(host);
				} else {
					mongo = new Mongo(host, options);
				}
			}
			if (mongo != null) {
				getDBWithAuth(ADMIN);
				this.version = new Version(mongo.getVersion());
			}
			connectionProperties.setConnected(true);
		} catch (UnknownHostException e) {
			throw new SQLException(e.getMessage(), e);
		} catch (MongoInternalException e) {
			throw new SQLException(e.getMessage(), e);
		} catch (MongoException e) {
			throw new SQLException(e.getMessage(), e);
		}
	}

	private DB getDBWithAuth(String name) {
		DB db = dbMap.get(name);
		if (db == null) {
			db = mongo.getDB(name);
			dbMap.put(name, db);
			DBUserPass auth = dbPassWordMap.get(name);
			if (auth != null) {
				db.authenticate(auth.user, auth.pass.toCharArray());
			}
		}
		return db;
	}

	@Override
	public boolean canDoOperation(SQLOperationType operation) {

		switch (operation) {
		case RENAME_FIELD:
			return this.version.isHigherEqualVersion(1, 7, 2);
		case CONNECT:
			return (mongo == null);
		case CLOSE:
		case RESOURCE_CAPTION:
		case PREPARED_EXECUTE:
		case PREPARED_EXECUTE_QUERY:
		case SELECT_ALL:
			return (mongo != null);
		}
		return super.canDoOperation(operation);
	}

	@Override
	public Set<String> getSchemas() {
		try {
			fLogger.info("start");
			capturing = true;
			return new LinkedHashSet<String>(getDatabaseNames());
		} catch (Exception e) {
			fLogger.error(StringUtil.EMPTY_STRING, e);
			return Collections.EMPTY_SET;
		} finally {
			fLogger.info("end");
			capturing = false;
		}
	}

	@Override
	public boolean doOperation(SQLOperationType operation, Request request)
			throws SQLException {

		TransactionRequest treq = null;
		if (request instanceof TransactionRequest) {
			treq = (TransactionRequest) request;
		}
		MongoTransactionRequest mreq = null;
		if (request instanceof MongoTransactionRequest) {
			mreq = (MongoTransactionRequest) request;
		}
		setMakeBlobData(request.makeBlob);

		boolean retCode = true;
		switch (operation) {
		case CONNECT:
			connect(request.getConnectionProperties());
			break;
		case CLOSE:
			close();
			break;
		case COUNT:
			treq.setResultCount(count(treq.getIDBTable()));
			break;
		case COUNTS:
			treq.setRDH(counts(treq.getIDBTables()));
			break;
		case RESOURCE_MIRRORING:
			MirroringRequest mirroring_request = (MirroringRequest) request;
			retCode = createMirroredTableTo(mirroring_request);
			break;
		case RESOURCE_CAPTION:
			getMetaData((GettingResourceRequest) request);
			break;
		case EXPLAIN_PLAN:
			break;
		case SELECT_SESSION:
			break;
		case SELECT_LOCK:
			break;
		case SELECT_ALL:
			IDBTable table = treq.getIDBTable();
			treq.setRDH(getAllData2(table, treq));
			break;
		case PREPARED_EXECUTE_QUERY:
			treq.setRDH(find(treq.getIDBTable(), mreq.getQueryUsesedToSearch(),
					mreq.getReturnFields(), mreq.getOrderBy(), 0,
					(int) treq.getMaxRownum()));
			break;
		case CREATE_TRIG_FNC_PROC:
			break;
		case WORST_SQL:
			break;
		case CHECK_VALIDATION:
			break;
		case PREPARED_EXECUTE:
			WriteResult result = null;
			result = preparedExecute(mreq);
			System.out.println("executed preparedExecute");
			retCode = StringUtil.isEmpty(result.getError());
			break;
		default:
			return super.doOperation(operation, request);
		}

		return retCode;

	}

	public long count(IDBTable table) throws SQLException {
		DB targetDB = getDBByIDBTable(table);
		if (!targetDB.collectionExists(table.getName())) {
			fLogger.fatal(String.format("no collection at this DB[%s]",
					targetDB.getName()));
		}

		return targetDB.getCollection(table.getName()).count();
	}

	public ResultSetDataHolder2 counts(IDBTable[] tables) throws SQLException {
		ResultSetDataHolder2 rdh = new ResultSetDataHolder2(
				new String[] { "CNT" }, null, getDatabaseType());
		for (IDBTable table : tables) {
			rdh.addRow(new String[] { String.valueOf(count(table)) });
		}

		return rdh;
	}

	@Override
	public ResultSetDataHolder2 executePrepareQuery(String sql_statement,
			String[] binds) throws SQLException {
		WolfMongoParserUtil util = new WolfMongoParserUtil(sql_statement);
		if (util.parse()) {
			return find(util.getFirstTable(), util.getQueryUsesedToSearch(),
					util.getReturnFields(), util.getOrderBy(), 0, 100);
		}
		return null;
	}

	public ResultSetDataHolder2 renameField(IDBColumn before, String afterName)
			throws SQLException {
		IDBTable table = (IDBTable) before.getParent();
		DB targetDB = getDBByIDBTable(table);
		if (!targetDB.collectionExists(table.getName())) {
			fLogger.fatal(String.format("no collection at this DB[%s]",
					targetDB.getName()));
		}
		DBCollection collection = targetDB.getCollection(table.getName());

		System.out.println("can?"
				+ canDoOperation(SQLOperationType.RENAME_FIELD));
		BasicDBObject updObj0 = (BasicDBObject) JSON.parse(String.format(
				"{ \"$rename\":{\"%s\" : \"%s\"} }", before.getName(),
				afterName));

		System.out.println("updObj0:" + updObj0);
		long time = System.currentTimeMillis();
		WriteResult result = collection.update(new BasicDBObject(), updObj0,
				false, true);

		return WriteResult2RSH(System.currentTimeMillis() - time, result);

		// DBCursor cur = collection.find();
		// boolean ret = false;
		//
		// while (cur.hasNext()) {
		// DBObject o = cur.next();
		// if (o.containsField(before.getName())) {
		// System.out.println("contain " + before.getName());
		// o.put(afterName, o.get(before.getName()));
		// o.removeField(before.getName());
		// System.out.println("removed ");
		// ret = true;
		// } else {
		// System.out.println("no contain");
		// }
		// targetDB.
		// }

	}

	public ResultSetDataHolder2 find(IDBTable table,
			DBObject queryUsesedToSearch, DBObject returnFields,
			DBObject orderBy, int numToSkip, int batchSize) throws SQLException {
		DB targetDB = getDBByIDBTable(table);
		if (!targetDB.collectionExists(table.getName())) {
			fLogger.fatal(String.format("no collection at this DB[%s]",
					targetDB.getName()));
		}
		DBCollection collection = targetDB.getCollection(table.getName());
		DBCursor cur = null;
		if (orderBy == null || orderBy.keySet().size() == 0) {
			cur = collection.find(queryUsesedToSearch, returnFields, numToSkip,
					batchSize);
		} else {
			cur = collection.find(queryUsesedToSearch, returnFields, numToSkip,
					batchSize).sort(orderBy);
		}

		return curso2Rdh(cur);
	}

	@Override
	public ResultSetDataHolder2 getAllData2(IDBTable table, Request request)
			throws SQLException {
		DB targetDB = getDBByIDBTable(table);
		if (!targetDB.collectionExists(table.getName())) {
			System.err.println("no collection!!!!!!!!!!!");
		}
		DBCollection collection = targetDB.getCollection(table.getName());
		return getAllData2(collection, table, request);
	}

	public ResultSetDataHolder2 getAllData2(DBCollection collection,
			IDBTable table, Request request) throws SQLException {
		ResultSetDataHolder2 rdh2 = curso2Rdh(collection.find());
		if (rdh2 != null) {
			rdh2.setTableName(table.getComment());
			rdh2.setTableNameE(table.getName());
		}
		return rdh2;
	}

	private DB getDBByIDBTable(INameFamily table) {
		INameFamily schema = table.getParent();
		DB targetDB = getDBWithAuth(schema.getName());
		// if (schema != null) {
		// if (targetDB == null
		// || !schema.getName().equalsIgnoreCase(targetDB.getName())) {
		// targetDB = getDBWithAuth(schema.getName());
		// }
		// }

		return targetDB;
	}

	boolean doCaptureColumn(DBRoot root, IDBTable dbTable, Request request) {
		if (this._info == null) {
			return false;
		}
		if (this._info.isCaptureWithColumnInfo()) {
			return true;
		}
		DBRoot old = request.getConnectionProperties().getDBRootResource();
		if (old != null && old.isRecentryUsed(dbTable)) {
			return true;
		}
		if (root != null && root.isRecentryUsed(dbTable)) {
			return true;
		}
		return false;
	}

	private ResultSetDataHolder2 WriteResult2RSH(long time, WriteResult result) {
		if (StringUtil.isEmpty(result.getError())) {
			ResultSetDataHolder2 rdh = new ResultSetDataHolder2(new String[] {
					"result", "N", "lazy" }, null, getDatabaseType());
			rdh.addRow(new String[] { "success", String.valueOf(result.getN()),
					String.valueOf(result.isLazy()) });
			rdh.setWrapTime(time);
			return rdh;
		} else {
			ResultSetDataHolder2 rdh = new ResultSetDataHolder2(new String[] {
					"result", "Error", "N", "lazy" }, null, getDatabaseType());
			rdh.addRow(new String[] { "failure", result.getError(),
					String.valueOf(result.getN()),
					String.valueOf(result.isLazy()) });
			rdh.setWrapTime(time);
			return rdh;
		}
	}

	public WriteResult preparedExecute(MongoTransactionRequest mreq) {
		DB targetDB = getDBByIDBTable(mreq.getIDBTable());
		if (!targetDB.collectionExists(mreq.getIDBTable().getName())) {
			fLogger.fatal(String.format("no collection at this DB[%s]",
					targetDB.getName()));
		}
		DBCollection collection = targetDB.getCollection(mreq.getIDBTable()
				.getName());
		WriteResult result = null;
		long time = System.currentTimeMillis();
		switch (mreq.getSqlType()) {
		case Insert:
			result = collection.insert(mreq.getInsertObject());
			break;
		case Delete:
			result = collection.remove(mreq.getRemoveMustMatch());
			break;
		case Update:
			if (mreq.isUpsert() == null) {
				result = collection.update(mreq.getQueryUsesedToSearch(),
						mreq.getUpdateObject());
			} else {
				result = collection
						.update(mreq.getQueryUsesedToSearch(),
								mreq.getUpdateObject(), mreq.isUpsert(),
								mreq.isMulti());
			}
			break;
		}
		mreq.setRDH(WriteResult2RSH((System.currentTimeMillis() - time), result));

		return result;
	}

	@Override
	public boolean connect(ConnectionProperties properties) throws SQLException {
		this._info = properties;
		DBRoot root = _info.getDBRootResource();
		addAuthenticate(properties.getAdminAuthenticate());
		init(properties);
		if (root != null) {
			this.defaultSchema = root.getDefaultSchema();
		}
		return true;
	}

	@Override
	public boolean close() throws SQLException {
		_info.setConnected(false);
		_info = null;
		nullClear();
		return true;
	}

	// ----------------------------------------------------------------
	// [5] MIRRORING
	// ----------------------------------------------------------------
	@Override
	public boolean existsSchema(String name) throws SQLException {
		fLogger.info("check schema[" + name + "]");
		DB db = getDBWithAuth(name);
		return db != null;
	}

	@Override
	public String getDefaultSchemaName() {
		if (this.defaultSchema == null) {
			return ADMIN;
		}

		return defaultSchema.getName();
	}

	private void dropSchema(String schemaName, boolean cascade) {
		fLogger.info("dropDatabase[" + schemaName + "]");
		mongo.getDB(schemaName).dropDatabase();
	}

	private boolean existsTable(String targetDBName, String tableName) {
		fLogger.info(String.format("check DB.TABLE %s.%s", targetDBName,
				tableName));
		boolean ret = false;
		DB db = getDBWithAuth(targetDBName);
		if (db == null) {
		} else {
			try {
				ret = db.collectionExists(tableName);
			} catch (MongoException e) {
			}
		}
		fLogger.info(String.format("check DB.TABLE %s.%s result[%b]",
				targetDBName, tableName, ret));
		return ret;
	}

	private void dropTable(String targetDBName, String tableName) {
		fLogger.info(String.format("drop collection[%s#%s]", targetDBName,
				tableName));

		DB db = getDBWithAuth(targetDBName);
		try {
			db.getCollection(tableName).drop();
		} catch (MongoException e) {
		}

	}

	@Override
	public void setTableColumns(String schema, IDBTable table)
			throws SQLException {
		// TODO Auto-generated method stub

	}

	@Override
	public boolean createMirroredTableTo_FaseOnSchema(
			MirroringRequest mirroring_request) throws SQLException {

		fLogger.info("start.");

		Collection<String> schemaCollection = mirroring_request
				.getMappingToSchemaNames();
		mirroring_request.beginTask("Schema Migraion Start",
				schemaCollection.size());
		int cnt = 0;
		for (String schemaName : schemaCollection) {
			try {
				int dropCnt = 0;
				int createCnt = 0;
				cnt++;
				String text = String.format("DB Migtaion NOW %s.%s [%d/%d]",
						mirroring_request.getConnectionProperties()
								.getDisplayString(), schemaName, cnt,
						schemaCollection.size());
				mirroring_request.subTask(text);
				if (mirroring_request.isCanceled()) {
					return false;
				}
				mirroring_request.worked(1);
				if (StringUtil.isEmpty(schemaName)) {
					throw new IllegalArgumentException(
							"mapping to schemaName must be not null.");
				}
				boolean existsSchema = existsSchema(schemaName);
				mirroring_request.subTask(String.format(
						"Exists Schema Check... exists[%b]", existsSchema));
				if (existsSchema && mirroring_request.dropSchema) {
					mirroring_request.subTask(String.format("Drop Schema [%s]",
							schemaName));
					dropSchema(schemaName, mirroring_request.cascade);
					fLogger.info(String.format("Schema droped"));
					dropCnt++;
				}
				// if (!existsSchema || mirroring_request.isDrop()) {
				// mirroring_request.subTask(String.format(
				// "CREATE Schema [%s]", schemaName));
				// createSchema(schemaName);
				// fLogger.info(String.format("schema created"));
				// }
				// only postgres???
				boolean rlt = commit();
				fLogger.info(String.format("commited result[%b]", rlt));

				mirroring_request.addResultRecord(new String[] { "SCHEMA", "-",
						schemaName, StringUtil.EMPTY_STRING, "○",
						String.format("C:%d D:%d", createCnt, dropCnt), "-" });
			} catch (SQLException e) {
				mirroring_request.addResultRecord(new String[] { "SCHEMA", "-",
						schemaName, StringUtil.EMPTY_STRING, "×", "-",
						e.getMessage() });
				if (!mirroring_request.continuing) {
					throw e;
				}
				boolean rlt = rollback();
				fLogger.info(String.format("rollbacked result[%b]", rlt));
			} finally {
				mirroring_request.worked(1);
			}
		}

		System.out.println(mirroring_request.getRdh());
		fLogger.info("end.");
		return true;
	}

	@Override
	public boolean migration(ITransactionSQL osql, DBCrossRefference original,
			boolean drop, boolean cascade) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean createMirroredTableTo_FaseOnTable(
			MirroringRequest mirroring_request) throws SQLException {
		fLogger.info("start ");

		boolean retCode = true;
		for (String mappingFromSchemaName : mirroring_request.getTableMapping()
				.keySet()) {
			Set<MirroringRequest.MirroringTableInfo> tableInfoSet = mirroring_request
					.getTableMapping().get(mappingFromSchemaName);
			String mappingToSchemaName = mirroring_request
					.getMappingToSchemaName(mappingFromSchemaName);
			// if (StringUtil.isEmpty(mappingToSchemaName)) {
			// mappingToSchemaName = getDefaultSchemaName();
			// }
			mirroring_request.beginTask(String.format(
					"Table migration start, table total [%d] in [%s] ",
					tableInfoSet.size(), mappingToSchemaName), tableInfoSet
					.size());
			int i = 0;
			for (MirroringRequest.MirroringTableInfo mappingFromTableInfo : tableInfoSet) {
				i++;
				int retCnt = 0;
				try {
					IDBTable mappingFromTable = mappingFromTableInfo
							.getMappingFromDBTable(mappingFromSchemaName);
					boolean existsTable = existsTable(mappingToSchemaName,
							mappingFromTableInfo.name);

					mirroring_request.subTask(String.format(
							"Exists Table Checker... exists[%b]", existsTable));
					if (existsTable && mirroring_request.dropTable) {
						dropTable(mappingToSchemaName,
								mappingFromTableInfo.name);
					}

					// for table
					ResultSetDataHolder2 rdh = null;
					fLogger.info("" + mappingFromTableInfo);
					DB mappingToDB = getDBWithAuth(mappingToSchemaName);

					DBCollection collection = mappingToDB
							.getCollection(mappingFromTableInfo.name);
					try {
						rdh = mirroring_request.getMirroringFromSql()
								.getAllData2(mappingFromTable,
										mirroring_request);

						for (int r = 0; r < rdh.getRowCount(); r++) {
							WriteResult result = null;
							try {
								result = collection.insert(getDBObjectFromRdh(
										rdh, r, mirroring_request.makeBlob));
								retCnt++;
							} catch (MongoException e) {
								mirroring_request.addResultRecord(new String[] {
										"TABLE", "-",
										mappingFromTableInfo.name,
										StringUtil.EMPTY_STRING, "×",
										String.valueOf(retCnt),
										result.getError() });
								retCode = false;
							}
							if (!StringUtil.isEmpty(result.getError())) {
								System.err.println("result=" + result);
								if (!mirroring_request.continuing) {
									throw new RuntimeException(
											result.getError());
								}
							}
						}
						boolean commit_result = commit();
						mirroring_request
								.subTask(String.format(
										"Insert commited... result[%b]",
										commit_result));
						mirroring_request.addResultRecord(new String[] {
								"TABLE", "-", mappingFromTableInfo.name,
								StringUtil.EMPTY_STRING, "○",
								String.valueOf(retCnt), "-" });

					} catch (Exception e) {
						fLogger.warn(StringUtil.EMPTY_STRING, e);
						mirroring_request.addResultRecord(new String[] {
								"TABLE", "-", mappingFromTableInfo.name,
								StringUtil.EMPTY_STRING, "×",
								String.valueOf(retCnt), e.getMessage() });
						mirroring_request.setThrowable(e);
						retCode = false;
					} finally {
					}

					// System.out.println(getAllData2(table));
				} catch (SQLException e) {
					mirroring_request.addResultRecord(new String[] { "TABLE",
							"-", mappingFromTableInfo.name,
							StringUtil.EMPTY_STRING, "×",
							String.valueOf(retCnt), e.getMessage() });
					mirroring_request.setThrowable(e);

					if (!mirroring_request.continuing) {
						throw e;
					}
					rollback();
				} finally {
					mirroring_request.worked(1);
				}
			}
		}

		return retCode;
	}

	@Override
	public boolean migration(ITransactionSQL osql, IDBSequence original,
			boolean drop, boolean cascade, boolean noSchema)
			throws SQLException {
		return false;
	}

	private BasicDBObject getDBObjectFromRdh(ResultSetDataHolder2 rdh, int row,
			boolean makeBlob) throws SQLException, IOException {
		StringRecordData[] rowData = rdh.getStringRecordRow(row);

		BasicDBObject ret = new BasicDBObject();
		String[] keys = rdh.getKey();
		if (keys.length <= 1) {
			return ret;
		}
		for (int i = 1; i < keys.length; i++) {
			if (rowData[i].isNullValue()) {
				ret.put(keys[i], null);
			} else {
				if (keys[i].indexOf("BLOB") > 0) {
					System.out.println("h");
				}
				if (makeBlob && (rdh.isBinaly(i) || rdh.isBlob(i))) {
					File f = new File(rowData[i].getString());
					if (f.isFile() && f.exists()) {
						if (f.length() < 4 * 1024 * 1024) {
							// o.k.
							byte[] bytes = FileUtil.getBytes(f);
							ret.put(keys[i], bytes);
						} else {
							// needs grid fs
							System.err
									.println("size over Grid Fs not support yet");
						}
					} else {
						ret.put(keys[i], rowData[i].getString());
					}
				} else {
					ret.put(keys[i], rowData[i].getString());
				}
			}
		}
		if (fLogger.isTraceEnabled()) {
			fLogger.trace("getDBObjectFromRs ret=" + ret);
		}
		return ret;
	}

	private BasicDBObject getDBObjectFromRs(ResultSet all_rs)
			throws SQLException, IOException {
		ResultSetMetaData rs_meta = all_rs.getMetaData();

		BasicDBObject ret = new BasicDBObject();
		if (rs_meta == null || rs_meta.getColumnCount() == 0) {
			return ret;
		}
		for (int i = 1; i <= rs_meta.getColumnCount(); i++) {
			Object o = all_rs.getObject(i);
			String columnName = rs_meta.getColumnName(i);

			if (o instanceof Serializable) {
				// URL url = (URL) o;
				// ret.put(columnName, StringUtil.nvl(o));
				ret.put(columnName, o);
			} else {
				ret.put(columnName, o);
			}
		}
		if (fLogger.isTraceEnabled()) {
			fLogger.trace("getDBObjectFromRs ret=" + ret);
		}
		return ret;
	}

	@Override
	public boolean supportResultSet() {
		// TODO Auto-generated method stub
		return false;
	}

}
