package com.atlassian.bitbucket.mesh.git;

import com.atlassian.bitbucket.mesh.io.IoBiConsumer;
import com.atlassian.bitbucket.mesh.repository.Repository;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Path;

/**
 * Provides access to the on-disk repository layout.
 */
public interface GitRepositoryLayout {

    /**
     * Edits the {@code config} file, complete with locking to prevent concurrent edits by other cluster nodes
     * or {@code git config} processes.
     * <p>
     * If {@code config} already exists, the {@code mutator} receives a {@code Reader} for it. Otherwise, the
     * first argument will be {@code null}. The {@code mutator} <i>always</i> receives a {@code Writer}, for
     * emitting the new {@code config} contents.
     *
     * @param repository the repository to edit the {@code config} for
     * @param mutator    a function to use for mutating the configuration
     * @throws IOException if the {@code mutator} fails, or if the {@code config} cannot be locked
     * @throws OverlappingFileLockException if the {@code config} file can't be locked because it has already
     *                                      been locked by this JVM
     */
    void editConfig(@Nonnull Repository repository, @Nonnull IoBiConsumer<Reader, Writer> mutator) throws IOException;

    /**
     * Edits the {@code relativePath} file, complete with locking to prevent concurrent edits by other cluster nodes
     * or {@code git} processes.
     * <p>
     * If the file already exists, the {@code mutator} receives a {@code Reader} for it. Otherwise, the
     * first argument will be {@code null}. The {@code mutator} <i>always</i> receives a {@code Writer}, for
     * emitting the new {@code relativePath} contents.
     * <p>
     * The primary use case for this method is guarding an edit with a lock. The file is edited <i>without a
     * backup</i>. This method is only suited for editing files where such protection is not necessary.
     *
     * @param repository the repository to edit the {@code relativePath} for
     * @param relativePath the path, relative to the repository, of the file to be edited
     * @param mutator    a function to use for mutating the configuration
     * @throws IOException if the {@code mutator} fails, or if the {@code relativePath} cannot be locked
     * @throws IllegalArgumentException if the {@code relativePath} resolves to a path outside the {@code repository}
     * @throws OverlappingFileLockException if the {@code relativePath} file can't be locked because it has already
     *                                      been locked by this JVM
     */
    void editFile(@Nonnull Repository repository, @Nonnull String relativePath,
                  @Nonnull IoBiConsumer<Reader, Writer> mutator) throws IOException;

    /**
     * Retrieves the directory that is used for tracking application state (such as a gc marker) for the provided
     * {@link Repository repository}
     *
     * @param repository the repository
     * @return the application state directory for the repository
     */
    @Nonnull
    Path getApplicationInfoDir(@Nonnull Repository repository);

    /**
     * Retrieves the file that stores the process identifier that is currently holding the GC lock.
     *
     * @param repository the repository
     * @return the {@code gc.pid} file for the repository
     */
    @Nonnull
    Path getGcPidFile(@Nonnull Repository repository);

    /**
     * Retrieves a {@link Path} denoting the absolute path to the {@code objects} directory within the specified
     * {@link Repository}'s {@link Repository#getPath()  directory}.
     *
     * @param repository the repository to retrieve an objects path for
     * @return the absolute path to the specified repository's {@code objects} directory
     * @throws NullPointerException if the provided {@code repository} is {@code null}
     */
    @Nonnull
    Path getObjectsDir(@Nonnull Repository repository);
}
