/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of the Liferay Enterprise
 * Subscription License ("License"). You may not use this file except in
 * compliance with the License. You can obtain a copy of the License by
 * contacting Liferay, Inc. See the License for the specific language governing
 * permissions and limitations under the License, including but not limited to
 * distribution rights of the Software.
 *
 *
 *
 */

package com.liferay.portal.dao.db;

import com.liferay.portal.dao.orm.hibernate.DialectImpl;
import com.liferay.portal.dao.orm.hibernate.MariaDBDialect;
import com.liferay.portal.kernel.configuration.Filter;
import com.liferay.portal.kernel.dao.db.DB;
import com.liferay.portal.kernel.dao.db.DBFactory;
import com.liferay.portal.kernel.dao.db.DBManager;
import com.liferay.portal.kernel.dao.db.DBType;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.security.pacl.DoPrivileged;
import com.liferay.portal.kernel.util.InfrastructureUtil;
import com.liferay.portal.kernel.util.InstanceFactory;
import com.liferay.portal.kernel.util.PropsKeys;
import com.liferay.portal.kernel.util.PropsUtil;
import com.liferay.portal.kernel.util.ReflectionUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.util.PropsValues;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;

import java.util.EnumMap;
import java.util.LinkedHashSet;
import java.util.ServiceLoader;
import java.util.Set;

import javax.sql.DataSource;

import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.dialect.Oracle9Dialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.Sybase11Dialect;
import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.dialect.SybaseAnywhereDialect;
import org.hibernate.dialect.SybaseDialect;

/**
 * @author Alexander Chow
 * @author Brian Wing Shun Chan
 */
@DoPrivileged
@SuppressWarnings("deprecation")
public class DBManagerImpl implements DBManager {

	public DBManagerImpl() {
		ServiceLoader<DBFactory> serviceLoader = ServiceLoader.load(
			DBFactory.class, DBManagerImpl.class.getClassLoader());

		for (DBFactory dbFactory : serviceLoader) {
			_dbFactories.put(dbFactory.getDBType(), dbFactory);
		}
	}

	@Override
	public DB getDB() {
		if (_db == null) {
			try {
				if (_log.isInfoEnabled()) {
					_log.info("Using dialect " + PropsValues.HIBERNATE_DIALECT);
				}

				Dialect dialect = (Dialect)InstanceFactory.newInstance(
					PropsValues.HIBERNATE_DIALECT);

				setDB(
					getDB(
						getDBType(dialect),
						InfrastructureUtil.getDataSource()));
			}
			catch (Exception e) {
				_log.error(e, e);
			}
		}

		return _db;
	}

	@Override
	public DB getDB(DBType dbType, DataSource dataSource) {
		int dbMajorVersion = 0;
		int dbMinorVersion = 0;

		if (dataSource != null) {
			try (Connection connection = dataSource.getConnection()) {
				DatabaseMetaData databaseMetaData = connection.getMetaData();

				dbMajorVersion = databaseMetaData.getDatabaseMajorVersion();
				dbMinorVersion = databaseMetaData.getDatabaseMinorVersion();
			}
			catch (SQLException sqle) {
				return ReflectionUtil.throwException(sqle);
			}
		}

		DBFactory dbCreator = _dbFactories.get(dbType);

		if (dbCreator == null) {
			throw new IllegalArgumentException(
				"Unsupported database type " + dbType);
		}

		return dbCreator.create(dbMajorVersion, dbMinorVersion);
	}

	@Override
	public int getDBInMaxParameters() {
		if (_databaseInMaxParameters != 0) {
			return _databaseInMaxParameters;
		}

		DBType dbType = getDBType();

		_databaseInMaxParameters = GetterUtil.getInteger(
			PropsUtil.get(
				PropsKeys.DATABASE_IN_MAX_PARAMETERS,
				new Filter(dbType.getName())),
			Integer.MAX_VALUE);

		return _databaseInMaxParameters;
	}

	@Override
	public int getDBMaxParameters() {
		if (_databaseMaxParameters != 0) {
			return _databaseMaxParameters;
		}

		DBType dbType = getDBType();

		_databaseMaxParameters = GetterUtil.getInteger(
			PropsUtil.get(
				PropsKeys.DATABASE_MAX_PARAMETERS,
				new Filter(dbType.getName())),
			Integer.MAX_VALUE);

		return _databaseMaxParameters;
	}

	@Override
	public DBType getDBType() {
		if (_db == null) {
			getDB();
		}

		return _db.getDBType();
	}

	@Override
	public DBType getDBType(Object dialect) {
		if (dialect instanceof DialectImpl) {
			DialectImpl dialectImpl = (DialectImpl)dialect;

			dialect = dialectImpl.getWrappedDialect();
		}

		if (dialect instanceof DB2Dialect) {
			return DBType.DB2;
		}

		if (dialect instanceof HSQLDialect) {
			return DBType.HYPERSONIC;
		}

		if (dialect instanceof MariaDBDialect) {
			return DBType.MARIADB;
		}

		if (dialect instanceof MySQLDialect) {
			return DBType.MYSQL;
		}

		if (dialect instanceof Oracle8iDialect ||
			dialect instanceof Oracle9Dialect) {

			return DBType.ORACLE;
		}

		if (dialect instanceof PostgreSQLDialect) {
			return DBType.POSTGRESQL;
		}

		if (dialect instanceof SQLServerDialect) {
			return DBType.SQLSERVER;
		}

		if (dialect instanceof Sybase11Dialect ||
			dialect instanceof SybaseAnywhereDialect ||
			dialect instanceof SybaseASE15Dialect ||
			dialect instanceof SybaseDialect) {

			return DBType.SYBASE;
		}

		throw new IllegalArgumentException("Unknown dialect type " + dialect);
	}

	@Override
	public Set<DBType> getDBTypes() {
		return new LinkedHashSet<>(_dbFactories.keySet());
	}

	@Override
	public void setDB(DB db) {
		_db = db;

		if (_log.isDebugEnabled()) {
			Class<?> clazz = _db.getClass();

			_log.debug(
				StringBundler.concat(
					"Using DB implementation ", clazz.getName(), " for ",
					String.valueOf(db.getDBType())));
		}
	}

	private static final Log _log = LogFactoryUtil.getLog(DBManagerImpl.class);

	private int _databaseInMaxParameters;
	private int _databaseMaxParameters;
	private DB _db;
	private final EnumMap<DBType, DBFactory> _dbFactories = new EnumMap<>(
		DBType.class);

}