package org.frameworkset.nosql.mongodb;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.frameworkset.util.StringUtil;
import com.mongodb.Bytes;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoClientOptions.Builder;
import com.mongodb.MongoCredential;
import com.mongodb.ReadPreference;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;
import com.mongodb.WriteConcernException;
import com.mongodb.WriteResult;

public class MongoDB {
	private static Method autoConnectRetryMethod;
	static {
		try {
			autoConnectRetryMethod = Builder.class.getMethod("autoConnectRetry", boolean.class);
		} catch (NoSuchMethodException e) {
			// // TODO Auto-generated catch block
			// e.printStackTrace();
		} catch (SecurityException e) {
			// // TODO Auto-generated catch block
			// e.printStackTrace();
		}
	}
	private static Logger log = Logger.getLogger(MongoDB.class);
	private String serverAddresses;
	private String option;
	private String writeConcern;
	private String readPreference;
	private Mongo mongoclient;
	private String mode = null;
	private boolean autoConnectRetry = true;
	private int connectionsPerHost = 500;
	private int maxWaitTime = 120000;
	private int socketTimeout = 0;
	private int connectTimeout = 15000;
	private int threadsAllowedToBlockForConnectionMultiplier = 50;
	private boolean socketKeepAlive = true;
	private List<ClientMongoCredential> credentials;
	private List<MongoCredential> mongoCredentials;

	public MongoDB getMongoClient() {

		// try {
		// Mongo mongoClient = new Mongo(Arrays.asList(new
		// ServerAddress("10.0.15.134", 27017),
		// new ServerAddress("10.0.15.134", 27018),
		// new ServerAddress("10.0.15.38", 27017),new
		// ServerAddress("10.0.15.39", 27017)
		// ));
		// mongoClient.addOption( Bytes.QUERYOPTION_SLAVEOK );
		// mongoClient.setWriteConcern(WriteConcern.JOURNAL_SAFE);
		//// ReadPreference.secondaryPreferred();
		// mongoClient.setReadPreference(ReadPreference.nearest());
		//// mongoClient.setReadPreference(ReadPreference.primaryPreferred());
		// return mongoClient;
		// } catch (Exception e) {
		// throw new java.lang.RuntimeException(e);
		// }
		return this;
	}
	
	public Mongo getMongo() {

		// try {
		// Mongo mongoClient = new Mongo(Arrays.asList(new
		// ServerAddress("10.0.15.134", 27017),
		// new ServerAddress("10.0.15.134", 27018),
		// new ServerAddress("10.0.15.38", 27017),new
		// ServerAddress("10.0.15.39", 27017)
		// ));
		// mongoClient.addOption( Bytes.QUERYOPTION_SLAVEOK );
		// mongoClient.setWriteConcern(WriteConcern.JOURNAL_SAFE);
		//// ReadPreference.secondaryPreferred();
		// mongoClient.setReadPreference(ReadPreference.nearest());
		//// mongoClient.setReadPreference(ReadPreference.primaryPreferred());
		// return mongoClient;
		// } catch (Exception e) {
		// throw new java.lang.RuntimeException(e);
		// }
		return this.mongoclient;
	}

	private List<ServerAddress> parserAddress() throws NumberFormatException, UnknownHostException {
		if (StringUtil.isEmpty(serverAddresses))
			return null;

		serverAddresses = serverAddresses.trim();
		List<ServerAddress> trueaddresses = new ArrayList<ServerAddress>();
		if (mode != null && mode.equals("simple")) {
			String info[] = serverAddresses.split(":");
			ServerAddress ad = new ServerAddress(info[0].trim(), Integer.parseInt(info[1].trim()));
			trueaddresses.add(ad);
			return trueaddresses;
		}

		String[] addresses = this.serverAddresses.split("\n");
		for (String address : addresses) {
			address = address.trim();
			String info[] = address.split(":");
			ServerAddress ad = new ServerAddress(info[0].trim(), Integer.parseInt(info[1].trim()));
			trueaddresses.add(ad);
		}
		return trueaddresses;
	}

	private int[] parserOption() throws NumberFormatException, UnknownHostException {
		if (StringUtil.isEmpty(this.option))
			return null;
		option = option.trim();
		String[] options = this.option.split("\r\n");
		int[] ret = new int[options.length];
		int i = 0;
		for (String op : options) {
			op = op.trim();
			ret[i] = _getOption(op);
			i++;
		}
		return ret;
	}

	private int _getOption(String op) {
		if (op.equals("QUERYOPTION_TAILABLE"))
			return Bytes.QUERYOPTION_TAILABLE;
		else if (op.equals("QUERYOPTION_SLAVEOK"))
			return Bytes.QUERYOPTION_SLAVEOK;
		else if (op.equals("QUERYOPTION_OPLOGREPLAY"))
			return Bytes.QUERYOPTION_OPLOGREPLAY;
		else if (op.equals("QUERYOPTION_NOTIMEOUT"))
			return Bytes.QUERYOPTION_NOTIMEOUT;

		else if (op.equals("QUERYOPTION_AWAITDATA"))
			return Bytes.QUERYOPTION_AWAITDATA;

		else if (op.equals("QUERYOPTION_EXHAUST"))
			return Bytes.QUERYOPTION_EXHAUST;

		else if (op.equals("QUERYOPTION_PARTIAL"))
			return Bytes.QUERYOPTION_PARTIAL;

		else if (op.equals("RESULTFLAG_CURSORNOTFOUND"))
			return Bytes.RESULTFLAG_CURSORNOTFOUND;
		else if (op.equals("RESULTFLAG_ERRSET"))
			return Bytes.RESULTFLAG_ERRSET;

		else if (op.equals("RESULTFLAG_SHARDCONFIGSTALE"))
			return Bytes.RESULTFLAG_SHARDCONFIGSTALE;
		else if (op.equals("RESULTFLAG_AWAITCAPABLE"))
			return Bytes.RESULTFLAG_AWAITCAPABLE;
		throw new RuntimeException("未知的option:" + op);

	}

	public static void main(String[] args) {
		String aa = "REPLICA_ACKNOWLEDGED(10)";
		int idx = aa.indexOf("(");
		String n = aa.substring(idx + 1, aa.length() - 1);
		System.out.println(n);
	}

	private WriteConcern _getWriteConcern() {
		if (StringUtil.isEmpty(this.writeConcern))
			return null;
		writeConcern = writeConcern.trim();
		if (this.writeConcern.equals("NONE"))
			return WriteConcern.UNACKNOWLEDGED;
		else if (this.writeConcern.equals("NORMAL"))
			return WriteConcern.NORMAL;
		else if (this.writeConcern.equals("SAFE"))
			return WriteConcern.SAFE;
		else if (this.writeConcern.equals("MAJORITY"))
			return WriteConcern.MAJORITY;
		else if (this.writeConcern.equals("FSYNC_SAFE"))
			return WriteConcern.FSYNC_SAFE;
		else if (this.writeConcern.equals("JOURNAL_SAFE"))
			return WriteConcern.JOURNAL_SAFE;
		else if (this.writeConcern.equals("REPLICAS_SAFE"))
			return WriteConcern.REPLICAS_SAFE;
		else if (this.writeConcern.startsWith("REPLICA_ACKNOWLEDGED")) {
			int idx = writeConcern.indexOf("(");
			if (idx < 0) {
				return WriteConcern.REPLICA_ACKNOWLEDGED;
			} else {
				String n = this.writeConcern.substring(idx + 1, writeConcern.length() - 1);
				try {
					if (n.indexOf(",") < 0) {
						int N = Integer.parseInt(n);
						return new WriteConcern(N);
					} else {
						String[] p = n.split(",");
						n = p[0];
						String _wtimeout = p[1];
						int N = Integer.parseInt(n);
						int wtimeout = Integer.parseInt(_wtimeout);
						return new WriteConcern(N, wtimeout, false);
					}
				} catch (NumberFormatException e) {
					return WriteConcern.REPLICA_ACKNOWLEDGED;
				}
			}
		} else if (this.writeConcern.equals("ACKNOWLEDGED"))
			return WriteConcern.ACKNOWLEDGED;
		else if (this.writeConcern.equals("UNACKNOWLEDGED"))
			return WriteConcern.UNACKNOWLEDGED;
		else if (this.writeConcern.equals("FSYNCED"))
			return WriteConcern.FSYNCED;
		else if (this.writeConcern.equals("JOURNALED"))
			return WriteConcern.JOURNALED;
		else if (this.writeConcern.equals("ERRORS_IGNORED"))
			return WriteConcern.UNACKNOWLEDGED;

		throw new RuntimeException("未知的WriteConcern:" + writeConcern);
	}

	private ReadPreference _getReadPreference() {
		if (StringUtil.isEmpty(this.readPreference))
			return null;
		if (readPreference.equals("PRIMARY"))
			return ReadPreference.primary();
		else if (readPreference.equals("SECONDARY"))
			return ReadPreference.secondary();
		else if (readPreference.equals("SECONDARY_PREFERRED"))
			return ReadPreference.secondaryPreferred();
		else if (readPreference.equals("PRIMARY_PREFERRED"))
			return ReadPreference.primaryPreferred();
		else if (readPreference.equals("NEAREST"))
			return ReadPreference.nearest();
		throw new RuntimeException("未知的ReadPreference:" + readPreference);
	}

	private void buildCredentials() {
		if (this.credentials != null && this.credentials.size() > 0) {
			this.mongoCredentials = new ArrayList<MongoCredential>();
			for (ClientMongoCredential clientMongoCredential : this.credentials) {
				if (StringUtil.isEmpty(clientMongoCredential.getMechanism())
						|| clientMongoCredential.getMechanism().equals(MongoCredential.MONGODB_CR_MECHANISM)) {
					mongoCredentials.add(MongoCredential.createMongoCRCredential(clientMongoCredential.getUserName(),
							clientMongoCredential.getDatabase(), clientMongoCredential.getPassword().toCharArray()));
				} else if (clientMongoCredential.getMechanism().equals(MongoCredential.PLAIN_MECHANISM)) {
					mongoCredentials.add(MongoCredential.createPlainCredential(clientMongoCredential.getUserName(),
							clientMongoCredential.getDatabase(), clientMongoCredential.getPassword().toCharArray()));
				} else if (clientMongoCredential.getMechanism().equals(MongoCredential.MONGODB_X509_MECHANISM)) {
					mongoCredentials
							.add(MongoCredential.createMongoX509Credential(clientMongoCredential.getUserName()));
				} else if (clientMongoCredential.getMechanism().equals(MongoCredential.GSSAPI_MECHANISM)) {
					mongoCredentials.add(MongoCredential.createGSSAPICredential(clientMongoCredential.getUserName()));
				}
			}
		}
	}

	public void init() {
		try {
			buildCredentials();
			if (mode != null && mode.equals("simple")) {
				this.initsimple();
			} else {

				// options.autoConnectRetry = autoConnectRetry;
				// options.connectionsPerHost = connectionsPerHost;
				// options.maxWaitTime = maxWaitTime;
				// options.socketTimeout = socketTimeout;
				// options.connectTimeout = connectTimeout;
				// options.threadsAllowedToBlockForConnectionMultiplier =
				// threadsAllowedToBlockForConnectionMultiplier;
				// options.socketKeepAlive=socketKeepAlive;
				Builder builder = MongoClientOptions.builder();
				// builder.autoConnectRetry( autoConnectRetry);
				_autoConnectRetry(builder, autoConnectRetry);
				builder.connectionsPerHost(connectionsPerHost);
				builder.maxWaitTime(maxWaitTime);
				builder.socketTimeout(socketTimeout);
				builder.connectTimeout(connectTimeout);
				builder.threadsAllowedToBlockForConnectionMultiplier(threadsAllowedToBlockForConnectionMultiplier);
				builder.socketKeepAlive(socketKeepAlive);
				MongoClientOptions options = builder.build();// new
																// MongoClientOptions();
				MongoClient mongoClient = null;
				List<ServerAddress> servers = parserAddress();
				if (mongoCredentials == null || mongoCredentials.size() == 0) {
					if (servers.size() > 1)
						mongoClient = new MongoClient(servers, options);
					else
						mongoClient = new MongoClient(servers.get(0), options);
				} else {
					if (servers.size() > 1)
						mongoClient = new MongoClient(servers, mongoCredentials, options);
					else
						mongoClient = new MongoClient(servers.get(0), mongoCredentials, options);
				}
				int[] ops = parserOption();
				for (int i = 0; ops != null && i < ops.length; i++)
					mongoClient.addOption(ops[i]);
				WriteConcern wc = this._getWriteConcern();
				if (wc != null)
					mongoClient.setWriteConcern(wc);
				// ReadPreference.secondaryPreferred();
				// if(servers.size() > 1)
				{
					ReadPreference rf = _getReadPreference();
					if (rf != null)
						mongoClient.setReadPreference(ReadPreference.nearest());
				}
				// mongoClient.setReadPreference(ReadPreference.primaryPreferred());
				this.mongoclient = mongoClient;
			}
		} catch (RuntimeException e) {
			log.error("初始化mongodb client failed.", e);
			throw e;

		} catch (Exception e) {
			log.error("初始化mongodb client failed.", e);
			throw new RuntimeException(e);

		}
	}

	private void _autoConnectRetry(Builder builder, boolean autoConnectRetry) {
		if (autoConnectRetryMethod != null)
			try {
				autoConnectRetryMethod.invoke(builder, autoConnectRetry);
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}

	public void initsimple() throws Exception {
		try {
			// MongoOptions options = new MongoOptions();
			// options.autoConnectRetry = autoConnectRetry;
			// options.connectionsPerHost = connectionsPerHost;
			// options.maxWaitTime = maxWaitTime;
			// options.socketTimeout = socketTimeout;
			// options.connectTimeout = connectTimeout;
			// options.threadsAllowedToBlockForConnectionMultiplier =
			// threadsAllowedToBlockForConnectionMultiplier;
			// options.socketKeepAlive=socketKeepAlive;
			// Mongo mongoClient = new Mongo(parserAddress().get(0),options);
			Builder builder = MongoClientOptions.builder();
			// builder.autoConnectRetry( autoConnectRetry);
			_autoConnectRetry(builder, autoConnectRetry);
			builder.connectionsPerHost(connectionsPerHost);
			builder.maxWaitTime(maxWaitTime);
			builder.socketTimeout(socketTimeout);
			builder.connectTimeout(connectTimeout);
			builder.threadsAllowedToBlockForConnectionMultiplier(threadsAllowedToBlockForConnectionMultiplier);
			builder.socketKeepAlive(socketKeepAlive);
			MongoClientOptions options = builder.build();// new
															// MongoClientOptions();
			MongoClient mongoClient = null;
			if (mongoCredentials == null || mongoCredentials.size() == 0) {
				mongoClient = new MongoClient(parserAddress().get(0), options);
			} else {
				mongoClient = new MongoClient(parserAddress().get(0), mongoCredentials, options);
			}

			int[] ops = parserOption();
			for (int i = 0; ops != null && i < ops.length; i++)
				mongoClient.addOption(ops[i]);
			WriteConcern wc = this._getWriteConcern();
			if (wc != null)
				mongoClient.setWriteConcern(wc);
			// ReadPreference.secondaryPreferred();

			// mongoClient.setReadPreference(ReadPreference.primaryPreferred());
			this.mongoclient = mongoClient;
		} catch (RuntimeException e) {
			throw e;

		} catch (Exception e) {
			throw e;

		}
	}

	public void close() {
		if (this.mongoclient != null)
			this.mongoclient.close();
	}

	public static WriteResult update(DBCollection collection, DBObject q, DBObject o) {
		try {
			WriteResult wr = collection.update(q, o);

			return wr;
		} catch (WriteConcernException e) {
			log.debug("update:", e);
			return null;
		}
	}

	public static WriteResult update(DBCollection collection, DBObject q, DBObject o, WriteConcern concern) {

		WriteResult wr = collection.update(q, o, false, false, concern);

		return wr;

	}

	public static DBObject findAndModify(DBCollection collection, DBObject query, DBObject update) {
		try {
			DBObject object = collection.findAndModify(query, update);
			return object;
		} catch (WriteConcernException e) {
			log.debug("findAndModify:", e);
			return null;
		}
	}

	public static DBObject findAndRemove(DBCollection collection, DBObject query) {
		try {
			DBObject object = collection.findAndRemove(query);
			return object;
		} catch (WriteConcernException e) {
			log.debug("findAndRemove:", e);
			return null;
		}
	}

	public static WriteResult insert(DBCollection collection, DBObject... arr) {

		try {
			return collection.insert(arr);
		} catch (WriteConcernException e) {
			log.debug("insert:", e);
			return null;
		}
	}
	
	public static WriteResult insert(DBCollection collection, List<? extends DBObject> arr) {

		try {
			return collection.insert(arr);
		} catch (WriteConcernException e) {
			log.debug("insert:", e);
			return null;
		}
	}

	public static WriteResult insert(WriteConcern concern, DBCollection collection, DBObject... arr) {

		return collection.insert(arr, concern);

	}
	
	public static WriteResult insert(WriteConcern concern, DBCollection collection, List<? extends DBObject> arr) {

		return collection.insert(arr, concern);

	}

	public static WriteResult remove(DBCollection collection, DBObject o) {
		try {
			return collection.remove(o);
		} catch (WriteConcernException e) {
			log.debug("remove:", e);
			return null;
		}
	}

	public static WriteResult remove(DBCollection collection, DBObject o, WriteConcern concern) {
		return collection.remove(o, concern);
	}

	public DB getDB(String dbname) {
		return this.mongoclient.getDB(dbname);
	}
	
	public  DBCollection getDBCollection(String dbname,String table)
	{
		DB db = this.getDB(dbname);
		return db.getCollection(table);
		
	}

}
