// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.util.backup;

import java.time.temporal.TemporalUnit;
import java.time.temporal.ChronoUnit;
import javax.annotation.Nullable;
import java.nio.file.DirectoryStream;
import java.io.IOException;
import java.util.Iterator;
import java.nio.file.attribute.FileTime;
import java.util.List;
import java.time.temporal.Temporal;
import java.nio.file.LinkOption;
import com.hypixel.hytale.server.core.Options;
import java.util.logging.Level;
import java.nio.file.StandardCopyOption;
import java.nio.file.CopyOption;
import java.time.temporal.TemporalAccessor;
import java.time.LocalDateTime;
import java.nio.file.Files;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.Path;
import javax.annotation.Nonnull;
import java.util.concurrent.CompletableFuture;
import com.hypixel.hytale.logger.HytaleLogger;
import java.time.Duration;
import java.time.format.DateTimeFormatter;

public class BackupTask
{
    private static final DateTimeFormatter BACKUP_FILE_DATE_FORMATTER;
    private static final Duration BACKUP_ARCHIVE_FREQUENCY;
    private static final HytaleLogger LOGGER;
    @Nonnull
    private final CompletableFuture<Void> completion;
    
    public static CompletableFuture<Void> start(@Nonnull final Path universeDir, @Nonnull final Path backupDir) {
        final BackupTask task = new BackupTask(universeDir, backupDir);
        return task.completion;
    }
    
    private BackupTask(@Nonnull final Path universeDir, @Nonnull final Path backupDir) {
        this.completion = new CompletableFuture<Void>();
        new Thread("Backup Runner") {
            {
                this.setDaemon(false);
            }
            
            @Override
            public void run() {
                BackupUtil.broadcastBackupStatus(true);
                try {
                    final Path archiveDir = backupDir.resolve("archive");
                    Files.createDirectories(backupDir, (FileAttribute<?>[])new FileAttribute[0]);
                    Files.createDirectories(archiveDir, (FileAttribute<?>[])new FileAttribute[0]);
                    BackupTask.cleanOrArchiveOldBackups(backupDir, archiveDir);
                    BackupTask.cleanOldBackups(archiveDir);
                    final String backupName = BackupTask.BACKUP_FILE_DATE_FORMATTER.format(LocalDateTime.now()) + ".zip";
                    final Path tempZip = backupDir.resolve(backupName + ".tmp");
                    BackupUtil.walkFileTreeAndZip(universeDir, tempZip);
                    final Path backupZip = backupDir.resolve(backupName);
                    Files.move(tempZip, backupZip, StandardCopyOption.REPLACE_EXISTING);
                    BackupTask.LOGGER.at(Level.INFO).log("Successfully created backup %s", backupZip);
                    BackupTask.this.completion.complete(null);
                }
                catch (final Throwable t) {
                    BackupTask.LOGGER.at(Level.SEVERE).withCause(t).log("Backup failed with exception");
                    BackupUtil.broadcastBackupError(t);
                    BackupTask.this.completion.completeExceptionally(t);
                }
                finally {
                    BackupUtil.broadcastBackupStatus(false);
                }
            }
        }.start();
    }
    
    private static void cleanOrArchiveOldBackups(@Nonnull final Path sourceDir, @Nonnull final Path archiveDir) throws IOException {
        final int maxCount = Options.getOptionSet().valueOf(Options.BACKUP_MAX_COUNT);
        if (maxCount < 1) {
            return;
        }
        List<Path> oldBackups = BackupUtil.findOldBackups(sourceDir, maxCount);
        if (oldBackups == null || oldBackups.isEmpty()) {
            return;
        }
        final Path oldestBackup = oldBackups.getFirst();
        final FileTime oldestBackupTime = Files.getLastModifiedTime(oldestBackup, new LinkOption[0]);
        final FileTime lastArchive = getMostRecentArchive(archiveDir);
        final boolean doArchive = lastArchive == null || Duration.between(oldestBackupTime.toInstant(), lastArchive.toInstant()).compareTo(BackupTask.BACKUP_ARCHIVE_FREQUENCY) > 0;
        if (doArchive) {
            oldBackups = oldBackups.subList(1, oldBackups.size());
            Files.move(oldestBackup, archiveDir.resolve(oldestBackup.getFileName()), StandardCopyOption.REPLACE_EXISTING);
            BackupTask.LOGGER.at(Level.INFO).log("Archived old backup: %s", oldestBackup);
        }
        for (final Path path : oldBackups) {
            BackupTask.LOGGER.at(Level.INFO).log("Clearing old backup: %s", path);
            Files.deleteIfExists(path);
        }
    }
    
    private static void cleanOldBackups(@Nonnull final Path dir) throws IOException {
        final int maxCount = Options.getOptionSet().valueOf(Options.BACKUP_MAX_COUNT);
        if (maxCount < 1) {
            return;
        }
        final List<Path> oldBackups = BackupUtil.findOldBackups(dir, maxCount);
        if (oldBackups == null || oldBackups.isEmpty()) {
            return;
        }
        for (final Path path : oldBackups) {
            BackupTask.LOGGER.at(Level.INFO).log("Clearing old backup: %s", path);
            Files.deleteIfExists(path);
        }
    }
    
    @Nullable
    private static FileTime getMostRecentArchive(@Nonnull final Path dir) throws IOException {
        FileTime mostRecent = null;
        try (final DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
            for (final Path path : stream) {
                if (!Files.isRegularFile(path, new LinkOption[0])) {
                    continue;
                }
                final FileTime modifiedTime = Files.getLastModifiedTime(path, new LinkOption[0]);
                if (mostRecent != null && modifiedTime.compareTo(mostRecent) <= 0) {
                    continue;
                }
                mostRecent = modifiedTime;
            }
        }
        return mostRecent;
    }
    
    static {
        BACKUP_FILE_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
        BACKUP_ARCHIVE_FREQUENCY = Duration.of(12L, ChronoUnit.HOURS);
        LOGGER = HytaleLogger.forEnclosingClass();
    }
}
