package org.bdware.sc.db;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.sc.Jedion;
import org.rocksdb.Options;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import org.rocksdb.RocksIterator;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class KeyValueRocksDBUtil {
    private static final Logger LOGGER = LogManager.getLogger(KeyValueRocksDBUtil.class);
    public static KeyValueRocksDBUtil instance = null;
    private final Map<String, RocksDB> dbLink;
    public String dbPath;

    private KeyValueRocksDBUtil(String path) {
        dbLink = new ConcurrentHashMap<>();
        dbPath = path;
    }

    public static void setupNC() {
        if (null == instance) {
            instance = new KeyValueRocksDBUtil("./NodeCenterDB");
            LOGGER.info("init nc Rocksdb in ./NodeCenterDB");
        }
    }

    public static void setupCM() {
        if (null == instance) {
            instance = new KeyValueRocksDBUtil("./ContractManagerDB");
            LOGGER.info("init cm Rocksdb in ./ContractManagerDB");
        }
    }

    private synchronized RocksDB getDB(String dbName) {
        if (!dbLink.containsKey(dbName)) {
            setupDB(dbName);
        }
        return dbLink.get(dbName);
    }

    public boolean containsKey(String dbName, String key) {
        String res = getValue(dbName, key);
        return res != null && !res.isEmpty();
    }

    public List<String> getKeys(String dbName) {
        List<String> ret = new ArrayList<>();
        visitDB(
                dbName,
                new Jedion.JedionVisitor() {
                    @Override
                    public void visit(String key, String value) {
                        ret.add(key);
                    }
                });
        return ret;
    }

    public List<Jedion.KV> getKeyValues(String dbName) {
        List<Jedion.KV> ret = new ArrayList<>();
        visitDB(
                dbName,
                new Jedion.JedionVisitor() {
                    @Override
                    public void visit(String key, String value) {
                        Jedion.KV kv = new Jedion.KV();
                        kv.key = key;
                        kv.value = value;
                        ret.add(kv);
                    }
                });
        return ret;
    }

    public void visitDB(String dbName, Jedion.JedionVisitor visitor) {
        RocksDB db = getDB(dbName);
        RocksIterator iter = db.newIterator();
        iter.seekToFirst();
        for (; iter.isValid(); iter.next()) {
            visitor.visit(new String(iter.key()), new String(iter.value()));
        }
    }

    public String getValue(String dbName, String key) {
        try {
            RocksDB db = getDB(dbName);
            return new String(db.get(key.getBytes()));
        } catch (Exception e) {
            return null;
        }
    }

    public void setValue(String dbName, String key, String value) {
        try {
            RocksDB db = getDB(dbName);
            db.put(key.getBytes(), value.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void delete(String dbName, String key) {
        try {
            RocksDB db = getDB(dbName);
            db.delete(key.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void setupDB(String dbName) {
        File file = new File(dbPath + "/" + dbName);
        Options options = new Options();
        options.setCreateIfMissing(true);
        File lockFile = new File(file, "LOCK");
        RocksDB db = null;
        LOGGER.trace("delete file" + lockFile.getAbsolutePath() + ": " + lockFile.delete());
        try {
            if (!file.exists()) {
                LOGGER.trace("create directory " + file.getAbsolutePath() + ": " + file.mkdirs());
            }
            // logger.info("OpenDB:" + file.getAbsolutePath());
            db = RocksDB.open(options, file.getAbsolutePath());
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        dbLink.put(dbName, db);
    }

    public long getCount(String dbName) {
        try {
            return getKeys(dbName).size();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0L;
    }
}
