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

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.LockTimeoutException;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.rep.ReplicaWriteException;
import com.taobao.tddl.common.exception.TddlException;
import com.taobao.tddl.common.exception.TddlNestableRuntimeException;
import com.taobao.tddl.executor.codec.CodecFactory;
import com.taobao.tddl.executor.codec.RecordCodec;
import com.taobao.tddl.executor.common.ExecutionContext;
import com.taobao.tddl.executor.common.TransactionConfig.Isolation;
import com.taobao.tddl.executor.cursor.ISchematicCursor;
import com.taobao.tddl.executor.cursor.SchematicCursor;
import com.taobao.tddl.executor.record.CloneableRecord;
import com.taobao.tddl.executor.spi.AbstractTable;
import com.taobao.tddl.executor.spi.IRepository;
import com.taobao.tddl.executor.spi.ITransaction;
import com.taobao.tddl.executor.utils.ExecUtils;
import com.taobao.tddl.optimizer.config.table.IndexMeta;
import com.taobao.tddl.optimizer.config.table.TableMeta;
import com.taobao.tddl.optimizer.core.plan.query.IQuery;

/**
 * @author jianxing <[email protected]>
 */
public class JE_Table extends AbstractTable {

    boolean               isTempTable = false;
    Map<String, Database> databases   = new HashMap();
    Environment           tmpEv       = null;

    public Map<String, Database> getDatabases() {
        return databases;
    }

    public void setDatabases(Map<String, Database> databases) {
        this.databases = databases;
    }

    Map<String/** index **/
    , KVCodec>    indexCodecMap;
    DatabaseEntry emptyValueEntry = new DatabaseEntry();

    {
        emptyValueEntry.setData(new byte[1]);
    }

    public JE_Table(TableMeta schema, IRepository repo){
        super(schema, repo);
        indexCodecMap = new HashMap<String, KVCodec>();

        KVCodec pkCodec = null;
        IndexMeta pkIndex = getSchema().getPrimaryIndex();

        if (pkCodec == null) {
            pkCodec = new KVCodec();
            pkCodec.setKey_codec(CodecFactory.getInstance(CodecFactory.FIXED_LENGTH)
                .getCodec((pkIndex.getKeyColumns())));
            if (pkIndex.getValueColumns() != null) {
                pkCodec.setValue_codec(CodecFactory.getInstance(CodecFactory.FIXED_LENGTH)
                    .getCodec((pkIndex.getValueColumns())));
            }
        }

        indexCodecMap.put(pkIndex.getName(), pkCodec);

        for (IndexMeta secondIndex : getSchema().getSecondaryIndexes()) {
            KVCodec secCodec = new KVCodec();
            secCodec.setKey_codec(CodecFactory.getInstance(CodecFactory.FIXED_LENGTH)
                .getCodec((secondIndex.getKeyColumns())));
            if (secondIndex.getValueColumns() != null) {
                secCodec.setValue_codec(CodecFactory.getInstance(CodecFactory.FIXED_LENGTH)
                    .getCodec((secondIndex.getValueColumns())));
            }
            indexCodecMap.put(secondIndex.getName(), secCodec);
        }

    }

    public Database getDatabase(String name) {
        Database db = databases.get(name);
        if (db == null) {
            synchronized (this) {
                db = databases.get(name);

                if (db == null) {
                    db = ((JE_Repository) this.repo).getDatabase(name, schema.isTmp(), schema.issortedDuplicates());
                    databases.put(name, db);
                }

            }
        }

        return db;
    }

    public ISchematicCursor getCursor(ITransaction txn, IndexMeta indexMeta, String isolation, String actualTableName)
                                                                                                                      throws TddlException {
        Database db = getDatabase(actualTableName);
        if (db == null) {
            throw new IllegalArgumentException("table don't contains indexName:" + actualTableName);
        }
        CursorConfig cc = CursorConfig.DEFAULT;
        LockMode lm = LockMode.DEFAULT;
        if (txn != null) {
            com.sleepycat.je.TransactionConfig _config = ((JE_Transaction) txn).config;
            if (_config.getReadUncommitted()) {
                cc = CursorConfig.READ_UNCOMMITTED;
                lm = LockMode.READ_UNCOMMITTED;
            } else if (_config.getReadCommitted()) {
                cc = CursorConfig.READ_COMMITTED;
                // lm = LockMode.READ_COMMITTED;
            }
        } else {
            if (Isolation.READ_COMMITTED.equals(isolation)) {
                cc = CursorConfig.READ_COMMITTED;
                // lm = LockMode.READ_COMMITTED;//not support
            } else if (Isolation.READ_UNCOMMITTED.equals(isolation)) {
                cc = CursorConfig.READ_UNCOMMITTED;
                lm = LockMode.READ_UNCOMMITTED;
            } else if (Isolation.REPEATABLE_READ.equals(isolation)) {
                // default
            } else if (Isolation.SERIALIZABLE.equals(isolation)) {
                // txn_config
            }
        }
        JE_Cursor je_cursor = new JE_Cursor(indexMeta, db.openCursor(txn == null ? null : ((JE_Transaction) txn).txn,
            cc), lm);
        if (txn != null) {
            ((JE_Transaction) txn).addCursor(je_cursor);
        }
        return new SchematicCursor(je_cursor, je_cursor.getiCursorMeta(), ExecUtils.getOrderBy(indexMeta));
    }

    /**
     * todo:触发更新二级索引
     * 
     * @param key
     * @param value
     * @throws TddlException
     */
    @Override
    public void put(ExecutionContext context, CloneableRecord key, CloneableRecord value, IndexMeta indexMeta,
                    String dbName) throws TddlException {
        DatabaseEntry keyEntry = new DatabaseEntry();
        DatabaseEntry valueEntry = new DatabaseEntry();
        keyEntry.setData(indexCodecMap.get(indexMeta.getName()).getKey_codec().encode(key));

        // 当临时表排序时候可能会用到临时表join,主键插入值为null
        // if (keyEntry.getData().length == 1 && !isTempTable) {
        // throw new RuntimeException("pk must not null.");
        // }

        if (value != null) {
            valueEntry.setData(indexCodecMap.get(indexMeta.getName()).getValue_codec().encode(value));
        } else {
            valueEntry = emptyValueEntry;
        }
        try {
            ITransaction transaction = context.getTransaction();
            com.sleepycat.je.Transaction txn = null;
            if (transaction != null && transaction instanceof JE_Transaction) {
                txn = ((JE_Transaction) transaction).txn;
            }
            OperationStatus operationStatus = getDatabase(dbName).put(txn, keyEntry, valueEntry);
            if (operationStatus.equals(OperationStatus.SUCCESS)) {
                return;
            }
        } catch (LockTimeoutException ex) {
            throw ex;
        } catch (ReplicaWriteException ex) {
            throw new TddlNestableRuntimeException(ex);
        }
    }

    // @Override
    // public long count() {
    // return databases.get(getSchema().getPrimaryIndex().getName()).count();
    // }
    //
    // @Override
    // public void sync() {
    // for (Entry<String, Database> entry : databases.entrySet()) {
    // entry.getValue().sync();
    // }
    // }

    @Override
    public void close() {
        for (Entry<String, Database> entry : databases.entrySet()) {
            // String dbName = entry.getValue().getDatabaseName();
            entry.getValue().close();

            // tmpEv.truncateDatabase(null, dbName, false);
            // if (this.tmpEv != null) {
            // tmpEv.removeDatabase(null, dbName);
            // }

        }

        // if (this.tmpEv != null) {
        // tmpEv.cleanLog();
        //
        // File home = tmpEv.getHome();
        // tmpEv.close();
        // deleteDir(home);
        //
        // }

    }

    @SuppressWarnings("unused")
    private static boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        // 目录此时为空,可以删除
        return dir.delete();
    }

    @Override
    public void delete(ExecutionContext context, CloneableRecord key, IndexMeta indexMeta, String dbName)
                                                                                                         throws TddlException {

        DatabaseEntry keyEntry = new DatabaseEntry();
        keyEntry.setData(indexCodecMap.get(indexMeta.getName()).getKey_codec().encode(key));
        try {
            getDatabase(dbName).delete(context.getTransaction() == null ? null : ((JE_Transaction) context.getTransaction()).txn,
                keyEntry);
        } catch (LockTimeoutException ex) {

            throw ex;
        } catch (ReplicaWriteException ex) {
            throw new TddlNestableRuntimeException(ex);
        }
    }

    @Override
    public CloneableRecord get(ExecutionContext context, CloneableRecord key, IndexMeta indexMeta, String dbName) {

        DatabaseEntry keyEntry = new DatabaseEntry();
        DatabaseEntry valueEntry = new DatabaseEntry();
        keyEntry.setData(indexCodecMap.get(indexMeta.getName()).getKey_codec().encode(key));
        OperationStatus status = getDatabase(dbName).get(context.getTransaction() == null ? null : ((JE_Transaction) context.getTransaction()).txn,
            keyEntry,
            valueEntry,
            LockMode.DEFAULT);
        if (OperationStatus.SUCCESS != status) {
            return null;
        }
        if (valueEntry.getSize() != 0) {
            return indexCodecMap.get(indexMeta.getName()).getValue_codec().decode(valueEntry.getData());
        } else {
            return null;
        }
    }

    public void setTempTable(boolean isTempTable) {
        this.isTempTable = isTempTable;
    }

    @Override
    public ISchematicCursor getCursor(ExecutionContext executionContext, IndexMeta meta, IQuery iQuery)
                                                                                                       throws TddlException {
        String actualTable = iQuery.getTableName();
        return getCursor(executionContext.getTransaction(), meta, executionContext.getIsolation(), actualTable);
    }

    @Override
    public ISchematicCursor getCursor(ExecutionContext executionContext, IndexMeta indexMeta, String actualTableName)
                                                                                                                     throws TddlException {
        Database db = getDatabase(actualTableName);
        if (db == null) {
            throw new IllegalArgumentException("table don't contains indexName:" + actualTableName);
        }
        ITransaction txn = executionContext.getTransaction();
        CursorConfig cc = CursorConfig.DEFAULT;
        LockMode lm = LockMode.DEFAULT;
        if (txn != null) {
            com.sleepycat.je.TransactionConfig _config = ((JE_Transaction) txn).config;
            if (_config.getReadUncommitted()) {
                cc = CursorConfig.READ_UNCOMMITTED;
                lm = LockMode.READ_UNCOMMITTED;
            } else if (_config.getReadCommitted()) {
                cc = CursorConfig.READ_COMMITTED;
                // lm = LockMode.READ_COMMITTED;
            }
        } else {

            cc = CursorConfig.READ_COMMITTED;

        }
        JE_Cursor je_cursor = new JE_Cursor(indexMeta, db.openCursor(txn == null ? null : ((JE_Transaction) txn).txn,
            cc), lm);
        if (txn != null) {
            ((JE_Transaction) txn).addCursor(je_cursor);
        }
        return new SchematicCursor(je_cursor, je_cursor.getiCursorMeta(), ExecUtils.getOrderBy(indexMeta));
    }
}

class KVCodec {

    RecordCodec key_codec;
    RecordCodec value_codec;

    public RecordCodec getKey_codec() {
        return key_codec;
    }

    public void setKey_codec(RecordCodec key_codec) {
        this.key_codec = key_codec;
    }

    public RecordCodec getValue_codec() {
        return value_codec;
    }

    public void setValue_codec(RecordCodec value_codec) {
        this.value_codec = value_codec;
    }
}