package com.atlassian.bitbucket.util;

import com.atlassian.bitbucket.scm.CommandResult;

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

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Extends {@link AbstractSummary} and adds additional properties for summarizing a page of output.
 */
public abstract class AbstractPagedSummary extends AbstractSummary {

    private final boolean lastPage;
    private final PageRequest pageRequest;
    private final int size;

    protected AbstractPagedSummary(@Nonnull AbstractPagedBuilder<?, ?> builder) {
        super(builder);

        lastPage = builder.lastPage;
        pageRequest = builder.buildPageRequest();
        size = builder.size;
    }

    /**
     * If this is not the {@link #isLastPage() last page}, retrieves the {@link PageRequest} which can be used to
     * request the next page. If this is the last page, returns {@code null}.
     *
     * @return a {@link PageRequest} to request the next page, or {@code null} if this is the {@link #isLastPage()
     *         last page}
     */
    @Nullable
    public PageRequest getNextPageRequest() {
        if (isLastPage()) {
            return null;
        }
        PageRequest pageRequest = getPageRequest();

        return new PageRequestImpl(pageRequest.getStart() + pageRequest.getLimit(), pageRequest.getLimit());
    }

    /**
     * Retrieves the {@link PageRequest} that was used to retrieve this page of results.
     *
     * @return the {@link PageRequest} for the current page
     */
    @Nonnull
    public PageRequest getPageRequest() {
        return pageRequest;
    }

    /**
     * Retrieves the number of results that were retrieved. This value will always be less than or equal to the
     * {@link PageRequest#getLimit() limit} of the {@link #getPageRequest() current page request}.
     *
     * @return the size of the current page
     */
    public int getSize() {
        return size;
    }

    /**
     * Retrieves a flag indicating whether this page is the last page of results.
     *
     * @return {@code true} if this is the final page of results; otherwise, {@code false} if there is a
     *         {@link #getNextPageRequest() next page}
     */
    public boolean isLastPage() {
        return lastPage;
    }

    public abstract static class AbstractPagedBuilder<B extends AbstractPagedBuilder<B, S>, S extends AbstractPagedSummary>
            extends AbstractBuilder<B, S> {

        private boolean lastPage;
        private PageRequest pageRequest;
        private int size;

        /**
         * Creates and initializes a new builder using details from the provided {@link AbstractSummary summary}. If
         * the provided summary is {@link AbstractPagedSummary paged}, those details will also be copied over.
         *
         * @param summary the summary to initialize this builder from
         */
        protected AbstractPagedBuilder(@Nonnull AbstractSummary summary) {
            super(summary);

            if (summary instanceof AbstractPagedSummary) {
                AbstractPagedSummary pageSummary = (AbstractPagedSummary) summary;
                lastPage = pageSummary.isLastPage();
                pageRequest = pageSummary.getPageRequest();
                size = pageSummary.getSize();
            }
        }

        /**
         * Creates a new builder with the provided {@link CommandResult result}.
         *
         * @param result the operation result for the summary
         */
        protected AbstractPagedBuilder(@Nonnull CommandResult result) {
            super(result);
        }

        @Nonnull
        public B lastPage(boolean value) {
            lastPage = value;

            return self();
        }

        @Nonnull
        public B pageRequest(@Nonnull PageRequest value) {
            pageRequest = checkNotNull(value, "pageRequest");

            return self();
        }

        @Nonnull
        public B size(int value) {
            size = value;

            return self();
        }

        @Nonnull
        private PageRequest buildPageRequest() {
            return pageRequest == null ? new PageRequestImpl(0, 25) : pageRequest;
        }
    }
}
