package com.atlassian.stash.internal.rest.content;

import com.atlassian.bitbucket.rest.content.RestChange;
import com.atlassian.bitbucket.rest.util.StatefulJsonWriter;
import com.atlassian.bitbucket.content.AbstractChangeCallback;
import com.atlassian.bitbucket.content.Change;
import com.atlassian.bitbucket.content.ChangeContext;
import com.atlassian.bitbucket.content.ChangeSummary;
import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.bitbucket.util.PageRequest;

import javax.annotation.Nonnull;
import java.io.IOException;

/**
 * @since 1.3
 */
public class JsonChangeCallback extends AbstractChangeCallback {

    private final NavBuilder.Commit commitNav;
    private final PageRequest pageRequest;
    private final StatefulJsonWriter writer;

    private boolean ended;
    private int received;
    private int written;

    public JsonChangeCallback(StatefulJsonWriter writer, PageRequest pageRequest) {
        this(writer, pageRequest, null);
    }

    public JsonChangeCallback(StatefulJsonWriter writer, PageRequest pageRequest, NavBuilder.Commit commitNav) {
        this.commitNav = commitNav;
        this.pageRequest = pageRequest;
        this.writer = writer;
    }

    @Override
    public boolean onChange(@Nonnull Change change) throws IOException {
        if (received++ < pageRequest.getStart()) {
            //This change is before the beginning of the requested page, so it should not be written
            return true;
        }

        if (written == pageRequest.getLimit()) {
            //We've already written as many changes as will fit on the page. This final change tells us that there is
            //another page of changes, which is important for detecting when we truncate the diff tree.
            endPage(false);

            return false;
        }
        writer.value(new RestChange(change, commitNav));
        ++written;

        return true;
    }

    @Override
    public void onEnd(@Nonnull ChangeSummary summary) throws IOException {
        endPage(!summary.isTruncated());
    }

    @Override
    public void onStart(@Nonnull ChangeContext context) throws IOException {
        writer.beginObject()
                .name("fromHash").value(context.getFromHash())
                .name("toHash").value(context.getToHash())
                .name("values").beginArray();
    }

    private void endPage(boolean lastPage) throws IOException {
        if (ended) {
            //May be called twice, once from onChange and once from onEnd.
            return;
        }
        ended = true;

        writer.endArray() //End the "values" array
                .name("size").value(written)
                .name("isLastPage").value(lastPage)
                .name("start").value(pageRequest.getStart())
                .name("limit").value(pageRequest.getLimit())
                .name("nextPageStart");
        //Either all results have been written (lastPage is set), or page.max.changes has been exceeded, meaning there
        //are more changes to return (lastPage is not set) but limit is higher than the number of changes written.
        if (lastPage || pageRequest.getLimit() > written) {
            writer.nullValue();
        } else {
            writer.value(pageRequest.getStart() + pageRequest.getLimit());
        }
        writer.endObject();
    }
}
