package org.bdware.sc;

import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.collections.StoredMap;
import com.sleepycat.je.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class Jedion {
    private static final Logger LOGGER = LogManager.getLogger(Jedion.class);
    //    static ExecutorService executor = Executors.newFixedThreadPool(1);
    private EnvironmentConfig envConfig = null; // 数据库环境配置对象
    private Environment myDbEnvironment = null; // 数据库环境对象
    private DatabaseConfig dbConfig = null; // 数据库配置对象
    // private Database myDatabaseConf = null;// 数据库对象-Conf
    // private Database myDatabaseUsers = null;// 数据库对象-Users
    private Database myDatabase; // 引用
    private StoredMap pendingDB = null;
    private String dbName = "yjs"; // 数据库名称

    public Jedion(String dbName) {
        this.dbName = dbName;
    }

    public void configEnvironment(File envDir) {
        envConfig = new EnvironmentConfig();
        envConfig.setAllowCreate(true); // 如果设置了true则表示当数据库环境不存在时候重新创建一个数据库环境，默认为false.
        envConfig.setTransactional(true); // 事务支持,如果为true，则表示当前环境支持事务处理，默认为false，不支持事务处理。
        envConfig.setReadOnly(false); // 是否以只读方式打开，默认为false.
        /*
         * envConfig.setCachePercent(50);//设置当前环境能够使用的RAM占整个JVM百分比
         * envConfig.setCacheSize(102400);//设置当前环境能使用的最大RAM,单位为byte
         */

        try {
            myDbEnvironment = new Environment(envDir, envConfig);
        } catch (DatabaseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    // 配置创建完环境对象后，可以用它创建数据库
    public void createDatabase() {
        dbConfig = new DatabaseConfig();
        dbConfig.setAllowCreate(true); // 如果设置了true则表示当数据库不存在时候重新创建一个数据库，默认为false.
        dbConfig.setTransactional(true); // 事务支持,如果为true，则表示当前数据库支持事务处理，默认为false，不支持事务处理。
        dbConfig.setReadOnly(false); // 是否以只读方式打开，默认为false.
        /*
         * dbConfig.setBtreeComparator();//设置用于Btree比较的比较器，通常是用来排序
         * dbConfig.setDuplicateComparator();//设置用来比较一个key有两个不同值的时候的大小比较器。
         * dbConfig.setSortedDuplicates(true);//设置一个key是否允许存储多个值，true代表允许，默认false.
         * dbConfig.setExclusiveCreate(true);//以独占的方式打开，也就是说同一个时间只能有一实例打开这个database。
         */
        try {
            myDatabase = myDbEnvironment.openDatabase(null, dbName, dbConfig);
            String CLASS_CATALOG = "java_class_catalog";
            Database catalogDatabase = myDbEnvironment.openDatabase(null, CLASS_CATALOG, dbConfig);
            // myDatabase = myDbEnvironment.openDatabase(null, dbName+"Users", dbConfig);
            // System.out.println("[Jedion]createDatabase " + myDatabase.getDatabaseName());
            StoredClassCatalog catalog = new StoredClassCatalog(catalogDatabase);
            EntryBinding keyBinding = new SerialBinding(catalog, byte[].class);
            pendingDB = new StoredMap(myDatabase, keyBinding, keyBinding, true);
        } catch (DatabaseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void writeToDatabase(final String key, final String value, final boolean isOverwrite) {
        try {
            writeToDatabaseInternal(key, value, isOverwrite);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //        executor.execute(() -> {
        //            try {
        //                writeToDatabaseInternal(key, value, isOverwrite);
        //            } catch (DatabaseException e) {
        //                e.printStackTrace();
        //            }
        //        });
    }

    // 像数据库中写数据
    private void writeToDatabaseInternal(String key, String value, boolean isOverwrite)
            throws DatabaseException {
        // JE的记录包含两部分，key键值和value数据值，这两个值都是通过DatabaseEntry对象封装起来的
        // 所以说如果要使用记录，则必须创建两个DatabaseEntry对象，一个是key，一个是value
        // DatabaseEntry内部使用的是bytes数组
        DatabaseEntry databaseKey = new DatabaseEntry(key.trim().getBytes(StandardCharsets.UTF_8));
        DatabaseEntry databaseValue =
                new DatabaseEntry(value.trim().getBytes(StandardCharsets.UTF_8));

        OperationStatus res = null; // 操作状态码
        Transaction txn; // 事务对象

        TransactionConfig txConfig = new TransactionConfig(); // 事务配置
        txConfig.setSerializableIsolation(true); // 设置串行化隔离级别

        txn = myDbEnvironment.beginTransaction(null, txConfig); // 开始事务

        if (isOverwrite) {
            // 添加一条记录。如数据库不支持一个key对应多个data或当前数据库中已经存在该key了，则使用此方法将使用新的值覆盖旧的值。
            res = myDatabase.put(txn, databaseKey, databaseValue);
        } else {
            // 不管数据库是否允许支持多重记录(一个key对应多个value),只要存在该key就不允许添加，并且返回perationStatus.KEYEXIST信息
            res = myDatabase.putNoOverwrite(txn, databaseKey, databaseValue);
        }

        txn.commit(); // 提交事务

        //			if (res == OperationStatus.SUCCESS)
        //				System.out.println("[Jedion] insert success");
        //			else if (res == OperationStatus.KEYEXIST)
        //				System.out.println("[Jedion] key exist");
        //			else
        //				System.out.println("[Jedion] insert fail");

    }

    // 遍历数据库中数据
    public ArrayList<String> getAllFromDatabase()
            throws DatabaseException {
        Cursor myCursor; // 游标
        ArrayList<String> resultList = new ArrayList<>();
        Transaction txn;

        txn = myDbEnvironment.beginTransaction(null, null);
        CursorConfig cc = new CursorConfig(); // 游标配置
        cc.setReadCommitted(true); // 设置隔离级别

        myCursor = myDatabase.openCursor(txn, cc);

        DatabaseEntry entryKey = new DatabaseEntry();
        DatabaseEntry entryValue = new DatabaseEntry();

        if (myCursor.getFirst(entryKey, entryValue, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
            String key = new String(entryKey.getData(), StandardCharsets.UTF_8);
            resultList.add(key);
            while (myCursor.getNext(entryKey, entryValue, LockMode.DEFAULT)
                    == OperationStatus.SUCCESS) {
                key = new String(entryKey.getData(), StandardCharsets.UTF_8);
                resultList.add(key);
            }
        }

        myCursor.close();

        txn.commit();
        return resultList;
    }

    public String readFromDatabaseOpted(String key) {
        return new String((byte[]) pendingDB.get(key.getBytes()));
    }

    // 从数据库读取相应键值的数据
    public String readFromDatabase(String key) throws DatabaseException {
        DatabaseEntry databaseKey = new DatabaseEntry(key.trim().getBytes(StandardCharsets.UTF_8));
        DatabaseEntry databaseValue = new DatabaseEntry();
        Transaction txn; // 事务对象

        TransactionConfig txConfig = new TransactionConfig(); // 事务配置
        txConfig.setSerializableIsolation(true); // 设置串行化隔离级别

        txn = myDbEnvironment.beginTransaction(null, txConfig); // 开始事务
        OperationStatus res = myDatabase.get(txn, databaseKey, databaseValue, LockMode.DEFAULT);

        txn.commit(); // 提交事务
        if (res == OperationStatus.SUCCESS) {
            byte[] retData = databaseValue.getData();
            return new String(retData, StandardCharsets.UTF_8);
        } else {
            return "";
        }
    }

    public void visitAllItem(JedionVisitor visitor) {
        Cursor myCursor = null;
        List<String> resultList = new ArrayList<>();
        Transaction txn = null;
        try {
            txn = myDbEnvironment.beginTransaction(null, null);
            CursorConfig cc = new CursorConfig();
            cc.setReadCommitted(true);
            myCursor = myDatabase.openCursor(txn, cc);
            DatabaseEntry foundKey = new DatabaseEntry();
            DatabaseEntry foundData = new DatabaseEntry();
            // 使用cursor.getPrev方法来遍历游标获取数据
            if (myCursor.getFirst(foundKey, foundData, LockMode.DEFAULT)
                    == OperationStatus.SUCCESS) {
                String theKey = new String(foundKey.getData(), StandardCharsets.UTF_8);
                String theData = new String(foundData.getData(), StandardCharsets.UTF_8);
                visitor.visit(theKey, theData);
                // System.out.println("Key | Data : " + theKey + " | " + theData + "");
                // System.out.println("Key : " + theKey + "\nvalue : " + theData);
                while (myCursor.getNext(foundKey, foundData, LockMode.DEFAULT)
                        == OperationStatus.SUCCESS) {
                    theKey = new String(foundKey.getData(), StandardCharsets.UTF_8);
                    theData = new String(foundData.getData(), StandardCharsets.UTF_8);
                    visitor.visit(theKey, theData);

                    // System.out.println("Key | Data : " + theKey + " | " + theData + "");
                    // System.out.println("Key : " + theKey + "\nvalue : " + theData);
                }
            }
            myCursor.close();
            txn.commit();

        } catch (Exception e) {
            try {
                if (null != txn) {
                    txn.abort();
                }
                if (myCursor != null) {
                    myCursor.close();
                }
            } catch (Exception e1) {
                e1.printStackTrace();
            }

        }
    }

    public List<String> getEveryItem() {
        Cursor myCursor = null;
        List<String> resultList = new ArrayList<>();
        Transaction txn = null;
        try {
            txn = myDbEnvironment.beginTransaction(null, null);
            CursorConfig cc = new CursorConfig();
            cc.setReadCommitted(true);
            myCursor = myDatabase.openCursor(txn, cc);
            DatabaseEntry foundKey = new DatabaseEntry();
            DatabaseEntry foundData = new DatabaseEntry();
            // 使用cursor.getPrev方法来遍历游标获取数据
            if (myCursor.getFirst(foundKey, foundData, LockMode.DEFAULT)
                    == OperationStatus.SUCCESS) {
                String theKey = new String(foundKey.getData(), StandardCharsets.UTF_8);
                String theData = new String(foundData.getData(), StandardCharsets.UTF_8);
                resultList.add(theKey);
                // System.out.println("Key | Data : " + theKey + " | " + theData + "");
                // System.out.println("Key : " + theKey + "\nvalue : " + theData);
                while (myCursor.getNext(foundKey, foundData, LockMode.DEFAULT)
                        == OperationStatus.SUCCESS) {
                    theKey = new String(foundKey.getData(), StandardCharsets.UTF_8);
                    theData = new String(foundData.getData(), StandardCharsets.UTF_8);
                    resultList.add(theKey);
                    // System.out.println("Key | Data : " + theKey + " | " + theData + "");
                    // System.out.println("Key : " + theKey + "\nvalue : " + theData);
                }
            }
            myCursor.close();
            txn.commit();
            return resultList;
        } catch (Exception e) {
            try {
                if (null != txn) {
                    txn.abort();
                }
                if (myCursor != null) {
                    myCursor.close();
                }
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            return null;
        }
    }

    public long getCount() {
        try {
            return myDatabase.count();
        } catch (DatabaseException e) {
            return 0;
        }
    }

    public List<KV> getEveryKeyValue() {
        LOGGER.debug("===========遍历数据库" + dbName + "中的所有数据==========");
        Cursor myCursor = null;
        List<KV> resultList = new ArrayList<>();
        Transaction txn = null;
        try {
            txn = myDbEnvironment.beginTransaction(null, null);
            CursorConfig cc = new CursorConfig();
            cc.setReadCommitted(true);
            myCursor = myDatabase.openCursor(txn, cc);
            DatabaseEntry foundKey = new DatabaseEntry();
            DatabaseEntry foundData = new DatabaseEntry();
            // 使用cursor.getPrev方法来遍历游标获取数据
            if (myCursor.getFirst(foundKey, foundData, LockMode.DEFAULT)
                    == OperationStatus.SUCCESS) {
                KV kv = new KV();
                kv.key = new String(foundKey.getData(), StandardCharsets.UTF_8);
                kv.value = new String(foundData.getData(), StandardCharsets.UTF_8);
                resultList.add(kv);
                // System.out.println("Key : " + theKey + "\nvalue : " + theData)
                while (myCursor.getNext(foundKey, foundData, LockMode.DEFAULT)
                        == OperationStatus.SUCCESS) {
                    kv = new KV();
                    kv.key = new String(foundKey.getData(), StandardCharsets.UTF_8);
                    kv.value = new String(foundData.getData(), StandardCharsets.UTF_8);
                    resultList.add(kv);
                }
            }
            myCursor.close();
            txn.commit();
            LOGGER.debug("getEveryKeyValue, size:" + resultList.size());
            return resultList;
        } catch (Exception e) {
            try {
                if (null != txn) {
                    txn.abort();
                }
                if (myCursor != null) {
                    myCursor.close();
                }
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            return null;
        }
    }

    // 删除数据库中一条数据
    public void deleteFromDatabase(String key) throws DatabaseException {
        Transaction txn;

        TransactionConfig txConfig = new TransactionConfig();
        txConfig.setSerializableIsolation(true);

        txn = myDbEnvironment.beginTransaction(null, txConfig);
        DatabaseEntry databaseKey;
        databaseKey = new DatabaseEntry(key.trim().getBytes(StandardCharsets.UTF_8));
        OperationStatus res = myDatabase.delete(txn, databaseKey);
        txn.commit();
        //		if (res == OperationStatus.SUCCESS)
        //			System.out.println("delete success");
        //		else if (res == OperationStatus.KEYEMPTY)
        //			System.out.println("no key");
        //		else
        //			System.out.println("delete fail");
    }

    // 一些其他方法
    public void otherMethod() throws DatabaseException {
        String databaseName = myDatabase.getDatabaseName(); // 数据库名字
        LOGGER.debug("databaseName :" + databaseName);

        Environment env = myDatabase.getEnvironment(); // 取得当前数据库的环境信息
        LOGGER.debug(env);

        // 取得当前环境下数据库名称列表
        LOGGER.debug(myDbEnvironment.getDatabaseNames());

        env.renameDatabase(null, databaseName, "newName"); // 给数据库改名
        env.removeDatabase(null, databaseName); // 删除当前环境数据库

        // 清空数据库中所有记录，并返回数量
        LOGGER.debug(env.truncateDatabase(null, databaseName, true));
    }

    // 关闭数据库
    public void closeDatabase() throws DatabaseException {
        if (myDatabase != null) myDatabase.close();

        if (myDbEnvironment != null) {
            myDbEnvironment.cleanLog();
            myDbEnvironment.close();
        }
    }

    public static abstract class JedionVisitor {
        public abstract void visit(String key, String value);
    }

    public static class KV {
        public String key;
        public String value;
    }
}
