package org.bdware.sc.engine;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.sc.memory.MemoryDumpUtil;
import org.bdware.sc.memory.MemoryRecoverUtil;
import org.bdware.sc.redo.TransRecordUtil;
import org.bdware.sc.redo.TransRecoverUtil;
import org.bdware.sc.syncMech.SyncRecord;
import org.bdware.sc.syncMech.SyncType;
import org.bdware.sc.trace.TraceRecordUtil;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

/*
 * 决策使用什么机制记录
 */
public class SyncMechUtil {
    private static final Logger LOGGER = LogManager.getLogger(SyncMechUtil.class);
    public AtomicInteger filedTrans; // 记录到文件中的trans数

    public Boolean startFlag = false;
    public List<SyncRecord> syncRecords;
    // public List<SyncRecord> syncRecoverRecords;
    public SyncType currType;

    public MemoryDumpUtil memoryDumpUtil;
    public MemoryRecoverUtil memoryRecoverUtil;
    public TraceRecordUtil traceRecordUtil;
    // public TraceRecoverUtil traceRecoverUtil;
    public TransRecordUtil transRecordUtil;
    public TransRecoverUtil transRecoverUtil;

    public DesktopEngine engine;
    public String contractID;
    public String syncFileName;
    public String dir;
    public String memoryDir;
    public String traceDir;
    public String transDir;
    public String syncDir;
    public String crDir; // ContractRecord dir

    public SyncMechUtil(DesktopEngine en) {
        this.engine = en;
    }

    /*
     * 路径操作
     */
    public void setDir(String str) {
        this.dir = str;
        String[] strs = str.split("/");
        this.contractID = strs[strs.length - 1];
        setMemoryDir(dir + "memory");
        setTraceDir(dir + "trace");
        setTransDir(dir + "trans");
        setSyncDir(dir + "sync");
        setCrDir(dir + "cr");
    }

    public void setMemoryDir(String dir) {
        this.memoryDir = dir;
        File f = new File(memoryDir);
        if (!f.exists()) {
            f.mkdirs();
        }
    }

    public void setTraceDir(String dir) {
        this.traceDir = dir;
        File f = new File(traceDir);
        if (!f.exists()) {
            f.mkdirs();
        }
    }

    public void setTransDir(String dir) {
        this.transDir = dir;
        File f = new File(transDir);
        if (!f.exists()) {
            f.mkdirs();
        }
    }

    public void setSyncDir(String dir) {
        this.syncDir = dir;
        File f = new File(syncDir);
        if (!f.exists()) {
            f.mkdirs();
        }
    }

    public void setCrDir(String dir) {
        this.crDir = dir;
        File f = new File(crDir);
        if (!f.exists()) {
            f.mkdirs();
        }
    }

    public void clearAllFiles() {
        cleanDirectory(syncDir);
        cleanDirectory(crDir);
        cleanDirectory(memoryDir);
        cleanDirectory(traceDir);
        cleanDirectory(transDir);
    }

    private void cleanDirectory(String path) {
        File file = new File(path);
        if (file.isDirectory()) {
            String[] children = file.list();
            for (String child : children) {
                File temp = new File(file, child);
                temp.delete();
            }
        }
    }

    /*
     * memory的操作
     * 无需startFlag就可以使用memory的操作
     */
    public synchronized String dumpMemory(String path) {
        if (memoryDumpUtil == null) memoryDumpUtil = new MemoryDumpUtil(engine.engine);
        return memoryDumpUtil.dumpMemory(path, true);
    }

    public synchronized String dumpMemory(String path, boolean stateful) {
        LOGGER.info("dumpMemroy : stateful=" + stateful);
        if (memoryDumpUtil == null) memoryDumpUtil = new MemoryDumpUtil(engine.engine);
        return memoryDumpUtil.dumpMemory(path, stateful);
    }

    public synchronized String loadMemoryDump(String path) {
        if (memoryRecoverUtil == null)
            memoryRecoverUtil = new MemoryRecoverUtil(engine.engine, engine.resources);
        memoryRecoverUtil.loadMemory(path, true);
        return "success";
    }

    public synchronized String loadMemoryDump(String path, boolean stateful) {
        LOGGER.info("loadMemroy : stateful=" + stateful);
        if (memoryRecoverUtil == null)
            memoryRecoverUtil = new MemoryRecoverUtil(engine.engine, engine.resources);
        memoryRecoverUtil.loadMemory(path, stateful);
        return "success";
    }

    /*
     *   同步机制操作
     */
    // 设置ContractRecord持久化的文件
    public void setCRFile(String fileName) {
        syncFileName = fileName.substring(15);
        File file = new File(syncDir + "/" + syncFileName);
        try {
            FileWriter fw = new FileWriter(file, true);
            PrintWriter pw = new PrintWriter(fw);
            pw.println(fileName);
            pw.flush();
            fw.flush();
            pw.close();
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void setStartFlag(boolean b) {
        synchronized (startFlag) {
            if (startFlag == b && !b) {
                return;
            }

            this.startFlag = b;
            if (startFlag) { // start
                // 如果之前start过了，此次start就是一个检查点

                filedTrans = new AtomicInteger(-1);
                syncRecords = new ArrayList<>();
                // dump current
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH_mm_ss");
                String path = format.format(new Date()) + "_" + new Random().nextInt();
                String state = dumpMemory(memoryDir + "/" + path);

                syncRecords.add(new SyncRecord(SyncType.Memory, path));

                changeCurrType(SyncType.Trans);
                path = format.format(new Date()) + "_" + new Random().nextInt();
                transRecordUtil.setFileName(path);
                syncRecords.add(new SyncRecord(SyncType.Trans, path));

                // write syncRecords
                appendSyncFile(syncFileName, 0, 1);
            } else { // stop
                clearAllFiles();
            }
        }
    }

    public void changeCurrType(SyncType t) {
        if (t == currType) return;

        // finASyncRecord();
        currType = t;
        switch (currType) {
            case Trace:
                if (traceRecordUtil == null) {
                    traceRecordUtil = new TraceRecordUtil(engine, this);
                }
                break;
            case Trans:
                if (transRecordUtil == null) {
                    transRecordUtil = new TransRecordUtil(engine, this);
                }
                break;
            case Memory:
                if (memoryDumpUtil == null) memoryDumpUtil = new MemoryDumpUtil(engine.engine);
                break;
            default:
                break;
        }
        // startASyncRecord();
    }

    // 追加写sync文件
    public void appendSyncFile(String fileName, int start, int end) {
        File file = new File(syncDir + "/" + fileName);
        try {
            FileWriter fw = new FileWriter(file, true);
            PrintWriter pw = new PrintWriter(fw);
            for (int i = start; i <= end; i++) {
                pw.println(syncRecords.get(i).getContent());
            }
            pw.flush();
            fw.flush();
            pw.close();
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /*
     * Trans操作
     */
    public synchronized String redo(String path) {
        if (transRecoverUtil == null) transRecoverUtil = new TransRecoverUtil(engine);

        // 先清空，否则会重复执行一些trans
        if (transRecoverUtil.transRecords != null && !transRecoverUtil.transRecords.isEmpty())
            transRecoverUtil.transRecords.clear();

        // 某一次检查点之后没有transRecords
        File file = new File(path);
        if (!file.exists()) return "success";

        transRecoverUtil.setTraceRecords(path);
        transRecoverUtil.recoverFromTransRecord();
        return "success";
    }
}