package com.ucar.datalink.worker.api.util.dialect;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.ucar.datalink.biz.utils.DataSourceFactory;
import com.ucar.datalink.common.errors.DatalinkException;
import com.ucar.datalink.domain.media.MediaSourceInfo;
import com.ucar.datalink.domain.media.MediaSourceType;
import com.ucar.datalink.worker.api.util.dialect.mysql.MysqlDialect;
import com.ucar.datalink.worker.api.util.dialect.oracle.OracleDialect;
import com.ucar.datalink.worker.api.util.dialect.postgresql.PostgreSqlDialect;
import com.ucar.datalink.worker.api.util.dialect.sqlserver.SqlServerDialect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.lob.DefaultLobHandler;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;

/**
 * 方言工厂类
 * Created by lubiao on 2017/3/8.
 */
public class DbDialectFactory {

    private static final Logger logger = LoggerFactory.getLogger(DbDialectFactory.class);

    private static final LoadingCache<MediaSourceInfo, DbDialect> dialects;

    private static final DefaultLobHandler defaultLobHandler;

    static {
        defaultLobHandler = new DefaultLobHandler();
        defaultLobHandler.setStreamAsLob(true);

        dialects = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).softValues().build(new CacheLoader<MediaSourceInfo, DbDialect>() {
            @Override
            public DbDialect load(MediaSourceInfo mediaSourceInfo) throws Exception {
                DataSource dataSource = DataSourceFactory.getDataSource(mediaSourceInfo);
                final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
                return jdbcTemplate.execute(
                        new ConnectionCallback<DbDialect>() {
                            @Override
                            public DbDialect doInConnection(Connection connection) throws SQLException, DataAccessException {
                                DatabaseMetaData meta = connection.getMetaData();
                                String databaseName = meta.getDatabaseProductName();
                                int databaseMajorVersion = meta.getDatabaseMajorVersion();
                                int databaseMinorVersion = meta.getDatabaseMinorVersion();
                                DbDialect dialect = generate(jdbcTemplate, databaseName,
                                        databaseMajorVersion,
                                        databaseMinorVersion, mediaSourceInfo.getType());
                                if (dialect == null) {
                                    throw new UnsupportedOperationException("no dialect for" + databaseName);
                                }

                                if (logger.isInfoEnabled()) {
                                    logger.info(String.format("--- DATABASE: %s, SCHEMA: %s ---",
                                            databaseName,
                                            (dialect.getDefaultSchema() == null) ? dialect.getDefaultCatalog() : dialect.getDefaultSchema()));
                                }

                                return dialect;
                            }
                        });
            }
        });

    }

    private static DbDialect generate(JdbcTemplate jdbcTemplate, String databaseName, int databaseMajorVersion,
                                      int databaseMinorVersion, MediaSourceType sourceType) {
        DbDialect dialect;

        if (sourceType == MediaSourceType.ORACLE) {
            dialect = new OracleDialect(jdbcTemplate, defaultLobHandler, databaseName, databaseMajorVersion,
                    databaseMinorVersion);
        } else if (sourceType == MediaSourceType.MYSQL) {
            dialect = new MysqlDialect(jdbcTemplate, defaultLobHandler, databaseName, databaseMajorVersion,
                    databaseMinorVersion);
        } else if (sourceType == MediaSourceType.SDDL) {
            dialect = new MysqlDialect(jdbcTemplate, defaultLobHandler, databaseName, databaseMajorVersion,
                    databaseMinorVersion) {
                @Override
                public boolean isGenerateSqlWithSchema() {
                    return false;//sddl类型的数据源schema是通配符,所以针对sddl数据库生成sql是不能带schema
                }
            };
        } else if (sourceType == MediaSourceType.SQLSERVER) {
            dialect = new SqlServerDialect(jdbcTemplate, defaultLobHandler, databaseName, databaseMajorVersion,
                    databaseMinorVersion);
        } else if (sourceType == MediaSourceType.POSTGRESQL) {
            dialect = new PostgreSqlDialect(jdbcTemplate, defaultLobHandler, databaseName, databaseMajorVersion,
                    databaseMinorVersion);
        } else {
            throw new DatalinkException(
                    String.format("invalid Media Source Type %s,can not generate the dialect.", sourceType));
        }

        return dialect;
    }

    public static DbDialect getDbDialect(MediaSourceInfo mediaSourceInfo) {
        return dialects.getUnchecked(mediaSourceInfo);
    }

    public static void invalidate(MediaSourceInfo mediaSourceInfo) {
        DbDialect dbDialect = dialects.getIfPresent(mediaSourceInfo);
        if (dbDialect != null) {
            dialects.invalidate(mediaSourceInfo);
        }
    }
}