package zmo.technology.lite.logging;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.StreamHandler;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ZeroLoggerStreamHandler extends StreamHandler {

    public static final String ZERO_LOGGER_PATH = "zero.logger.path";
    public static final String ZERO_LOGGER_NODE = "zero.logger.node";
    public static final String ZERO_LOGGER_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";

    public static final String ZERO_LOGGER_FILE_DATE_FORMAT = "yyyyMMdd";
    public static final String ZERO_LOGGER_FILE_FORMAT = "(.*)\\.(.*)\\.([0-9]{8})\\.([0-9]{10})\\.([0-9]{3})\\.log";

    public static final int inferLoggerFormatLength(Level leval) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(ZERO_LOGGER_DATE_FORMAT);

        StringBuilder builder = new StringBuilder();
        if (!System.getProperty(ZERO_LOGGER_NODE).isEmpty()) {
            builder.append("[NODE:");
            builder.append(System.getProperty(ZERO_LOGGER_NODE));
            builder.append("]");
        }

        builder.append("[");
        builder.append(dateFormat.format(new Date()));
        builder.append("]");

        builder.append("[");
        builder.append(leval.getLocalizedName());
        builder.append("]: ");

        return builder.toString().getBytes().length;
    }

    public static final String makeLoggerFileName(String name, Level level, int sliceNumber) {
        StringBuilder builder = new StringBuilder();
        builder.append(name);
        builder.append(".");
        builder.append(level.toString().toLowerCase());
        builder.append(".");
        builder.append(new SimpleDateFormat(ZERO_LOGGER_FILE_DATE_FORMAT).format(new Date()));
        builder.append(".");
        builder.append(System.currentTimeMillis() / 1000);
        builder.append(".");
        builder.append(String.format("%03d", sliceNumber));
        builder.append(".log");
        return builder.toString();
    }

    public static class ZeroLoggerFormatter extends Formatter {

        private String nodeMessage;

        public ZeroLoggerFormatter() {
            super();
            StringBuilder builder = new StringBuilder();
            if (!System.getProperty(ZERO_LOGGER_NODE).isEmpty()) {
                builder.append("[NODE:");
                builder.append(System.getProperty(ZERO_LOGGER_NODE));
                builder.append("]");
            }
            nodeMessage = builder.toString();
        }

        @Override
        public String format(LogRecord record) {
            SimpleDateFormat dateFormat = new SimpleDateFormat(ZERO_LOGGER_DATE_FORMAT);

            StringBuilder builder = new StringBuilder();

            builder.append(nodeMessage);
            builder.append("[");
            builder.append(dateFormat.format(new Date()));
            builder.append("]");

            builder.append("[");
            builder.append(record.getLevel().getLocalizedName());
            builder.append("]: ");
            builder.append(record.getMessage());
            return builder.toString();
        }
    }

    private static final int ZERO_LOGFILE_MAX_LIMIT = 1024 * 1024 * 50;
    private static final int ZERO_LOGFILE_DATE_INTERVAL = 7 * 24 * 60 * 60;

    private int formatLength;

    private AtomicLong amassLength;
    private String name;
    private int sliceNumber;

    public ZeroLoggerStreamHandler(String name, Level leval, Formatter formatter) throws Exception {
        super();
        this.name = name;
        amassLength = new AtomicLong(0);
        formatLength = inferLoggerFormatLength(leval);
        setFormatter(formatter);
        setLevel(leval);
        setEncoding("UTF-8");
        sliceNumber = -1;
        checkLoggerFile();
    }

    public ZeroLoggerStreamHandler(String name, Level leval) throws Exception {
        this(name, leval, new ZeroLoggerFormatter());
    }

    private void cleanExpiredFiles() {
        Set<File> files = fetchLoggerFiles();
        for (File file : files) {
            Matcher matcher = Pattern.compile(ZERO_LOGGER_FILE_FORMAT).matcher(file.getName());
            matcher.find();

            if (matcher.group(1).equals(name) &&
                    matcher.group(2).equals(getLevel().toString().toLowerCase()) &&
                    System.currentTimeMillis() / 1000 - Long.parseLong(matcher.group(4)) > ZERO_LOGFILE_DATE_INTERVAL)
                file.delete();
        }
    }

    private Map.Entry<Integer, File> findLogger() {
        Set<File> files = fetchLoggerFiles();
        TreeMap<Integer, File> effectiveFiles = new TreeMap<>();
        String today = new SimpleDateFormat(ZERO_LOGGER_FILE_DATE_FORMAT).format(new Date());

        for (File file : files) {
            Matcher matcher = Pattern.compile(ZERO_LOGGER_FILE_FORMAT).matcher(file.getName());
            matcher.find();

            if (matcher.group(1).equals(name) &&
                    matcher.group(2).equals(getLevel().toString().toLowerCase()) &&
                    matcher.group(3).equals(today)) {
                effectiveFiles.put(Integer.parseInt(matcher.group(5)), file);
            }
        }
        if (effectiveFiles.isEmpty())
            return null;
        return effectiveFiles.lastEntry();
    }

    private File getLoggerFile() {
        String loggerName = makeLoggerFileName(name, getLevel(), sliceNumber);
        String directory = System.getProperty(ZERO_LOGGER_PATH);
        String pathName = directory.endsWith(File.separator)
                ? directory + loggerName
                : directory + File.separator + loggerName;
        return new File(pathName);
    }

    private void updateLoggerFile(File loggerFile) {
        try {
            close();
            setOutputStream(new BufferedOutputStream(new FileOutputStream(loggerFile, true)));
            cleanExpiredFiles();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void updateLoggerFile() {
        updateLoggerFile(getLoggerFile());
    }

    private void checkLoggerFile() {
        if (sliceNumber >= 0) {
            if (amassLength.get() >= ZERO_LOGFILE_MAX_LIMIT) {
                flush();
                sliceNumber++;
                updateLoggerFile();
                amassLength.set(0);
            }
        } else {
            Map.Entry<Integer, File> entry = findLogger();
            if (entry == null) {
                sliceNumber = 0;
                updateLoggerFile();
            } else if (entry.getValue().length() > ZERO_LOGFILE_MAX_LIMIT) {
                sliceNumber = entry.getKey();
                sliceNumber++;
                updateLoggerFile();
            } else {
                sliceNumber = entry.getKey();
                amassLength.set(entry.getValue().length());
                updateLoggerFile(entry.getValue());
            }
        }
    }

    private Set<File> recursionDirectory(File path, String patternPathName) {
        Set<File> files = new HashSet<>();
        if (path.isDirectory())
            for (File file : path.listFiles()) {
                if (file.isDirectory())
                    files.addAll(recursionDirectory(file, patternPathName));
                else if (file.isFile() && Pattern.matches(patternPathName, file.getName()))
                    files.add(file);
            }
        return files;
    }

    private Set<File> fetchLoggerFiles() {
        if (!System.getProperty(ZERO_LOGGER_PATH).isEmpty())
            return recursionDirectory(new File(System.getProperty(ZERO_LOGGER_PATH)), ZERO_LOGGER_FILE_FORMAT);
        return null;
    }

    @Override
    public void publish(LogRecord record) {
        super.publish(record);
        amassLength.set(amassLength.get() + formatLength + record.getMessage().getBytes().length);
        checkLoggerFile();
    }

    // public static void main(String[] args) {
    // System.setProperty(ZERO_LOGGER_NODE, "10.0.0.144");
    // System.setProperty(ZERO_LOGGER_PATH,
    // "/Users/bourbon/Desktop/zero-core/logs");

    // long start = System.currentTimeMillis();
    // for (int i = 0; i < 1000000; i++) {
    // try {
    // ZeroLogger.INFOLNS("新的日志");
    // throw new NullPointerException();
    // } catch (Exception e) {
    // ZeroLogger.SEVERE(e);
    // }

    // }
    // System.out.println("use : " + (System.currentTimeMillis() - start) + "ms");
    // }
}