/*
 * Copyright (c) 2011-2018, Meituan Dianping. All Rights Reserved.
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.dianping.zebra.single.pool;

import com.dianping.zebra.exception.ZebraConfigException;
import com.dianping.zebra.group.config.datasource.entity.DataSourceConfig;
import com.dianping.zebra.group.util.DataSourceState;
import com.dianping.zebra.log.Logger;
import com.dianping.zebra.log.LoggerFactory;
import com.dianping.zebra.single.jdbc.SingleDataSource;
import com.dianping.zebra.util.JdbcDriverClassHelper;
import com.dianping.zebra.util.StringUtils;
import org.apache.tomcat.jdbc.pool.PoolProperties;

import javax.sql.DataSource;
import java.sql.SQLException;

public class TomcatJdbcDataSourcePool extends AbstractDataSourcePool implements DataSourcePool {

	protected static final Logger LOGGER = LoggerFactory.getLogger(TomcatJdbcDataSourcePool.class);

	private org.apache.tomcat.jdbc.pool.DataSource pool = null;

	@Override
	public DataSource build(DataSourceConfig config, boolean withDefaultValue) {
		PoolProperties properties = new PoolProperties();

		properties.setUrl(config.getJdbcUrl());
		properties.setUsername(config.getUsername());
		properties.setPassword(config.getPassword());
		properties.setDriverClassName(JdbcDriverClassHelper.getDriverClassNameByJdbcUrl(config.getJdbcUrl()));

		if (withDefaultValue) {
			properties.setInitialSize(getIntProperty(config, "initialPoolSize", 5));
			properties.setMaxActive(getIntProperty(config, "maxPoolSize", 30));
			properties.setMinIdle(getIntProperty(config, "minPoolSize", 5));
			properties.setMaxIdle(getIntProperty(config, "maxPoolSize", 20));
			properties.setMaxWait(getIntProperty(config, "checkoutTimeout", 1000));
			properties.setValidationQuery(getStringProperty(config, "preferredTestQuery", "SELECT 1"));
			properties.setMinEvictableIdleTimeMillis(getIntProperty(config, "minEvictableIdleTimeMillis", 300000));// 5min
			properties.setTimeBetweenEvictionRunsMillis(getIntProperty(config, "timeBetweenEvictionRunsMillis", 30000)); // 30s
			properties.setNumTestsPerEvictionRun(getIntProperty(config, "numTestsPerEvictionRun", 6));
			properties.setValidationQueryTimeout(getIntProperty(config, "validationQueryTimeout", 0));
			properties.setValidationInterval(getIntProperty(config, "validationInterval", 30000));// 30s
			properties.setRemoveAbandonedTimeout(getIntProperty(config, "removeAbandonedTimeout", 300));
			if (StringUtils.isNotBlank(getStringProperty(config, "connectionInitSql", null))) {
				properties.setInitSQL(getStringProperty(config, "connectionInitSql", null));
			}

			properties.setTestWhileIdle(true);
			properties.setTestOnBorrow(false);
			properties.setTestOnReturn(false);
			properties.setRemoveAbandoned(true);
		} else {
			try {
				PropertiesInit<PoolProperties> propertiesInit = new PropertiesInit<PoolProperties>(properties);
				propertiesInit.initPoolProperties(config);
			} catch (Exception e) {
				throw new ZebraConfigException(
				      String.format("tomcat-jdbc dataSource [%s] created error : ", config.getId()), e);
			}
		}

		org.apache.tomcat.jdbc.pool.DataSource datasource = new org.apache.tomcat.jdbc.pool.DataSource();
		datasource.setPoolProperties(properties);

		this.pool = datasource;
		LOGGER.info(String.format("New dataSource [%s] created.", config.getId()));

		return this.pool;
	}

	@Override
	public void close(SingleDataSource singleDataSource, boolean forceClose) throws SQLException {
		String dsId = singleDataSource.getId();
		LOGGER.info(singleDataSource.getAndIncrementCloseAttempt() + " attempt to close datasource [" + dsId + "]");

		if (forceClose) {
			LOGGER.info("closing old datasource [" + dsId + "]");

			this.pool.close();

			singleDataSource.setState(DataSourceState.CLOSED);
			LOGGER.info("old datasource [" + dsId + "] closed");
		} else {
			if (this.pool.getActive() == 0 || singleDataSource.getCloseAttempt() >= MAX_CLOSE_ATTEMPT) {
				LOGGER.info("closing old datasource [" + dsId + "]");

				this.pool.close();

				LOGGER.info("old datasource [" + dsId + "] closed");
				singleDataSource.setState(DataSourceState.CLOSED);
			} else {
				throwException(dsId);
			}
		}
	}

	@Override
	public int getNumBusyConnection() {
		if (this.pool != null) {
			try {
				return this.pool.getActive();
			} catch (Exception e) {
			}
		}

		return 0;
	}

	@Override
	public int getNumConnections() {
		if (this.pool != null) {
			try {
				return this.pool.getPoolSize();
			} catch (Exception e) {
			}
		}

		return 0;
	}

	@Override
	public int getNumIdleConnection() {
		if (this.pool != null) {
			try {
				return this.pool.getIdle();
			} catch (Exception e) {
			}
		}

		return 0;
	}

	@Override
	public DataSource getInnerDataSourcePool() {
		return this.pool;
	}
}