package com.atlassian.bitbucket.internal.branch.model;

import com.atlassian.bitbucket.branch.model.BranchClassifier;
import com.atlassian.bitbucket.branch.model.BranchType;
import com.atlassian.bitbucket.repository.*;
import com.atlassian.bitbucket.util.*;
import com.google.common.collect.Iterables;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.Predicate;

import static com.atlassian.bitbucket.internal.branch.model.InternalBranchTypes.asInternalBranchType;
import static com.atlassian.bitbucket.util.PageUtils.createEmptyPage;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * A simple {@link BranchClassifier} implementation that matches branches by verifying whether their display ID starts
 * with a certain prefix.
 *
 * @since 2.8
 */
public class PrefixBranchClassifier implements BranchClassifier {

    private static final int BRANCHES_PER_PAGE = 1000;

    private final RefService refService;
    private final Repository repository;
    private final Iterable<InternalBranchType> types;

    public PrefixBranchClassifier(@Nonnull RefService refService,
                                  @Nonnull Repository repository,
                                  @Nonnull Iterable<InternalBranchType> types) {
        this.refService = checkNotNull(refService, "refService");
        this.repository = checkNotNull(repository, "repository");
        this.types = checkNotNull(types, "types");
    }

    @Nullable
    @Override
    public InternalBranchType getType(@Nonnull final Branch branch) {
        checkNotNull(branch, "branch");
        return Iterables.find(types, input -> branch.getDisplayId().startsWith(input.getPrefix()), null);
    }

    @Nonnull
    @Override
    public Iterable<Branch> getAllBranchesByType(@Nonnull final BranchType branchType) {
        return new PagedIterable<>(new PageProvider<Branch>() {
            @Override
            public Page<Branch> get(PageRequest request) {
                return getBranchesByType(branchType, request);
            }
        }, BRANCHES_PER_PAGE);
    }

    @Nonnull
    @Override
    public Page<Branch> getBranchesByType(@Nonnull BranchType branchType, @Nonnull PageRequest pageRequest) {
        checkNotNull(branchType, "branchType");
        checkNotNull(pageRequest, "pageRequest");
        if (!Iterables.contains(types, branchType)) {
            return createEmptyPage(pageRequest);
        }
        return getPageForType(branchType, pageRequest);
    }

    private Page<Branch> getPageForType(BranchType branchType, PageRequest pageRequest) {
        final InternalBranchType internalBranchType = asInternalBranchType(branchType);
        PageProvider<Branch> branchesProvider = new PageProvider<Branch>() {
            @Override
            public Page<Branch> get(PageRequest request) {
                return refService.getBranches(new RepositoryBranchesRequest.Builder(repository)
                        .filterText(internalBranchType.getPrefix())
                        .order(RefOrder.ALPHABETICAL)
                        .build(), request);
            }
        };
        Predicate<MinimalRef> startsWithPrefix = ref -> StringUtils.startsWith(ref.getDisplayId(), internalBranchType.getPrefix());
        return PageUtils.filterPages(branchesProvider, startsWithPrefix, pageRequest);
    }
}
