package com.atlassian.bitbucket.mesh.repository;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import java.nio.file.Path;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.Objects.requireNonNull;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.apache.commons.lang3.StringUtils.leftPad;

/**
 * A repository that is fully managed by the application.
 * <p>
 * {@code ManagedRepository} provides a few different types of identifiers:
 * <ul>
 *     <li>{@link #getId()} - the fully qualified ID, of the form {@code p/0123/h/abcdef123783297374924/r/1237910}.</li>
 *     <li>{@link #getHierarchyId()} - the ID of the hierarchy this repository is part of;
 *         {@code abcdef123783297374924} in the example above.</li>
 *     <li>{@link #getLocalId()} - the ID of the repository relative to the hierarchy; {@code 1237910} in the example
 *          above.</li>
 * </ul>
 */
public interface ManagedRepository extends Repository {

    /**
     * Repository IDs are hierarchical resource identifiers that use
     * <ul>
     *   <li>{@code p} to identify the base 16 partition number</li>
     *   <li>{@code h} to identify the repository hierarchy</li>
     *   <li>{@code r} to identify the repository within the hierarchy</li>
     *   <li>{@code q} (as a query parameter) to identify a quarantine environment of the repository (optional)</li>
     * </ul>
     * As an example, {@code p/004b/h/3a1561bdf8c5e349ae03/r/c2e81a18b4} corresponds to repository {@code c2e81a18b4}
     * in hierarchy {@code 3a1561bdf8c5e349ae03}, which is located on partition 75 ({@code 004b}).
     */
    // Partition pattern copied from com.atlassian.bitbucket.mesh.partition.Partition in core
    Pattern PATTERN_PARTITION = Pattern.compile("[0-9a-f]{4,15}");
    Pattern PATTERN_REPOSITORY_ID = Pattern.compile(
            "p/(?<partition>" + PATTERN_PARTITION.pattern() + ")" +
                    "/h/(?<hierarchy>[a-z0-9\\-]{20,})" + //include - to support UUIDs
                    "/r/(?<repo>[a-z0-9\\-]+)" + // include - to support UUIDs
                    "(?:\\?.*)?"); //Allow query parameters, but no further path entries

    @Nonnull
    static String formatId(int partition, @Nonnull String hierarchyId, @Nonnull String localId) {
        return formatId(Integer.toString(partition, 16), hierarchyId, localId);
    }

    @Nonnull
    static String formatId(@Nonnull String partition, @Nonnull String hierarchyId, @Nonnull String localId) {
        return "p/" + leftPad(partition, 4, '0') + "/h/" + hierarchyId + "/r/" + localId;
    }

    /**
     * @return the marker file used by garbage collection to signal that this repository needs to be vacuumed
     *         before GC can safely run in {@link #getSharedObjects()}
     */
    @Nonnull
    Path getDirtyMarker();

    /**
     * @return the repository hierarchy ID
     */
    @Nonnull
    String getHierarchyId();

    /**
     * @return ID that identifies the repository relative to the hierarchy
     */
    @Nonnull
    String getLocalId();

    /**
     * @return the ID of the partition the repository is stored on
     */
    @Nonnull
    String getPartition();

    /**
     * @return the repository where shared objects for this hierarchy's repositories are stored
     */
    @Nonnull
    SharedObjectsRepository getSharedObjects();

    /**
     * @return the repository version
     */
    long getVersion();

    /**
     * @return {@code true} if the {@link #getDirtyMarker() dirty marker} exists for this repository
     */
    boolean isDirty();

    class Id {

        private final String hierarchyId;
        private final String localId;
        private final String partition;

        public Id(@Nonnull String partition, @Nonnull String hierarchyId, @Nonnull String localId) {
            this.hierarchyId = requireNonNull(hierarchyId, "hierarchyId");
            this.localId = requireNonNull(localId, "localId");
            this.partition = requireNonNull(partition, "partition");
        }

        public static boolean matches(@Nullable String repositoryId) {
            return repositoryId != null && PATTERN_REPOSITORY_ID.matcher(repositoryId).matches();
        }

        @Nonnull
        public static Optional<Id> parseId(@Nonnull String repositoryId) {
            Matcher matcher = PATTERN_REPOSITORY_ID.matcher(repositoryId);
            if (matcher.matches()) {
                return of(new Id(matcher.group("partition"), matcher.group("hierarchy"), matcher.group("repo")));
            }
            return empty();
        }

        @Nonnull
        public String getHierarchyId() {
            return hierarchyId;
        }

        @Nonnull
        public String getLocalId() {
            return localId;
        }

        @Nonnull
        public String getPartition() {
            return partition;
        }
    }
}
