/*
 * Copyright (c) 2022 MiLaboratories Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.milaboratory.util;

import org.apache.commons.io.FileUtils;
import org.apache.commons.math3.random.RandomDataGenerator;
import org.apache.commons.math3.random.Well44497b;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

public class TempFileManager {
    private static volatile String prefix = "milib_";
    private static final AtomicBoolean initialized = new AtomicBoolean(false);
    static final ConcurrentHashMap<String, File> createdFiles = new ConcurrentHashMap<>();
    private static final RandomDataGenerator privateRandom = new RandomDataGenerator(new Well44497b());

    public static void setPrefix(String prefix) {
        TempFileManager.prefix = prefix;
    }

    public static void seed(long seed) {
        synchronized (privateRandom) {
            privateRandom.getRandomGenerator().setSeed(seed);
        }
    }

    private static synchronized void ensureInitialized() {
        if (initialized.compareAndSet(false, true)) {
            // Adding delete files shutdown hook on the very firs execution of getTempFile()
            Runtime.getRuntime().addShutdownHook(new Thread(new RemoveAction(), "DeleteTempFiles"));
            seed(System.nanoTime() + 17 * (new SecureRandom()).nextLong());
        }
    }

    public static File getTempFile() {
        return getTempFile(null, null);
    }

    public static File getTempFile(String suffix) {
        return getTempFile(null, suffix);
    }

    public static File getTempFile(Path tmpDir) {
        return getTempFile(tmpDir, null);
    }

    public static File getTempFile(Path tmpDir, String suffix) {
        try {
            ensureInitialized();

            File file;
            String name;

            do {
                synchronized (privateRandom) {
                    name = prefix + privateRandom.nextHexString(40);
                }
                file = tmpDir == null ? Files.createTempFile(name, suffix).toFile() : Files.createTempFile(tmpDir, name, suffix).toFile();
            } while (createdFiles.putIfAbsent(name, file) != null);

            if (file.length() != 0)
                throw new RuntimeException();

            return file;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static File getTempDir() {
        try {
            ensureInitialized();

            File dir;
            String name;

            do {
                synchronized (privateRandom) {
                    name = prefix + privateRandom.nextHexString(40);
                }
                dir = Files.createTempDirectory(name).toFile();
            } while (createdFiles.putIfAbsent(name, dir) != null);

            return dir;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Register temp directory or file. The file or directory will be deleted on JVM shutdown.
     * Anyway it is a good idea to delete the folder as early as possible.
     *
     * @param path file or directory
     */
    public static void register(File path) {
        ensureInitialized();
        createdFiles.put(path.getAbsolutePath(), path);
    }

    public static TempFileDest systemTempFolderDestination(String nameSuffix) {
        String fName = nameSuffix + privateRandom.nextHexString(20);
        TempFileDest result = new TempFileDest.Folder(Paths.get(System.getProperty("java.io.tmpdir"))
                .resolve(fName), true);
        result.reset();
        return result;
    }

    public static TempFileDest smartTempDestination(String outputFile,
                                                    String additionalPrefix,
                                                    boolean useSystemTemp) {
        return smartTempDestination(Paths.get(outputFile), additionalPrefix, useSystemTemp);
    }

    public static TempFileDest smartTempDestination(File outputFile,
                                                    String additionalPrefix,
                                                    boolean useSystemTemp) {
        return smartTempDestination(outputFile.toPath(), additionalPrefix, useSystemTemp);
    }

    public static TempFileDest smartTempDestination(Path outputFile,
                                                    String additionalPrefix,
                                                    boolean useSystemTemp) {
        String fName = outputFile.getFileName().toString() + additionalPrefix;
        TempFileDest result;
        if (useSystemTemp) {
            fName += privateRandom.nextHexString(20);
            result = new TempFileDest.Folder(Paths.get(System.getProperty("java.io.tmpdir"))
                    .resolve(fName), true);
        } else {
            Path folder = outputFile.toAbsolutePath().getParent();
            result = new TempFileDest.FilesWithPrefix(folder, fName, true);
        }
        result.reset();
        return result;
    }

    private static final class RemoveAction implements Runnable {
        @Override
        public void run() {
            for (File file : createdFiles.values()) {
                if (file.exists()) {
                    try {
                        if (Files.isDirectory(file.toPath()))
                            FileUtils.deleteDirectory(file);
                        else
                            file.delete();
                    } catch (Exception e) {
                    }
                }
            }
        }
    }
}
