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 MultiIndexTimeDBUtil implements MultiIndexTimeDBUtilIntf {
    private static final Logger LOGGER = LogManager.getLogger(MultiIndexTimeDBUtil.class);
    private final Map<String, TimeSerialIndex> secondaryIndex;
    public String dbPath;
    public String tableName;
    Random random = new Random();
    private Jedion db;
    private TimeSerialIndex primaryIndex;

    public MultiIndexTimeDBUtil(String path, String tableName) {
        secondaryIndex = new HashMap<>();
        dbPath = path;
        this.tableName = tableName;
        setupDB();
    }

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

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

        primaryIndex = new TimeSerialIndex(timeIndex.getAbsolutePath());
    }

    public synchronized void put(String label, String val) {
        TimeSerialIndex index = getIndex(label);
        long key = random.nextLong();
        index.index(key);
        if (index != primaryIndex) {
            primaryIndex.index(key);
        }
        db.writeToDatabase(String.valueOf(key), val, true);
    }

    public synchronized long queryOffset(String label, long begin) {
        TimeSerialIndex index = getIndex(label);
        return index.findNearest(begin);
    }

    public synchronized List<String> queryByDateAsString(String label, long begin, long end) {
        List<String> ret = new ArrayList<>();
        TimeSerialIndex index = getIndex(label);
        long offset = index.findNearest(begin);
        long size = index.findNearest(end);
        int len = (int) (size - offset + 1);
        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 synchronized List<JsonObject> queryByDateAsJson(String label, long begin, long end) {
        List<JsonObject> ret = new ArrayList<>();
        TimeSerialIndex index = getIndex(label);
        long offset = index.findNearest(begin);
        long size = index.findNearest(end);
        int len = (int) (size - offset + 1);
        List<Long> data = index.request(offset, len);
        for (Long l : data) {
            String t = null;
            try {
                t = db.readFromDatabase(l.toString());
                if (t != null && !t.isEmpty()) {
                    JsonObject jo = JsonUtil.parseStringAsJsonObject(t);
                    jo.addProperty("key", l.toString());
                    ret.add(jo);
                }
            } catch (Exception e) {
                LOGGER.error("parse db json error:" + t);
            }
        }
        return ret;
    }

    public long size() {
        return primaryIndex.size();
    }

    public long size(String label) {
        try {
            TimeSerialIndex index = getIndex(label);
            if (index != null) return index.size();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0L;
    }

    public List<JsonObject> queryByOffset(String label, long offset, int count) {
        List<JsonObject> ret = new ArrayList<>();
        TimeSerialIndex index = getIndex(label);
        List<Long> data = index.request(offset, count);
        for (Long l : data) {
            try {
                String t = db.readFromDatabase(l.toString());
                JsonObject jo;
                if (t != null && !t.isEmpty()) {
                    jo = JsonUtil.parseStringAsJsonObject(t);
                } else {
                    jo = new JsonObject();
                }
                jo.addProperty("key", l.toString());
                ret.add(jo);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    public String get(String key) {
        try {
            return db.readFromDatabase(key);
        } catch (DatabaseException e) {
            e.printStackTrace();
        }
        return null;
    }

    private TimeSerialIndex getIndex(String label) {
        if (null == label) {
            return primaryIndex;
        }
        if (secondaryIndex.containsKey(label)) {
            return secondaryIndex.get(label);
        } else {
            TimeSerialIndex index =
                    new TimeSerialIndex(dbPath + "/" + tableName + "/" + label + ".timeindex");
            secondaryIndex.putIfAbsent(label, index);
            return index;
        }
    }

    // TODO remove this method, using index by type instead
    public Map<String, Integer> querySort(String label, long date) {
        List<String> ret = new ArrayList<>();
        Map<String, Integer> map = new HashMap<>();
        Map<String, String> data2;
        TimeSerialIndex index = getIndex(label);
        long offset = index.findNearest(date);
        int len = (int) (index.size() - offset);
        List<Long> data = index.request(offset, len);
        String action;
        for (Long l : data) {
            String t = null;
            try {
                t = db.readFromDatabase(l.toString());
                data2 = JsonUtil.fromJson(t, new TypeToken<Map<String, String>>() {
                }.getType());
                if (null != data2) {
                    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) {
                LOGGER.error("parse db json error:" + t);
            }
        }
        return map;
    }

    public JsonArray countInInterval(String label, long start, long interval, long end) {
        TimeSerialIndex index = getIndex(label);
        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;
    }

    // TODO
    public void rebuildIndex(String labelField) {
        try {
            List<Jedion.KV> dataKVs = db.getEveryKeyValue();
            Map<Long, Jedion.KV> memoryIndex = new HashMap<>();
            List<Long> times = new ArrayList<>();
            for (Jedion.KV dataKV : dataKVs) {
                JsonObject jo = JsonUtil.fromJson(dataKV.value, JsonObject.class);
                Long time = jo.get("date").getAsLong();
                if (time < 0) continue;
                times.add(time);
                memoryIndex.put(time, dataKV);
            }
            times.sort(Long::compareTo);
            for (Long time : times) {
                Jedion.KV dataKV = memoryIndex.get(time);
                JsonObject jo = JsonUtil.fromJson(dataKV.value, JsonObject.class);
                TimeSerialIndex tsi = getIndex(jo.get(labelField).getAsString());
                tsi.manullyIndex(time, Long.parseLong(dataKV.key));
                primaryIndex.manullyIndex(time, Long.parseLong(dataKV.key));
            }
            // System.out.println("leak size = " + (dataKVs.size() - memoryIndex.size()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 每一条data都是json串
        // 转为JsonObject_obj后，获取obj.date，obj.labelField, key，
        // 按date排序后，
        // 把每个TimeIndex都primaryIndex.manuellyIndex(obj.data,key);
        // manuellyIndex可参考LenVarTimeIndex写法
    }
}
