package org.bdware.sc.boundry.utils;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.sc.ContractProcess;
import org.bdware.sc.compiler.PermissionStub;
import org.bdware.sc.node.Permission;
import org.rocksdb.Options;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksIterator;
import wrp.jdk.nashorn.internal.runtime.PropertyMap;
import wrp.jdk.nashorn.internal.runtime.ScriptObject;
import wrp.jdk.nashorn.internal.scripts.JO;

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

@PermissionStub(permission = Permission.RocksDB)
public class RocksDBUtil {
    private static final Logger LOGGER = LogManager.getLogger(RocksDBUtil.class);

    static Map<String, RocksDBUtil> cacheDB = new HashMap<>();

    static {
        RocksDB.loadLibrary();
    }

    RocksDB rocksDB;
    String path;

    public void tryLoad(String path, boolean readOnly) throws Exception {
        this.path = path;
        Options options = new Options();
        options.setCreateIfMissing(true);
        File parent = new File("./ContractDB/" + ContractProcess.getContractDir());
        File dir = new File(parent, path);
        //LOGGER.info("init RocksDB in " + dir.getAbsolutePath());
        if (!dir.exists()) {
            LOGGER.info("create directory " + dir.getAbsolutePath() + ": " + dir.mkdirs());
        }
        File lockFile = new File(dir, "LOCK");
        if (readOnly) {
            rocksDB = RocksDB.openReadOnly(options, dir.getAbsolutePath());
        } else {
            rocksDB = RocksDB.open(options, dir.getAbsolutePath());
        }
    }

    public RocksDBUtil(String path, boolean readOnly) {
        try {
            tryLoad(path, readOnly);
        } catch (Exception e) {
            LOGGER.info("======TRY Load Again 2s later====");
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException ex) {
            }
            try {
                tryLoad(path, readOnly);
            } catch (Exception ex) {
                LOGGER.info("======LOAD FAILED!====");
                e.printStackTrace();
            }
        }
    }

    public static synchronized RocksDBUtil loadDB(String path, boolean readOnly) {
        if (cacheDB.containsKey(path)) {
            return cacheDB.get(path);
        }
        RocksDBUtil ret = new RocksDBUtil(path, readOnly);
        cacheDB.put(path, ret);
        return ret;
    }

    public static RocksDBUtil loadDB(String path, String readOnly) {
        return loadDB(path, Boolean.parseBoolean(readOnly));
    }

    public void close() {
        rocksDB.close();
        cacheDB.remove(path);
    }

    public String get(String key) {
        try {
            return new String(rocksDB.get(key.getBytes()));
        } catch (Exception e) {
        }
        return null;
    }

    public long estimateKeySize() {
        try {
            return rocksDB.getLongProperty("rocksdb.estimate-num-keys");
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    public void put(String key, String value) {
        try {
            rocksDB.put(key.getBytes(), value.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String delete(String key) {
        try {
            rocksDB.delete(key.getBytes());

            return "success";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "failed";
    }

    public RocksIterator newIterator() {
        try {
            return rocksDB.newIterator();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public ScriptObject getNext(RocksIterator iter) {
        if (iter.isValid()) {
            JO ret = new JO(PropertyMap.newMap());
            ret.put("key", new String(iter.key()), false);
            ret.put("value", new String(iter.value()), false);
            iter.next();
            return ret;
        }
        return null;
    }

    public String clean() {
        return "todo";
    }
}
