package com.taobao.tddl.repo.bdb.spi;

import java.io.File;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.Durability;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.rep.ReplicaWriteException;
import com.taobao.tddl.common.exception.NotSupportException;
import com.taobao.tddl.common.exception.TddlException;
import com.taobao.tddl.common.exception.TddlNestableRuntimeException;
import com.taobao.tddl.common.model.Group;
import com.taobao.tddl.common.model.lifecycle.AbstractLifecycle;
import com.taobao.tddl.common.properties.ConnectionProperties;
import com.taobao.tddl.common.utils.GeneralUtil;
import com.taobao.tddl.executor.spi.ICommandHandlerFactory;
import com.taobao.tddl.executor.spi.ICursorFactory;
import com.taobao.tddl.executor.spi.IGroupExecutor;
import com.taobao.tddl.executor.spi.IRepository;
import com.taobao.tddl.executor.spi.ITHLog;
import com.taobao.tddl.executor.spi.ITable;
import com.taobao.tddl.executor.spi.ITempTable;
import com.taobao.tddl.optimizer.config.table.TableMeta;

/**
 * @author jianxing <[email protected]>
 */
public class JE_Repository extends AbstractLifecycle implements IRepository {

    protected final AtomicReference<ITHLog> historyLog = new AtomicReference<ITHLog>();
    protected ICommandHandlerFactory        cef        = null;
    protected BDBConfig                     config;
    protected Environment                   env;
    protected Map<String, ITable>           tables     = new ConcurrentHashMap<String, ITable>();
    protected ICursorFactory                cursorFactoryBDBImp;
    protected Environment                   env_tmp;
    protected Durability                    durability;
    protected Random                        r          = new Random();
    private final Map                       cp;

    public JE_Repository(Map cp){

        this.cp = cp;
    }

    public void commonConfig(EnvironmentConfig envConfig, BDBConfig config) {
        System.setProperty("JEMonitor", "true");
        envConfig.setConfigParam(EnvironmentConfig.NODE_MAX_ENTRIES, "256");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_EVICT_BYTES, (1024 * 1024 * 2) + "");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_NODES_PER_SCAN, "10");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_LRU_ONLY, "false");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_FORCED_YIELD, "true");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_CORE_THREADS, Runtime.getRuntime().availableProcessors()
                                                                         + "");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_MAX_THREADS, Runtime.getRuntime().availableProcessors() + "");
        envConfig.setConfigParam(EnvironmentConfig.CHECKPOINTER_BYTES_INTERVAL, 1024 * 1024 * 200 + "");

        envConfig.setConfigParam(EnvironmentConfig.CLEANER_LOOK_AHEAD_CACHE_SIZE, 1024 * 8 + "");
        envConfig.setConfigParam(EnvironmentConfig.CLEANER_READ_SIZE, 1024 * 1024 + "");
        envConfig.setConfigParam(EnvironmentConfig.CLEANER_MAX_BATCH_FILES, 3 + "");
        envConfig.setConfigParam(EnvironmentConfig.LOG_FILE_MAX, 1024 * 1024 * 200 + "");
        envConfig.setConfigParam(EnvironmentConfig.LOG_FILE_CACHE_SIZE, "1024");
        envConfig.setConfigParam(EnvironmentConfig.LOG_USE_WRITE_QUEUE, "true");
        envConfig.setConfigParam(EnvironmentConfig.LOG_WRITE_QUEUE_SIZE, 1024 * 1024 * 2 + "");
        // envConfig.setConfigParam(EnvironmentConfig.HALT_ON_COMMIT_AFTER_CHECKSUMEXCEPTION,
        // "true");
        envConfig.setConfigParam(EnvironmentConfig.LOG_ITERATOR_READ_SIZE, 1024 * 8 + "");
        envConfig.setConfigParam(EnvironmentConfig.LOCK_TIMEOUT, 2000 + "\tMILLISECONDS");
        envConfig.setConfigParam(EnvironmentConfig.ENV_RECOVERY_FORCE_CHECKPOINT, "true");

        envConfig.setConfigParam(EnvironmentConfig.CLEANER_MIN_UTILIZATION, (config.getCleaner_min_utilization()));
        envConfig.setConfigParam(EnvironmentConfig.CLEANER_LAZY_MIGRATION, config.getCleanerLazyMigration());
        envConfig.setConfigParam(EnvironmentConfig.CLEANER_THREADS, config.getCleanerThreadCount() + "");
        envConfig.setConfigParam(EnvironmentConfig.CLEANER_MAX_BATCH_FILES, config.getCleanerBatchFileCount() + "");
        envConfig.setConfigParam(EnvironmentConfig.ENV_RUN_CLEANER, config.getAutoClean() ? "true" : "false");

    }

    @Override
    public BDBConfig getRepoConfig() {
        return config;
    }

    @Override
    public boolean isWriteAble() {
        return true;
    }

    @Override
    public ICursorFactory getCursorFactory() {
        return cursorFactoryBDBImp;
    }

    @Override
    public void init() {

        // EnvironmentConfig envConfig = new EnvironmentConfig();
        // commonConfig(envConfig);
        // envConfig.setCachePercent(config.getCachePercent());
        // envConfig.setAllowCreate(true);
        // if (config.isTransactional()) {
        // envConfig.setCachePercent(config.getCachePercent());
        // envConfig.setTransactional(config.isTransactional());
        // envConfig.setTxnTimeout(config.getTxnTimeout(), TimeUnit.SECONDS);
        // this.durability = config.isCommitSync() ? Durability.COMMIT_SYNC :
        // Durability.COMMIT_NO_SYNC;
        // envConfig.setDurability(this.durability);
        // }
        // File repo_dir = new File(config.getRepoDir());
        // if (!repo_dir.exists()) {
        // repo_dir.mkdirs();
        // }
        // this.env = new Environment(repo_dir, envConfig);
        cef = new CommandHandlerFactoryBDBImpl();
        cursorFactoryBDBImp = new CursorFactoryBDBImp();

    }

    public void commonConfig(EnvironmentConfig envConfig) {
        System.setProperty("JEMonitor", "true");
        envConfig.setConfigParam(EnvironmentConfig.NODE_MAX_ENTRIES, "256");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_EVICT_BYTES, (1024 * 1024 * 2) + "");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_NODES_PER_SCAN, "10");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_LRU_ONLY, "false");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_FORCED_YIELD, "true");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_CORE_THREADS, Runtime.getRuntime().availableProcessors()
                                                                         + "");
        envConfig.setConfigParam(EnvironmentConfig.EVICTOR_MAX_THREADS, Runtime.getRuntime().availableProcessors() + "");
        envConfig.setConfigParam(EnvironmentConfig.CHECKPOINTER_BYTES_INTERVAL, 1024 * 1024 * 200 + "");
        envConfig.setConfigParam(EnvironmentConfig.CLEANER_LAZY_MIGRATION, "true");
        envConfig.setConfigParam(EnvironmentConfig.CLEANER_THREADS, Runtime.getRuntime().availableProcessors() + "");
        envConfig.setConfigParam(EnvironmentConfig.CLEANER_LOOK_AHEAD_CACHE_SIZE, 1024 * 8 + "");
        envConfig.setConfigParam(EnvironmentConfig.CLEANER_READ_SIZE, 1024 * 1024 + "");
        envConfig.setConfigParam(EnvironmentConfig.LOG_FILE_MAX, 1024 * 1024 * 200 + "");
        envConfig.setConfigParam(EnvironmentConfig.LOG_FILE_CACHE_SIZE, "1024");
        envConfig.setConfigParam(EnvironmentConfig.LOG_USE_WRITE_QUEUE, "true");
        envConfig.setConfigParam(EnvironmentConfig.LOG_WRITE_QUEUE_SIZE, 1024 * 1024 * 2 + "");
        // envConfig.setConfigParam(EnvironmentConfig.HALT_ON_COMMIT_AFTER_CHECKSUMEXCEPTION,
        // "true");
        envConfig.setConfigParam(EnvironmentConfig.LOG_ITERATOR_READ_SIZE, 1024 * 8 + "");
        envConfig.setConfigParam(EnvironmentConfig.LOCK_TIMEOUT, 2000 + "\tMILLISECONDS");
        envConfig.setConfigParam(EnvironmentConfig.ENV_RECOVERY_FORCE_CHECKPOINT, "true");
    }

    @Override
    public ITable getTable(TableMeta table_schema, String groupNode, String actualTableName) throws TddlException {
        ITable table = tables.get(table_schema.getTableName());
        if (table == null) {
            synchronized (this) {
                table = tables.get(table_schema.getTableName());
                if (table == null) {
                    try {
                        table = initTable(table_schema);
                    } catch (ReplicaWriteException ex) {
                        throw new TddlNestableRuntimeException(ex);
                    }
                    if (!table_schema.isTmp()) {
                        tables.put(table_schema.getTableName(), table);
                    }
                }

            }
        }
        return table;
    }

    public JE_Table initTable(TableMeta table_schema) throws TddlException {
        return new JE_Table(table_schema, this);
    }

    public Database getDatabase(String name, boolean isTmp, boolean isSortedDuplicates) {
        DatabaseConfig dbConfig = new DatabaseConfig();
        dbConfig.setAllowCreate(true);
        Environment _env = env;
        if (isTmp) {
            dbConfig.setTemporary(true);
            dbConfig.setSortedDuplicates(isSortedDuplicates);
            _env = getTmpEnv();
        } else {
            if (!config.isTransactional()) {
                dbConfig.setDeferredWrite(config.isCommitSync());
            } else {
                dbConfig.setTransactional(true);
            }
        }

        Database database = buildPrimaryIndex(dbConfig, _env, name);
        return database;
    }

    public Environment getEnv() {
        return env;
    }

    @Override
    public void doDestroy() throws TddlException {
        for (Entry<String, ITable> t : tables.entrySet()) {
            t.getValue().close();
        }
        env.close();
        if (env_tmp != null) {
            env_tmp.close();
        }
    }

    public synchronized Environment getTmpEnv() {
        if (env_tmp != null) {
            return env_tmp;
        } else {
            EnvironmentConfig envConfig = new EnvironmentConfig();

            long cachePercent = GeneralUtil.getExtraCmdLong(cp, ConnectionProperties.TEMP_TABLE_CACHE_PERCENT, 20);

            envConfig.setCachePercent((int) cachePercent);
            envConfig.setAllowCreate(true);
            envConfig.setConfigParam(EnvironmentConfig.LOG_FILE_MAX, 1000000 + "");
            String path = null;

            path = GeneralUtil.getExtraCmdString(cp, ConnectionProperties.TEMP_TABLE_DIR);

            if (path == null) {
                path = System.getProperty("user.dir") + "/bdbtmp/" + System.currentTimeMillis() + r.nextInt()
                       + "requestID." + genRequestID() + "/";
            }

            File repo_dir = new File(path);
            // repo_dir = new File(System.getProperty("user.dir") + "/bdbtmp/");
            if (!repo_dir.exists()) {
                repo_dir.mkdirs();
            }
            env_tmp = new Environment(repo_dir, envConfig);
            return env_tmp;
        }
    }

    @Override
    public boolean isEnhanceExecutionModel(String groupKey) {
        return true;
    }

    private Database buildPrimaryIndex(DatabaseConfig dbConfig, Environment _env, String dbName) {
        Database database = _env.openDatabase(null, dbName, dbConfig);
        return database;
    }

    // @Override
    public void cleanTempTable() {
        if (env_tmp != null) {
            env_tmp.cleanLog();
        }
    }

    @Override
    public ICommandHandlerFactory getCommandExecutorFactory() {
        return cef;
    }

    public int cleanLog() {
        return env.cleanLog();
    }

    @Override
    public IGroupExecutor getGroupExecutor(Group group) {
        throw new NotSupportException();
    }

    public void setConfig(BDBConfig config) {
        this.config = config;

    }

    @Override
    public ITempTable getTempTable(TableMeta meta) throws TddlException {
        JE_TempTable table = new JE_TempTable(meta, this);
        table.tmpEv = this.getTmpEnv();
        return table;
    }

    static AtomicLong currentID = new AtomicLong(0L);

    public static long genRequestID() {
        return currentID.addAndGet(1L);
    }

}