package cn.hutool.db.ds;

import cn.hutool.core.util.StrUtil;
import cn.hutool.db.ds.bee.BeeDSFactory;
import cn.hutool.db.ds.c3p0.C3p0DSFactory;
import cn.hutool.db.ds.dbcp.DbcpDSFactory;
import cn.hutool.db.ds.druid.DruidDSFactory;
import cn.hutool.db.ds.hikari.HikariDSFactory;
import cn.hutool.db.ds.pooled.PooledDSFactory;
import cn.hutool.db.ds.tomcat.TomcatDSFactory;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import cn.hutool.setting.Setting;

import javax.sql.DataSource;
import java.io.Closeable;
import java.io.Serializable;

/**
 * 抽象数据源工厂类<br>
 * 通过实现{@link #getDataSource(String)} 方法实现数据源的获取<br>
 * 如果{@link DataSource} 的实现是数据库连接池库，应该在getDataSource调用时创建数据源并缓存
 *
 * @author Looly
 *
 */
public abstract class DSFactory implements Closeable, Serializable{
	private static final long serialVersionUID = -8789780234095234765L;

	private static final Log log = LogFactory.get();

	/** 某些数据库需要的特殊配置项需要的配置项 */
	public static final String[] KEY_CONN_PROPS = {"remarks", "useInformationSchema"};

	/** 别名字段名：URL */
	public static final String[] KEY_ALIAS_URL = { "url", "jdbcUrl" };
	/** 别名字段名：驱动名 */
	public static final String[] KEY_ALIAS_DRIVER = { "driver", "driverClassName" };
	/** 别名字段名：用户名 */
	public static final String[] KEY_ALIAS_USER = { "user", "username" };
	/** 别名字段名：密码 */
	public static final String[] KEY_ALIAS_PASSWORD = { "pass", "password" };

	/** 数据源名 */
	protected final String dataSourceName;

	/**
	 * 构造
	 *
	 * @param dataSourceName 数据源名称
	 */
	public DSFactory(String dataSourceName) {
		this.dataSourceName = dataSourceName;
	}

	/**
	 * 获得默认数据源
	 *
	 * @return 数据源
	 */
	public DataSource getDataSource() {
		return getDataSource(StrUtil.EMPTY);
	}

	/**
	 * 获得分组对应数据源
	 *
	 * @param group 分组名
	 * @return 数据源
	 */
	public abstract DataSource getDataSource(String group);

	/**
	 * 关闭默认数据源（空组）
	 */
	@Override
	public void close() {
		close(StrUtil.EMPTY);
	}

	/**
	 * 关闭对应数据源
	 *
	 * @param group 分组
	 */
	public abstract void close(String group);

	/**
	 * 销毁工厂类，关闭所有数据源
	 */
	public abstract void destroy();

	// ------------------------------------------------------------------------- Static start
	/**
	 * 获得数据源<br>
	 * 使用默认配置文件的无分组配置
	 *
	 * @return 数据源
	 */
	public static DataSource get() {
		return get(null);
	}

	/**
	 * 获得数据源
	 *
	 * @param group 配置文件中对应的分组
	 * @return 数据源
	 */
	public static DataSource get(String group) {
		return GlobalDSFactory.get().getDataSource(group);
	}

	/**
	 * 设置全局的数据源工厂<br>
	 * 在项目中存在多个连接池库的情况下，我们希望使用低优先级的库时使用此方法自定义之<br>
	 * 重新定义全局的数据源工厂此方法可在以下两种情况下调用：
	 *
	 * <pre>
	 * 1. 在get方法调用前调用此方法来自定义全局的数据源工厂
	 * 2. 替换已存在的全局数据源工厂，当已存在时会自动关闭
	 * </pre>
	 *
	 * @param dsFactory 数据源工厂
	 * @return 自定义的数据源工厂
	 */
	public static DSFactory setCurrentDSFactory(DSFactory dsFactory) {
		return GlobalDSFactory.set(dsFactory);
	}

	/**
	 * 创建数据源实现工厂<br>
	 * 此方法通过“试错”方式查找引入项目的连接池库，按照优先级寻找，一旦寻找到则创建对应的数据源工厂<br>
	 * 连接池优先级：Hikari &gt; Druid &gt; Tomcat &gt; Dbcp &gt; C3p0 &gt; Hutool Pooled
	 *
	 * @param setting 数据库配置项
	 * @return 日志实现类
	 */
	public static DSFactory create(Setting setting) {
		final DSFactory dsFactory = doCreate(setting);
		log.debug("Use [{}] DataSource As Default", dsFactory.dataSourceName);
		return dsFactory;
	}

	/**
	 * 创建数据源实现工厂<br>
	 * 此方法通过“试错”方式查找引入项目的连接池库，按照优先级寻找，一旦寻找到则创建对应的数据源工厂<br>
	 * 连接池优先级：Hikari &gt; Druid &gt; Tomcat &gt; BeeCP &gt; Dbcp &gt; C3p0 &gt; Hutool Pooled
	 *
	 * @param setting 数据库配置项
	 * @return 日志实现类
	 * @since 4.1.3
	 */
	private static DSFactory doCreate(Setting setting) {
		try {
			return new HikariDSFactory(setting);
		} catch (NoClassDefFoundError | NoSuchMethodError e) {
			// ignore
		}
		try {
			return new DruidDSFactory(setting);
		} catch (NoClassDefFoundError | NoSuchMethodError e) {
			// ignore
		}
		try {
			return new TomcatDSFactory(setting);
		} catch (NoClassDefFoundError | NoSuchMethodError e) {
			//如果未引入包，此处会报org.apache.tomcat.jdbc.pool.PoolConfiguration未找到错误
			//因为org.apache.tomcat.jdbc.pool.DataSource实现了此接口，会首先检查接口的存在与否
			// ignore
		}
		try {
			return new BeeDSFactory(setting);
		} catch (NoClassDefFoundError | NoSuchMethodError e) {
			// ignore
		}
		try {
			return new DbcpDSFactory(setting);
		} catch (NoClassDefFoundError | NoSuchMethodError e) {
			// ignore
		}
		try {
			return new C3p0DSFactory(setting);
		} catch (NoClassDefFoundError | NoSuchMethodError e) {
			// ignore
		}
		return new PooledDSFactory(setting);
	}
	// ------------------------------------------------------------------------- Static end
}
