package com.atlassian.bitbucket.util;

import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

public class FileUtils {

    private FileUtils() {
        throw new UnsupportedOperationException(getClass().getName() +
                " is a utility class and should not be instantiated");
    }

    /**
     * Constructs a file path from a series of path segments.
     *
     * @param base base directory
     * @param segments subsequent path segments
     * @return a {@link File} with the appropriate file separator for the OS
     */
    @Nonnull
    public static File construct(@Nonnull File base, String... segments) {
        return segments.length == 0 ? base : new File(base, join(segments));
    }

    /**
     * Uses Java's {@code File.createTempFile} to create a temporary directory.
     * <p>
     * Since Java provides no mechanism for creating a temporary directory, a temp file is created (to get a name),
     * and then deleted and a directory is made in the same location.
     *
     * @param prefix    the prefix string to be used in generating the directory's name; must be at least three
     *                  characters long
     * @param suffix    the suffix string to be used in generating the directory's name; may be {@code null}, in which
     *                  case the suffix {@code ".tmp"} will be used
     * @param directory the directory in which the directory is to be created, or {@code null} if the default temporary
     *                  file directory should be used
     * @return A newly-created empty directory
     * @throws IOException If a directory could not be created
     */
    @Nonnull
    public static File createTempDir(@Nonnull String prefix, @Nullable String suffix, @Nullable File directory) throws IOException {
        File temporary = File.createTempFile(prefix, suffix, directory);
        if (temporary.delete() && temporary.mkdir()) {
            return temporary;
        }
        throw new IOException("Could not create directory: " + temporary);
    }

    /**
     * Joins the provided path {@code segments} using the path separator for the underlying OS.
     *
     * @param segments the path segments to join
     * @return a {@code String} containing the provided {@code segments}, joined with the correct OS-specific separator
     */
    @Nonnull
    public static String join(String... segments) {
        return StringUtils.join(segments, File.separator);
    }

    /**
     * Creates the specified {@code directory}, if it does not already exist. If the path does exist, it is validated
     * that it is a directory and not a file.
     *
     * @param directory the directory to create
     * @throws IllegalStateException if the {@code directory} path already exists and is not a directory, or if the
     *                               directory cannot be created
     * @throws NullPointerException if the provided {@code directory} is {@code null}
     */
    public static void mkdir(@Nonnull File directory) {
        checkNotNull(directory, "directory");

        if (directory.exists()) {
            if (!directory.isDirectory()) {
                throw new IllegalStateException(directory.getAbsolutePath() + " already exists and is not a directory");
            }
        } else if (!directory.mkdirs()) {
            throw new IllegalStateException("Could not create " + directory.getAbsolutePath());
        }
    }

    /**
     * Creates a directory with the specified {@code path}, if it does not already exist. If the path does exist, it
     * is validated that it is a directory and not a file.
     *
     * @param path the path for the new directory
     * @return the created directory
     * @throws IllegalArgumentException if the {@code path} is blank or empty
     * @throws IllegalStateException if the specified {@code path} already exists and is not a directory, or if the
     *                               directory cannot be created
     * @throws NullPointerException if the provided {@code path} is {@code null}
     */
    @Nonnull
    public static File mkdir(@Nonnull String path) {
        checkArgument(!checkNotNull(path, "path").trim().isEmpty(), "A path for the created directory is required");

        File dir = new File(path);
        mkdir(dir);

        return dir;
    }

    /**
     * Creates the specified {@code child} directory beneath the {@code parent}, if it does not already exist. If the
     * path does exist, it is validated that it is a directory and not a file. If no {@code parent} is provided, the
     * {@code child} will be created in the working directory for the application.
     *
     * @param parent the base path for creating the new directory, or {@code null} to use the working directory
     * @param child  the path beneath the parent for the new directory
     * @return the created directory
     * @throws IllegalArgumentException if the {@code child} path is blank or empty
     * @throws IllegalStateException if the {@code child} path already exists and is not a directory, or if the
     *                               directory cannot be created
     * @throws NullPointerException if the provided {@code child} is {@code null}
     */
    @Nonnull
    public static File mkdir(@Nullable File parent, @Nonnull String child) {
        checkArgument(!checkNotNull(child, "child").trim().isEmpty(), "A path for the created directory is required");

        File dir = new File(parent, child);
        mkdir(dir);

        return dir;
    }
}
