package org.bdware.sc.db;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.sc.bean.IDSerializable;
import org.bdware.sc.index.TimeSerialIndex;
import org.bdware.sc.util.JsonUtil;
import org.rocksdb.Options;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import org.rocksdb.RocksIterator;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public abstract class StatusRecorder<T extends IDSerializable> {
    private static final Logger LOGGER = LogManager.getLogger(StatusRecorder.class);
    private final Map<String, T> status;
    TimeSerialIndex versionIndex;
    RocksDB db;
    String preFix;

    public StatusRecorder(String dirPath, String dbName, String preFix) {
        this.preFix = preFix;
        status = new ConcurrentHashMap<>();
        versionIndex = new TimeSerialIndex(dirPath + "/" + dbName + ".timeindex");
        db = setUpDB(new File(dirPath, dbName).getAbsolutePath());
        RocksIterator iter = db.newIterator();
        iter.seekToFirst();
        Set<String> toRemove = new HashSet<>();
        for (; iter.isValid(); iter.next()) {
            try {

                ObjectInputStream os =
                        new ObjectInputStream(new ByteArrayInputStream(iter.value()));
                T meta = (T) os.readObject();
                status.put(meta.getID(), meta);

            } catch (Exception e) {
                e.printStackTrace();
                toRemove.add(new String(iter.key()));
            }
        }
        try {
            for (String toDelete : toRemove) {
                db.delete(toDelete.getBytes());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        LOGGER.debug(dbName + " load size:" + status.size());
        LOGGER.debug(dbName + " toRemoveKeys:" + JsonUtil.toJson(toRemove));
    }

    private RocksDB setUpDB(String absolutePath) {
        File file = new File(absolutePath);
        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());
            }
            db = RocksDB.open(options, file.getAbsolutePath());
            return db;
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return db;
    }

    public synchronized void updateValue(T t) {
        // 同步更新到数据库
        if (null == t) {
            return;
        }
        try {
            status.put(t.getID(), t);
            String key = preFix + t.getID();
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            ObjectOutputStream oo = new ObjectOutputStream(bo);
            oo.writeObject(t);
            oo.close();
            db.put(key.getBytes(), bo.toByteArray());
            versionIndex.index(t.getID().hashCode());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void remove(T t) {
        try {
            status.remove(t.getID());
            db.delete((preFix + t.getID()).getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    List<T> getLast(int count) {
        List<T> ret = new ArrayList<>();
        Set<Long> set = new HashSet<>(versionIndex.requestLast(count));
        for (T t : status.values()) {
            if (null != t && set.contains((long) t.getID().hashCode())) {
                ret.add(t);
            }
        }
        return ret;
    }

    public long getVersion() {
        return versionIndex.size();
    }

    public Map<String, T> getStatus() {
        return status;
    }
}
