package com.atlassian.bitbucket.mesh.repository;

import com.atlassian.bitbucket.mesh.git.ref.RefUpdate;
import com.atlassian.bitbucket.mesh.git.worktree.CreateWorkTreeParameters;
import com.atlassian.bitbucket.mesh.git.worktree.PublishWorkTreeParameters;
import com.atlassian.bitbucket.mesh.git.worktree.WorkTreeNotFoundException;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.channels.WritableByteChannel;

public interface WorkTreeManager {

    /**
     * Remove the work tree, unpublished changes are <em>lost</em>. This operation cannot be undone.
     *
     * @param workTree to remove
     */
    void close(@Nonnull WorkTree workTree);

    /**
     * Create a new work tree. This will set up a new work tree on disk and return all information to the caller.
     *
     * @param parameters parameters to use to set up and configure the work tree
     * @return           a work tree that is ready to be used
     */
    @Nonnull
    WorkTree create(@Nonnull CreateWorkTreeParameters parameters);

    /**
     * Get a work tree with the given ID. If no work tree is found {@code null} is returned. The returned work tree
     * may be expired.
     *
     * @param id ID of the work tree.
     * @return A WorkTree if the ID is valid, {@code null} for all IDs that do not denote an existing work tree
     */
    @Nullable
    WorkTree findById(@Nonnull String id);


    /**
     * Retrieves the work tree with the specified {@code id}.
     *
     * @param id the ID of the work tree to retrieve
     * @return the specified work tree
     * @throws WorkTreeNotFoundException if the work tree can't be found
     */
    @Nonnull
    WorkTree getById(@Nonnull String id);

    /**
     * Creates a new directory, and all of its parent directories, at {@code relativePath}. If a directory already
     * exists at {@code relativePath}, the method simply returns.
     *
     * @param workTree work tree to create the directory in
     * @param relativePath the path at which a directory should be created, relative to the repository root
     */
    void mkdir(@Nonnull WorkTree workTree, @Nonnull String relativePath);

    /**
     * Prepare the publishing of changes to the repository. Neither work tree nor repository will be changed during
     * the operation. See {@link #updateRef(WorkTree, PublishWorkTreeParameters, RefUpdate)} for updating the repository.
     *
     * @param workTree work tree to prepare publishing for
     * @param parameters parameters detailing the publish operation
     * @return the change that will be published to the repository
     */
    @Nonnull
    RefUpdate publish(@Nonnull WorkTree workTree, @Nonnull PublishWorkTreeParameters parameters);

    /**
     * Update the repository with the changes in the work tree. Any missing objects will be copied to the repository
     * and then the ref detailed in the {@link PublishWorkTreeParameters} is updated pointing to the new commit.
     * See {@link #publish(WorkTree, PublishWorkTreeParameters)} for how to prepare the publish.
     *
     * @param workTree work tree to copy changes from
     * @param parameters parameters detailing the update ref operation
     */
    void updateRef(@Nonnull WorkTree workTree, @Nonnull PublishWorkTreeParameters parameters, @Nonnull RefUpdate refUpdate);

    /**
     * Opens a {@link WritableByteChannel} to the file with the relative path given. The file will be created if it
     * does not exist, but no directories will be created, use {@link WorkTreeManager#mkdir(WorkTree, String)} to
     * create the directory first.
     *
     * @param workTree work tree to write the file in
     * @param relativePath the path at which a directory should be created, relative to the repository root
     * @return an open channel to the requested file
     */
    @Nonnull
    WritableByteChannel writeFile(@Nonnull WorkTree workTree, @Nonnull String relativePath) throws IOException;
}
