package cn.orm.util.metadata.util;

import cn.orm.util.metadata.database.DatabaseConfig;
import cn.orm.util.metadata.database.Dialect;
import cn.orm.util.metadata.database.IntrospectedColumn;
import cn.orm.util.metadata.database.IntrospectedTable;
import cn.orm.util.metadata.database.SimpleDataSource;
import cn.orm.util.metadata.database.introspector.DatabaseIntrospector;
import cn.orm.util.metadata.database.introspector.OracleIntrospector;
import cn.orm.util.metadata.database.introspector.PGIntrospector;
import cn.orm.util.metadata.database.introspector.SqlServerIntrospector;
import cn.util.core.IsKit;
import cn.util.core.LogKit;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据库操作
 */
public class DBMetadataUtil 
{
	private SimpleDataSource dataSource;
	private Connection connection;
	private LetterCase letterCase;
	private Dialect dialect;
	private DatabaseIntrospector introspector;
	private DatabaseMetaData databaseMetaData;
	private List<String> catalogs;
	private List<String> schemas;

	private java.sql.Statement stmt;
	private java.sql.ResultSet rs;
	public List<Map<String, Object>> query(String sqlquery) 
	{  
		//每个元素对应一个表的记录
		List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
		try {
			getConnection();
			if (connection != null && !connection.isClosed()) {
				stmt = connection.createStatement();
				rs = stmt.executeQuery(sqlquery);
				while (rs.next()) {
					Map<String, Object> tableMap = new HashMap<String, Object>();
					String tableName = rs.getString("TABLE_NAME");
					String columnName = rs.getString("COLUMN_NAME");
					String ref = rs.getString("REFERENCED_COLUMN_NAME");
					Boolean refKey = false;
					if(IsKit.isNotNullOrEmpty(ref)) {
						refKey = true;
					}
					tableMap.put(tableName +"&"+columnName,refKey);
					listMap.add(tableMap);
				}
			}
		}catch (SQLException e) {

		}finally {
			try {
				//先关闭结果集，再关闭语句，最后关闭连接。
				if(IsKit.isNotNull(rs)) {
					rs.close();
				} 
				if(IsKit.isNotNull(stmt)) {
					stmt.close();
				} 
				if(IsKit.isNotNull(connection)) {
					connection.close();
				} 
			}catch (Exception e) {
				LogKit.error(e);
			}finally {
				rs = null;
				stmt = null;
				connection = null;
			}
		}
		return listMap;
	}

	public DBMetadataUtil(SimpleDataSource dataSource) {
		this(dataSource, false, true);
	}

	public DBMetadataUtil(SimpleDataSource dataSource, boolean forceBigDecimals, boolean useCamelCase) 
	{
		if (dataSource == null) {
			throw new NullPointerException("Argument dataSource can't be null!");
		}
		this.dataSource = dataSource;
		this.dialect = dataSource.getDialect();
		try {
			initLetterCase();
			this.introspector = getDatabaseIntrospector(forceBigDecimals, useCamelCase);
			getConnection();
			this.catalogs = introspector.getCatalogs();
			this.schemas = introspector.getSchemas();
			closeConnection();
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	private void initLetterCase() {
		try {
			DatabaseMetaData databaseMetaData = getConnection().getMetaData();
			if (databaseMetaData.storesLowerCaseIdentifiers()) {
				letterCase = LetterCase.LOWER;
			} else if (databaseMetaData.storesUpperCaseIdentifiers()) {
				letterCase = LetterCase.UPPER;
			} else {
				letterCase = LetterCase.NORMAL;
			}
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	public String convertLetterByCase(String value) {
		if (value == null) {
			return null;
		}
		switch (letterCase) {
		case UPPER:
			return value.toUpperCase();
		case LOWER:
			return value.toLowerCase();
		default:
			return value;
		}
	}
	public DatabaseMetaData getDatabaseMetaData() throws SQLException {
		if (connection != null && !connection.isClosed()) {
			if (databaseMetaData != null) {
				return databaseMetaData;
			} else {
				databaseMetaData = connection.getMetaData();
				return databaseMetaData;
			}
		} else {
			databaseMetaData = getConnection().getMetaData();
			return databaseMetaData;
		}
	}

	public DatabaseIntrospector getIntrospector() {
		return introspector;
	}

	private DatabaseIntrospector getDatabaseIntrospector(boolean forceBigDecimals, boolean useCamelCase) {
		switch (dataSource.getDialect()) {
		case ORACLE:
			return new OracleIntrospector(this, forceBigDecimals, useCamelCase);
		case POSTGRESQL:
			return new PGIntrospector(this, forceBigDecimals, useCamelCase);
		case SQLSERVER:
			return new SqlServerIntrospector(this, forceBigDecimals, useCamelCase);
		case DB2:
		case HSQLDB:
		case MARIADB:
		case MYSQL:
		default:
			return new DatabaseIntrospector(this, forceBigDecimals, useCamelCase);
		}
	}

	public List<String> getCatalogs() throws SQLException {
		return catalogs;
	}

	public List<String> getSchemas() throws SQLException {
		return schemas;
	}

	public List<IntrospectedTable> introspectTables(DatabaseConfig config) throws SQLException 
	{
		getConnection();
		try {
			return introspector.introspectTables(config);
		} finally {
			closeConnection();
		}
	}

	public static void sortTables(List<IntrospectedTable> tables) {
		if (IsKit.isNotEmpty(tables)) {
			Collections.sort(tables, new Comparator<IntrospectedTable>() {
				public int compare(IntrospectedTable o1, IntrospectedTable o2) {
					return o1.getName().compareTo(o2.getName());
				}
			});
		}
	}

	public static void sortColumns(List<IntrospectedColumn> columns) {
		if (IsKit.isNotEmpty(columns)) {
			Collections.sort(columns, new Comparator<IntrospectedColumn>() {
				public int compare(IntrospectedColumn o1, IntrospectedColumn o2) {
					int result = o1.getTableName().compareTo(o2.getTableName());
					if (result == 0) {
						result = o1.getName().compareTo(o2.getName());
					}
					return result;
				}
			});
		}
	}

	private void closeConnection() {
		if (connection != null) {
			try {
				connection.close();
				connection = null;
				databaseMetaData = null;
			} catch (SQLException e) {
			}
		}
	}

	public boolean testConnection() {
		try {
			if (!getConnection().isClosed()) {
				return true;
			}
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			closeConnection();
		}
		return false;
	}

	public Connection getConnection() throws SQLException {
		if (connection != null && !connection.isClosed()) {
			return connection;
		}
		try {
			connection = dataSource.getConnection();
			return connection;
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	private enum LetterCase {
		UPPER, LOWER, NORMAL
	}

	public Dialect getDialect() {
		return dialect;
	}

	/**
	 * 获取默认的Config,任何情况下都会返回一个
	 *
	 * @return
	 * @throws SQLException
	 */
	public DatabaseConfig getDefaultConfig(String tableName) throws SQLException 
	{
		DatabaseConfig config = null;
		if (catalogs.size() == 1) {
			if (schemas.size() == 1) {
				config = new DatabaseConfig(catalogs.get(0), schemas.get(0));
			} else if (schemas.size() == 0) {
				config = new DatabaseConfig(catalogs.get(0), null);
			}
		} else if (catalogs.size() == 0) {
			if (schemas.size() == 1) {
				config = new DatabaseConfig(null, schemas.get(0));
			} else if (schemas.size() == 0) {
				config = new DatabaseConfig(null, null);
			}
		}
		String dbName = tableName;
		if (config == null) {
			if(IsKit.isNotNullOrEmpty(dataSource.getUrl())) {
				dbName = getDbNameByUrl(dataSource.getUrl());	
			}
			switch (getDialect()) {
			case DB2:
			case ORACLE:
				config = new DatabaseConfig(null, dataSource.getUser());
				break;
			case MYSQL:
				if (schemas.size() > 0) {
					break;
				}
				if (dbName != null) {
					for (String catalog : catalogs) {
						if (dbName.equalsIgnoreCase(catalog)) {
							config = new DatabaseConfig(catalog, null);
							break;
						}
					}
				}
				break;
			case SQLSERVER:
				String sqlserverCatalog = null;
				if (dbName != null) {
					for (String catalog : catalogs) {
						if (dbName.equalsIgnoreCase(catalog)) {
							sqlserverCatalog = catalog;
							break;
						}
					}
					if (sqlserverCatalog != null) {
						for (String schema : schemas) {
							if ("dbo".equalsIgnoreCase(schema)) {
								config = new DatabaseConfig(sqlserverCatalog, "dbo");
								break;
							}
						}
					}
				}
				break;
			case POSTGRESQL:
				String postgreCatalog = null;
				if (dbName != null) {
					for (String catalog : catalogs) {
						if (dbName.equalsIgnoreCase(catalog)) {
							postgreCatalog = catalog;
							break;
						}
					}
					if (postgreCatalog != null) {
						for (String schema : schemas) {
							if ("public".equalsIgnoreCase(schema)) {
								config = new DatabaseConfig(postgreCatalog, "public");
								break;
							}
						}
					}
				}
				break;
			default:
				break;
			}
		}
		if(config == null){
			config = new DatabaseConfig(null, null);
		}
		return config;
	}

	/**
	 * 通过jdbcUrl获取数据库名
	 *
	 * @param url
	 * @return
	 */
	private String getDbNameByUrl(String url) 
	{
		if (url.indexOf('/') > 0) {
			String dbName = url.substring(url.lastIndexOf('/') + 1);
			if (dbName.indexOf('?') > 0) {
				dbName = dbName.substring(0, dbName.indexOf('?'));
			}
			return dbName;
		}
		return null;
	}
}
