package com.atlassian.bitbucket.repository.sync;

import com.atlassian.bitbucket.repository.Repository;

import javax.annotation.Nonnull;
import java.util.Date;
import java.util.Set;

/**
 * Describes the synchronization status for a given {@link Repository repository}. If
 * synchronization is not {@link #isEnabled() enabled}, the following collections will always be empty:
 * <ul>
 *     <li>{@link #getAheadRefs()}</li>
 *     <li>{@link #getDivergedRefs()}</li>
 *     <li>{@link #getOrphanedRefs()}</li>
 * </ul>
 * Even if synchronization is {@link #isEnabled() enabled}, it is <i>not</i> guaranteed to be {@link #isAvailable()
 * available}. If synchronization is not available <i>and</i> enabled, it will not be performed.
 */
public interface RefSyncStatus {

    /**
     * Retrieves the time at which refs were most recently synchronized from upstream. Synchronization is performed
     * incrementally, meaning it is likely only a subset of the repository's refs were affected. If synchronization
     * was once enabled but is currently disabled, this is the time of the last synchronization prior to it being
     * disabled.
     *
     * @return the time at which refs were most recently synchronized from upstream for this repository
     */
    @Nonnull
    Date getLastSync();

    /**
     * Retrieves a set of {@link RejectedRef refs} which are <i>{@link RejectedRefState#AHEAD strictly ahead}</i> of
     * their upstream refs. These refs include local changes which have not been merged to upstream, as well as all
     * of the commit that are available upstream.
     * <p>
     * The {@link RefSyncAction#DISCARD DISCARD} action may be used to <i>throw away</i> the local changes and revert
     * to the upstream commit.
     *
     * @return a set containing zero or more refs which are <i>strictly ahead</i> of upstream
     */
    @Nonnull
    Set<RejectedRef> getAheadRefs();

    /**
     * Retrieves a set of {@link RejectedRef refs} which have {@link RejectedRefState#DIVERGED diverged} from their
     * upstream refs. Unlike refs which are {@link #getAheadRefs() ahead}, diverged refs include changes which have
     * not been merged to upstream but are missing some commits that are available upstream.
     * <p>
     * The following {@link RefSyncAction actions} may be performed on diverged refs:
     * <ul>
     *     <li>{@link RefSyncAction#DISCARD DISCARD} may be used to <i>throw away</i> the local changes and pull in
     *     the new commits available upstream</li>
     *     <li>{@link RefSyncAction#MERGE MERGE} may be used to <i>merge in</i> the new upstream commits, which
     *     will leave the ref <i>{@link RejectedRefState#AHEAD strictly ahead}</i>
     *     <ul>
     *         <li>Note that, after performing a merge, the ref will move to the {@link #getAheadRefs() ahead}
     *         collection; it is still not considered to be in sync with upstream</li>
     *     </ul>
     *     </li>
     * </ul>
     *
     * @return a set containing zero or more refs which have <i>diverged</i> from upstream
     */
    @Nonnull
    Set<RejectedRef> getDivergedRefs();

    /**
     * Retrieves a set of {@link RejectedRef refs} which have been {@link RejectedRefState#ORPHANED orphaned} and
     * no longer have upstream refs. If a ref in a given repository is not referring to the same commit as its
     * upstream ref and the upstream ref is deleted, the local ref is not automatically deleted and instead becomes
     * orphaned. Additionally, refs which are pushed to the local repository without being pushed to the upstream
     * repository are also marked as orphaned.
     * <p>
     * The {@link RefSyncAction#DISCARD DISCARD} action may be used to <i>delete</i> the local ref, "synchronizing"
     * it with the state it has upstream (which is to say, no state at all).
     *
     * @return a set containing zero or more refs which have been <i>orphaned</i> and no longer exist upstream
     */
    @Nonnull
    Set<RejectedRef> getOrphanedRefs();

    /**
     * Retrieves a flag indicating whether synchronization is available for the repository. In order to enable
     * synchronization, if it is not enabled, it must be available. However, even after synchronization has been
     * enabled, it is possible for it to cease to be available.
     *
     * @return {@code true} if synchronization is available for the repository; otherwise, {@code false}
     * @see RefSyncService#isAvailable(Repository) RefSyncService.isAvailable(Repository)
     */
    boolean isAvailable();

    /**
     * Retrieves a flag indicating whether synchronization is enabled for the repository.
     *
     * @return {@code true} if synchronization has been requested; otherwise, {@code false} if upstream changes should
     *         not be automatically propagated
     * @see RefSyncService#isEnabled(Repository) RefSyncService.isEnabled(Repository)
     * @see RefSyncService#setEnabled(Repository, boolean)
     *      RefSyncService.setEnabled(Repository, boolean)
     */
    boolean isEnabled();
}
