package hiro.yoshioka.sql.params;

import hiro.yoshioka.sdh.DatabaseType;
import hiro.yoshioka.sql.resource.DBRoot;
import hiro.yoshioka.sql.twitter.WolfAccessToken;
import hiro.yoshioka.util.StringUtil;

import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import com.mongodb.MongoOptions;

public class ConnectionProperties extends Properties implements Serializable,
		Cloneable {
	public static final String ADMIN_AUTHENTICATE = "admin";
	public static final String MONGO_OPTIONS = "MONGO_OPTIONS";
	public static final String HOST = "HOST";
	public static final String DRIVER_NAME = "DRIVER_NAME";
	public static final String DRIVER_FILE_PATH = "DRIVER_FILE_PATH";
	public static final String DISPLAY = "DISPLAY";
	public static final String PORT = "PORT";
	public static final String URL = "url";
	public static final String EXTRA_URLS = "extra_urls";
	public static final String INTERNAEL_LOGIN = "internal_logon";
	public static final String MAX_ROW_NUM = "MAX_ROW_NUM";
	public static final String DBMS = "DBMS";
	public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL";
	public static final String O_AUTH_AccessToken = "O_AUTH_AccessToken";
	public static final String O_AUTH_AccessTokenSecret = "O_AUTH_AccessTokenSecret";

	private DBRoot root;
	private boolean doSerializableDBRoot = true;
	private boolean captureWithDDL;
	private boolean captureWithViewInfo;
	private boolean captureWithTableInfo;
	private boolean captureWithProcedureInfo;
	private boolean captureWithSynonymInfo;
	private boolean captureWithSequenceInfo;
	private boolean captureWithTriggerInfo;
	private boolean captureWithColumnInfo;
	private boolean validateDBResource;
	private boolean autoCommit;
	private DBResourceCapturingFilter capturingFilter = DBResourceCapturingFilter.NOTHING;
	private static final String CATALOG_REGREX = "CATALOG_REGREX";
	private static final String TITLE_REGREX = "TITLE_REGREX";
	private static final String FILEPATH_REGREX = "FILEPATH_REGREX";
	private boolean captureResourceAfterTheConnectProcess = true;
	private WolfAccessToken accessToken;

	private transient boolean connected;
	private transient long connectTime;
	private int recursiveSearchDepth;
	private long created = System.currentTimeMillis();
	private transient int requestCount;
	private transient int endOfRequestCount;

	public ConnectionProperties() {
	}

	public boolean isCaptureWithColumnInfo() {
		return captureWithColumnInfo;
	}

	public void setCaptureWithColumnInfo(boolean captureWithColumnInfo) {
		this.captureWithColumnInfo = captureWithColumnInfo;
	}

	public boolean isCaptureResourceAfterTheConnectProcess() {
		return captureResourceAfterTheConnectProcess;
	}

	public void setCaptureResourceAfterTheConnectProcess(
			boolean captureResourceAfterTheConnectProcess) {
		this.captureResourceAfterTheConnectProcess = captureResourceAfterTheConnectProcess;
	}

	public boolean isValidateDBResource() {
		return validateDBResource;
	}

	public void setValidateDBResource(boolean validateDBResource) {
		this.validateDBResource = validateDBResource;
	}

	public boolean isCaptureWithProcedureInfo() {
		return captureWithProcedureInfo;
	}

	public void setCaptureWithProcedureInfo(boolean captureWithProcedureInfo) {
		this.captureWithProcedureInfo = captureWithProcedureInfo;
	}

	public boolean isCaptureWithSequenceInfo() {
		return captureWithSequenceInfo;
	}

	public void setCaptureWithSequenceInfo(boolean captureWithSequenceInfo) {
		this.captureWithSequenceInfo = captureWithSequenceInfo;
	}

	public boolean isCaptureWithTriggerInfo() {
		return captureWithTriggerInfo;
	}

	public void setCaptureWithTriggerInfo(boolean captureWithTriggerInfo) {
		this.captureWithTriggerInfo = captureWithTriggerInfo;
	}

	public boolean isCaptureWithSynonymInfo() {
		return captureWithSynonymInfo;
	}

	public void setCaptureWithSynonymInfo(boolean captureWithSynonymInfo) {
		this.captureWithSynonymInfo = captureWithSynonymInfo;
	}

	public boolean isCaptureWithViewInfo() {
		return captureWithViewInfo;
	}

	public void setCaptureWithViewInfo(boolean captureWithViewInfo) {
		this.captureWithViewInfo = captureWithViewInfo;
	}

	public boolean isCaptureWithTableInfo() {
		return captureWithTableInfo;
	}

	public void setCaptureWithTableInfo(boolean captureWithTableInfo) {
		this.captureWithTableInfo = captureWithTableInfo;
	}

	public void setCaptureWithDDL(boolean captureWithDDL) {
		this.captureWithDDL = captureWithDDL;
	}

	public boolean isCaptureWithDDL() {
		return captureWithDDL;
	}

	public boolean isSerializableDBRoot() {
		return doSerializableDBRoot;
	}

	public void setSerializableDBRoot(boolean serializableDBRoot) {
		this.doSerializableDBRoot = serializableDBRoot;
	}

	public void setAdminAuthenticate(String user, String pass) {
		put(ADMIN_AUTHENTICATE, new DBUserPass(ADMIN_AUTHENTICATE, user, pass));
	}

	public void setAuthenticate(DBUserPass dup) {
		put("Authenticate", dup);
	}

	public void setAuthenticate(String dbName, String user, String pass) {
		this.setAuthenticate(new DBUserPass(dbName, user, pass));
	}

	public DBUserPass getAuthenticate() {
		return (DBUserPass) get("Authenticate");
	}

	public void setHost(String host) {
		setProperty(HOST, host);
	}

	public DBUserPass getAdminAuthenticate() {
		return (DBUserPass) get(ADMIN_AUTHENTICATE);
	}

	public String getHost() {
		return StringUtil.nvl(getProperty(HOST));
	}

	public void setMongoOptions(MongoOptions options) {
		put(MONGO_OPTIONS, options);
	}

	public void setDisplayString(String displayString) {
		setProperty(DISPLAY, displayString);
	}

	public String getDisplayString() {
		return StringUtil.nvl(getProperty(DISPLAY));
	}

	public void setURLString(String url) {
		setProperty(URL, url);
	}

	public String getURLString() {
		return StringUtil.nvl(getProperty(URL));
	}

	public Set<String> getExtraUrlSet() {
		Set<String> retList = (Set<String>) get(EXTRA_URLS);
		return retList;
	}

	public void addExtraUrl(String urlString) {
		Set<String> retList = (Set<String>) get(EXTRA_URLS);
		if (retList == null) {
			retList = new LinkedHashSet<String>();
			put(EXTRA_URLS, retList);
		}
		retList.add(urlString);
	}

	public void setWritableDBMS_PutLine(boolean isWritable) {
		setProperty(DBMS, String.valueOf(isWritable));
	}

	public boolean isWritableDBMS_PutLine() {
		String str = getProperty(DBMS);
		if (StringUtil.isEmpty(str)) {
			return false;
		}
		if ("true".equalsIgnoreCase(str)) {
			return true;
		}
		return false;
	}

	public void setDriverName(String driverName) {
		setProperty(DRIVER_NAME, driverName);
	}

	public String getDriverName() {
		return StringUtil.nvl(getProperty(DRIVER_NAME));
	}

	public void setDriverFilePath(String driverFilePath) {
		setProperty(DRIVER_FILE_PATH, driverFilePath);
	}

	public String getDriverFilePath() {
		return StringUtil.nvl(getProperty(DRIVER_FILE_PATH));
	}

	public boolean isExistsDriverFile() {
		File f = new File(getDriverFilePath());
		if (f.exists() && f.isFile()) {
			return true;
		}
		return false;
	}

	public void setInternaelLogin(String internal_login) {
		setProperty(INTERNAEL_LOGIN, internal_login);
	}

	public String getInternaelLogin() {
		return StringUtil.nvl(getProperty(INTERNAEL_LOGIN));
	}

	public MongoOptions getMongoOptions() {
		return (MongoOptions) get(MONGO_OPTIONS);
	}

	public void setDBRoot(DBRoot root) {
		this.root = root;
	}

	public DBRoot getDBRootResource() {
		return root;
	}

	@Override
	public synchronized String toString() {
		DBUserPass auth = getAuthenticate();
		if (auth == null) {
			return String
					.format("%s Nothing Authentication URL[%s] DBroot Exists[%b] created[%tF] connected[%b] onlyUserInfo[%b] Interval[%d] Hash[%d]",
							getDisplayString(), getURLString(), (root != null),
							new Date(created), isConnected(),
							isOnlyConnectedUserInfo(), getKeepAliveInterval(),
							hashCode());
		} else {
			return String
					.format("%s USER[%s] URL[%s] DBroot Exists[%b] created[%tF] connected[%b] onlyUserInfo[%b] Interval[%d] Hash[%d]",
							getDisplayString(), auth.getUser(), getURLString(),
							(root != null), new Date(created), isConnected(),
							isOnlyConnectedUserInfo(), getKeepAliveInterval(),
							hashCode());
		}
	}

	public synchronized Object cloneWithOutCreated() {
		ConnectionProperties clone = (ConnectionProperties) super.clone();
		long t = System.currentTimeMillis();
		t = t + (t % 10);
		clone.created = t;
		return clone;
	}

	public synchronized Object cloneWithoutDBRoot() {
		ConnectionProperties clone = (ConnectionProperties) cloneWithOutCreated();
		clone.connectTime = 0;
		clone.connected = false;
		clone.root = null;
		clone.requestCount = 0;
		clone.endOfRequestCount = 0;
		return clone;
	}

	@Override
	public synchronized int hashCode() {
		return (int) created;
	}

	public boolean isConnected() {
		return connected;
	}

	public void setConnected(boolean connected) {
		this.connected = connected;
		if (connected) {
			connectTime = System.currentTimeMillis();
		}
	}

	public long getElapsedTime() {
		if (connected) {
			return System.currentTimeMillis() - connectTime;
		}
		return 0;
	}

	public void setRecursiveSearchDepth(int recursiveSearchDepth) {
		this.recursiveSearchDepth = recursiveSearchDepth;
	}

	public int getRecursiveSearchDepth() {
		return recursiveSearchDepth;
	}

	public boolean isOnlyConnectedUserInfo() {
		if (DBResourceCapturingFilter.MATCHES.equals(getCapturingFilter())) {
			Set<String> set = getCalalogRegrex();
			if (set.size() == 1) {
				for (String s : set) {
					return s.equalsIgnoreCase(getAuthenticate().user);
				}
			}
		}
		return false;
	}

	@Override
	public synchronized boolean equals(Object o) {
		if (o instanceof ConnectionProperties) {
			return this.created == ((ConnectionProperties) o).created;
		}
		return false;
	}

	public long getCreated() {
		return created;
	}

	private void readObject(ObjectInputStream in) throws IOException,
			ClassNotFoundException {
		in.defaultReadObject();
		connected = false;
	}

	private void writeObject(ObjectOutputStream stream) throws IOException {
		if (!doSerializableDBRoot) {
			root = null;
		}
		stream.defaultWriteObject();
	}

	public Properties getRelationalDBConnectionProperties() {
		Properties p = new Properties();
		DBUserPass dup = getAuthenticate();
		p.setProperty("user", dup.user);
		p.setProperty("password", dup.pass);
		p.setProperty("url", getURLString());
		if (DatabaseType.ORACLE.equals(getDatabaseType())) {
			if (!StringUtil.isEmpty(getInternaelLogin())) {
				p.setProperty(INTERNAEL_LOGIN, getInternaelLogin());
			}
		}
		return p;
	}

	public DBResourceCapturingFilter getCapturingFilter() {
		return capturingFilter;
	}

	public void setCapturingFilter(DBResourceCapturingFilter capturingFilter) {
		this.capturingFilter = capturingFilter;
		if (capturingFilter == DBResourceCapturingFilter.NOTHING) {
			remove(CATALOG_REGREX);
			remove(FILEPATH_REGREX);
			remove(TITLE_REGREX);
		}
	}

	public Set<String> getCalalogRegrex() {
		return getRegrexSet(CATALOG_REGREX);
	}

	public Set<String> getFilePathRegrex() {
		return getRegrexSet(FILEPATH_REGREX);
	}

	public Set<String> getTitleRegres() {
		return getRegrexSet(TITLE_REGREX);
	}

	public void addCalalogRegrex(String matchingString) {
		addRegrex(matchingString, CATALOG_REGREX);
	}

	public void addFilePathRegrex(String matchingString) {
		addRegrex(matchingString, FILEPATH_REGREX);
	}

	public void addTitleRegrex(String matchingString) {
		addRegrex(matchingString, TITLE_REGREX);
	}

	private Set<String> getRegrexSet(String key) {
		Set<String> ret = (Set<String>) get(key);
		if (ret == null) {
			return Collections.EMPTY_SET;
		}
		return ret;
	}

	private void addRegrex(String matchingString, String key) {
		if (StringUtil.isEmpty(matchingString)) {
			return;
		}
		Set<String> ret = (Set<String>) get(key);
		if (ret == null) {
			ret = new TreeSet<String>();
			put(key, ret);
		}
		ret.add(matchingString);
	}

	public void setMaxRowNum(int rownum) {
		put(MAX_ROW_NUM, rownum);
	}

	public int getMaxRowNum() {
		Integer max = (Integer) get(MAX_ROW_NUM);
		if (max == null) {
			if (DatabaseType.DOMINO.equals(getDatabaseType())) {
				return 100;
			}
			return 1000;
		}
		return max;
	}

	public void setKeepAliveInterval(int interval) {
		put(KEEP_ALIVE_INTERVAL, interval);
	}

	public int getKeepAliveInterval() {
		Integer interval = (Integer) get(KEEP_ALIVE_INTERVAL);
		if (interval == null) {
			return 0;
		}
		return interval;
	}

	public boolean isCapturingTarget(String categoriesOrSchemaName) {
		switch (getCapturingFilter()) {
		case NOTHING:
			return true;
		case IGNORE:
			if (!StringUtil.isEmpty(categoriesOrSchemaName)) {
				for (String ignore : getCalalogRegrex()) {
					if (categoriesOrSchemaName.matches(ignore)) {
						return false;
					}
				}
			}
			return true;
		case MATCHES:
			return isAnyMatch(categoriesOrSchemaName, getCalalogRegrex());
		}
		return true;
	}

	public boolean isCapturingTarget(String categories, String filePath,
			String title) {
		switch (getCapturingFilter()) {
		case NOTHING:
			return true;
		case IGNORE:
			if (!StringUtil.isEmpty(categories)) {
				for (String ignore : getCalalogRegrex()) {
					if (categories.matches(ignore)) {
						return false;
					}
				}
			}
			if (!StringUtil.isEmpty(filePath)) {
				for (String ignore : getFilePathRegrex()) {
					if (filePath.matches(ignore)) {
						return false;
					}
				}
			}
			if (!StringUtil.isEmpty(title)) {
				for (String ignore : getTitleRegres()) {
					if (title.matches(ignore)) {
						return false;
					}
				}
			}
			return true;
		case MATCHES:
			return isAnyMatch(categories, getCalalogRegrex())
					&& isAnyMatch(filePath, getFilePathRegrex())
					&& isAnyMatch(title, getTitleRegres());
		}
		return true;
	}

	private boolean isAnyMatch(String compareTarget, Set<String> matchSet) {
		if (StringUtil.isEmpty(compareTarget)) {
			return true;
		}
		if (matchSet.size() == 0) {
			return true;
		}
		for (String ok : matchSet) {
			if (compareTarget.matches(ok)) {
				return true;
			}
		}
		return false;
	}

	public DatabaseType getDatabaseType() {
		return DatabaseType.parse(getDriverName());
	}

	public void setOAuthAccessToken(String accessToken) {
		setProperty(O_AUTH_AccessToken, accessToken);
	}

	public void setOAuthAccessTokenSecret(String accessTokenSecret) {
		setProperty(O_AUTH_AccessTokenSecret, accessTokenSecret);
	}

	public String getOAuthAccessToken() {
		return StringUtil.nvl(getProperty(O_AUTH_AccessToken));
	}

	public String getOAuthAccessTokenSecret() {
		return StringUtil.nvl(getProperty(O_AUTH_AccessTokenSecret));
	}

	public void countUpRequest() {
		requestCount++;
	}

	public void countEndOfRequest() {
		endOfRequestCount++;
	}

	public int getRequestCount() {
		return requestCount;
	}

	public int getEndOfRequestCount() {
		return endOfRequestCount;
	}

	public boolean isAutoCommit() {
		return autoCommit;
	}

	public void setAutoCommit(boolean autoCommit) {
		this.autoCommit = autoCommit;
	}

	// ==========================================================================
	// FOR TWITTER
	// ==========================================================================
	public WolfAccessToken getAccessToken() {
		return accessToken;
	}

	public void setAccessToken(WolfAccessToken accessToken) {
		this.accessToken = accessToken;
	}

}
