package org.bdware.sc.db;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.sleepycat.je.DatabaseException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.sc.Jedion;
import org.bdware.sc.index.TimeSerialIndex;
import org.bdware.sc.util.JsonUtil;

import java.io.File;
import java.util.*;

public class TimeDBUtil {
    private static final Logger LOGGER = LogManager.getLogger(TimeDBUtil.class);
    public static TimeDBUtil instance;
    private final Map<String, Jedion> dbLink;
    private final Map<String, TimeSerialIndex> index;
    public String dbPath;
    Random random = new Random();

    public TimeDBUtil(String path) {
        dbLink = new HashMap<>();
        index = new HashMap<>();
        dbPath = path;
    }

    public static void setupNC() {
        instance = new TimeDBUtil("./NodeCenterDB");
    }

    public static void setupCM() {
        instance = new TimeDBUtil("./ContractManagerDB");
    }

    private static void deleteJelck(File file) {
        if (file.exists()) {
            LOGGER.trace("delete file" + file.getAbsolutePath() + ": " + file.delete());
        }
    }

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

    private void setupDB(String dbName) {
        Jedion db = new Jedion(dbName);
        File file = new File(dbPath + "/" + dbName);
        File timeIndex = new File(dbPath + "/" + dbName + ".timeindex");
        deleteJelck(new File(file, "je.lck"));
        if (!file.exists()) {
            LOGGER.trace("create directory " + file.getAbsolutePath() + ": " + file.mkdirs());
        }
        db.configEnvironment(file);
        db.createDatabase();
        dbLink.put(dbName, db);
        index.put(dbName, new TimeSerialIndex(timeIndex.getAbsolutePath()));
    }

    public void put(String dbName, String val) {
        Jedion db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long key = random.nextLong();
        index.index(key);

        db.writeToDatabase(String.valueOf(key), val, true);
    }

    public JsonArray countInIntreval(String dbName, long start, long interval, long end) {
        TimeSerialIndex index = getIndex(dbName);
        long offset = index.findNearest(start);
        JsonArray array = new JsonArray();
        do {
            start += interval;
            long offset2 = index.findNearest(start);
            array.add(offset2 - offset);
            offset = offset2;
        } while (start < end);
        return array;
    }

    public List<String> query(String dbName, long end) {
        List<String> ret = new ArrayList<>();
        Jedion db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);
        for (Long l : data) {
            try {
                String t = db.readFromDatabase(l.toString());
                if (!t.isEmpty()) {
                    ret.add(t);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    public String queryDetail(String dbName, String key) {
//        if (!dbName.equals(CMTables.LocalContractLogDB.toString())) {
//            return null;
//        }
        try {
            Jedion db = getDB(dbName);
            return db.readFromDatabase(key);
        } catch (DatabaseException e) {
            e.printStackTrace();
        }

        return null;
    }

    public List<JsonObject> queryLogBefore(String dbName, long end, String contractName) {
        if (!dbName.equals(CMTables.LocalContractLogDB.toString())) {
            return null;
        }
        List<JsonObject> ret = new ArrayList<>();
        Jedion db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);
        for (Long l : data) {
            try {
                String t = db.readFromDatabase(l.toString());
                if (null != t && !t.isEmpty()) {
                    JsonObject jo = JsonUtil.parseStringAsJsonObject(t);
                    jo.addProperty("key", l.toString());
                    if (jo.has("contractName")
                            && jo.get("contractName").getAsString().equals(contractName)) {
                        ret.add(jo);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    public List<String> queryWithKey(String dbName, long end) {
        if (!dbName.equals(CMTables.LocalContractLogDB.toString())) {
            return null;
        }

        List<String> ret = new ArrayList<>();
        Jedion db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);

        for (Long l : data) {
            try {
                String t = db.readFromDatabase(l.toString());
                if (t != null && !t.isEmpty()) {
                    t = t.substring(0, t.length() - 1);
                    t += (",\"key\":\"" + l + "\"}");
                    ret.add(t);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    public List<String> querySort(String dbName, long end) {
        if (!(dbName.equals("NC_CMLog")
                || dbName.equals("NC_NodeLogDB")
                || dbName.equals(CMTables.LocalNodeLogDB.toString())
                || dbName.equals(CMTables.LocalContractLogDB.toString()))) {
            return null;
        }

        List<String> ret = new ArrayList<>();
        Map<String, Integer> map = new HashMap<>();
        Map<String, String> data2;

        Jedion db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);

        String action;
        for (Long l : data) {
            try {
                String t = db.readFromDatabase(l.toString());
                data2 = JsonUtil.fromJson(t, new TypeToken<Map<String, String>>() {
                }.getType());
                if (data2 != null) {
                    action = data2.get("action");
                    if (map.containsKey(action)) {
                        int count = map.get(action) + 1;
                        map.put(action, count);
                    } else {
                        map.put(action, 1);
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        for (String key : map.keySet()) {
            ret.add("{\"action\":\"" + key + "\",\"times\":" + map.get(key) + "}");
        }

        return ret;
    }

    public List<String> queryDate(String dbName, long end) {
        if (!dbName.equals("NC_CMLog") &&
                !dbName.equals("NC_NodeLogDB") &&
                !dbName.equals(CMTables.LocalNodeLogDB.toString()) &&
                !dbName.equals(CMTables.LocalContractLogDB.toString())) {
            return null;
        }

        List<String> ret = new ArrayList<>();
        Map<String, String> data2;

        Jedion db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);

        String date;
        for (Long l : data) {
            try {
                data2 =
                        JsonUtil.fromJson(
                                db.readFromDatabase(l.toString()),
                                new TypeToken<Map<String, String>>() {
                                }.getType());
                if (data2 != null) {
                    date = data2.get("date");
                    ret.add(date);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    public List<String> queryByOffset(String dbName, int offset, int len) {
        List<String> ret = new ArrayList<>();
        Jedion db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        List<Long> data = index.request(offset, len);
        for (Long l : data) {
            try {
                String t = db.readFromDatabase(l.toString());
                ret.add(t);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    public List<JsonObject> queryActionDatePair(String dbName, long end, List<String> category) {
        if (!(dbName.equals("NC_CMLog") || (dbName.equals("NC_NodeLogDB")))) {
            return null;
        }

        List<JsonObject> ret = new ArrayList<>();
        JsonObject data2;
        Jedion db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);

        for (Long l : data) {
            try {
                String t = db.readFromDatabase(l.toString());
                data2 = JsonUtil.parseStringAsJsonObject(t);
                ret.add(data2);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return ret;
    }

    public List<JsonObject> queryActionDatePair(String dbName, long end) {
        if (!(dbName.equals("NC_CMLog") || (dbName.equals("NC_NodeLogDB")))) {
            return null;
        }

        List<JsonObject> ret = new ArrayList<>();
        JsonObject data2;

        Jedion db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);

        for (Long l : data) {
            String t = null;

            try {
                t = db.readFromDatabase(l.toString());
                data2 = JsonUtil.parseStringAsJsonObject(t);
                ret.add(data2);
            } catch (Exception e) {
                LOGGER.error("parse json error:" + t);
            }
        }

        return ret;
    }

    public String get(String dbName, String key) {
        Jedion db = getDB(dbName);
        try {
            return db.readFromDatabase(key);
        } catch (DatabaseException e) {
            e.printStackTrace();
        }
        return null;
    }

    private TimeSerialIndex getIndex(String dbName) {
        return index.get(dbName);
    }

    public long getCount(String dbName) {
        try {
            Jedion db = getDB(dbName);
            return db.getCount();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0L;
    }
}
