package ru.yandex.wmtools.common.service;

import java.util.List;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import ru.yandex.wmtools.common.data.DbBanConfig;
import ru.yandex.wmtools.common.data.partition.IPartition;
import ru.yandex.wmtools.common.util.IServiceJdbcTemplate;
import ru.yandex.wmtools.common.util.PartitionJdbcTemplate;
import ru.yandex.wmtools.common.util.ServiceJdbcTemplate;
import ru.yandex.wmtools.common.util.ServiceTransactionTemplate;

/**
 * This class has methods that wrap standard methods for working with database,
 * giving some additional features:
 * - replication database support
 * - safe switching to another database, when current is down
 * - read-only interface
 * and so on
 * User: baton
 * Date: 20.03.2007
 * Time: 10:09:24
 */
public class JdbcConnectionWrapperService extends AbstractDbService {
    private static final Logger logger = LoggerFactory.getLogger(JdbcConnectionWrapperService.class.getName());

    private Integer databaseCount = null;

    private ReadOnlyReplicationJdbcTemplate[] writeJdbcTemplate;
    private ServiceTransactionTemplate[] serviceTransactionTemplate;

    private List<DataSource> readDS;
    private List<DataSource> writeDS;

    private DbBanConfig dbBanConfig;

    /**
     * Spring init-method.
     */
    public final void init() throws Exception {
        databaseCount = writeDS.size();

        writeJdbcTemplate = new ReadOnlyReplicationJdbcTemplate[databaseCount];
        serviceTransactionTemplate = new ServiceTransactionTemplate[databaseCount];

        for (int i = 0; i < databaseCount; i++) {
            DataSource writeDataSource = writeDS.get(i);
            String databaseInfo = createDatabaseInfo(i, writeDataSource);
            DBBanner dbBanner = new DBBanner(dbBanConfig.getErrortToBan(), dbBanConfig.getBanAttemptCount(),
                    dbBanConfig.getBanTimeMilliseconds(), databaseInfo);
            writeJdbcTemplate[i] = new ReadOnlyReplicationJdbcTemplate(
                    createServiceJdbcTemplate(writeDataSource, i),
                    createServiceJdbcTemplate(readDS.get(i), i),
                    writeDataSource, databaseInfo, dbBanner);

            serviceTransactionTemplate[i] = new ServiceTransactionTemplate();
            serviceTransactionTemplate[i].setTransactionManager(new DataSourceTransactionManager(writeDataSource));
        }
    }

    @Override
    public IServiceJdbcTemplate getJdbcTemplate(final IPartition partition) {
        return new PartitionJdbcTemplate(writeJdbcTemplate, partition);
    }

    protected IServiceJdbcTemplate createServiceJdbcTemplate(DataSource dataSource, int index) {
        return new ServiceJdbcTemplate(dataSource, createDatabaseInfo(index, dataSource));
    }

    protected String createDatabaseInfo(int index, DataSource dataSource) {
        return "[index = " + index + "; url = " + dataSource.getUrl() + "]";
    }

    @Override
    public ServiceTransactionTemplate getServiceTransactionTemplate(final IPartition partition) {
        int databaseIndex = partition.getDatabaseIndex(databaseCount);
        return serviceTransactionTemplate[databaseIndex];
    }

    @Override
    public int getDatabaseCount() {
        return databaseCount;
    }

    /**
     * Выводит в лог число активных и неактивных соединений в пуле для базы данных с заданным индексом
     *
     * @param index индекс БД (0-8)
     */
    @Override
    public void logConnections(int index) {
        DataSource dataSource = writeDS.get(index);
        if (dataSource == null) {
            logger.error("Datasource not found");
            return;
        }
        StringBuilder sb = new StringBuilder();
        sb.append("DB Index: ").append(index);
        sb.append(" Active: ").append(dataSource.getNumActive());
        sb.append(" Idle: ").append(dataSource.getNumIdle());
        logger.debug(sb.toString());
    }

    @Required
    public void setReadDataSource(List<DataSource> dataSources) {
        this.readDS = dataSources;
    }

    @Required
    public void setDataSource(List<DataSource> dataSources) {
        this.writeDS = dataSources;
    }

    @Required
    public void setDbBanConfig(DbBanConfig dbBanConfig) {
        this.dbBanConfig = dbBanConfig;
    }
}
