package com.atlassian.stash.internal.scm.git.command.revlist;

import com.atlassian.bitbucket.commit.Commit;
import com.atlassian.bitbucket.commit.MinimalCommit;
import com.atlassian.bitbucket.commit.SimpleCommit;
import com.atlassian.bitbucket.content.NoSuchPathException;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.io.LineReader;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.git.command.CommitReaderSettings;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Iterables;

import java.io.IOException;
import java.util.*;

/**
 * An implementation of {@link AbstractCommitRevListOutputHandler} which emits a single {@link Commit}.
 * <p>
 * This output handler assumes that the output has been limited by the command itself--it is not safe to use without
 * the {@code -1} flag on {@code git rev-list}.
 */
public class SingleCommitRevListOutputHandler extends AbstractCommitRevListOutputHandler<Commit> {

    private final Map<String, Commit> commitsById;
    private final String commitId;
    private final I18nService i18nService;
    private final String path;

    public SingleCommitRevListOutputHandler(I18nService i18nService, Repository repository,
                                            String commitId, String path) {
        this (i18nService, repository, commitId, path, true, false);
    }

    public SingleCommitRevListOutputHandler(I18nService i18nService, Repository repository, String commitId,
                                            String path, boolean withMessages, boolean withPaths) {
        this(i18nService, repository, commitId, path, withMessages, withPaths, CommitReaderSettings.UNLIMITED_MESSAGE_LENGTH);
    }

    public SingleCommitRevListOutputHandler(I18nService i18nService, Repository repository, String commitId,
                                            String path, boolean withMessages, boolean withPaths, int maxMessageSize) {
        super(repository, withMessages, withPaths, maxMessageSize, path);

        this.commitId = commitId;
        this.i18nService = i18nService;
        this.path = path;

        commitsById = new LinkedHashMap<>();
    }

    @Override
    public Commit getOutput() {
        //If the commit was requested by ID, which is all the system itself ever does, we can look it up
        //in the map directly
        Commit commit = commitsById.get(commitId);
        if (commit == null) {
            //If it was requested by a partial ID or by name, hope for the best. There are some other
            //approaches we could take, but, given this should all go away in 4.0, we're going simple
            commit = Iterables.getFirst(commitsById.values(), null);
            if (commit == null) {
                throw new NoSuchPathException(
                        i18nService.createKeyedMessage("bitbucket.service.repository.pathnotfound.atrevision", path, commitId),
                        path, commitId);
            }
        }

        return replaceParents(commit);
    }

    @Override
    protected void processReader(LineReader reader) throws IOException {
        //This implementation always reads all of the commits from the output. While a "single commit" is
        //expected, parent information may also be provided. However, because reasons, commits may be out
        //of order, so trying to decide how many parents to read based on the number of parents the first
        //commit we read may result in a partial read (which triggers the git command to fail)
        Commit commit;
        while ((commit = readCommit(reader)) != null) {
            commitsById.put(commit.getId(), commit);
        }
    }

    private Commit replaceParents(Commit commit) {
        Collection<MinimalCommit> minimalParents = commit.getParents();

        //The parents are not necessarily returned in the order they're listed on the commit. Since their order
        //is significant, we rebuild the list from the map using the order of the minimal parents
        List<MinimalCommit> parents = new ArrayList<>(minimalParents.size());
        for (MinimalCommit minimalParent : minimalParents) {
            //Look up the full parent by its ID. If there's no match (there should be, really), leave it minimal
            parents.add(MoreObjects.firstNonNull(commitsById.get(minimalParent.getId()), minimalParent));
        }

        return new SimpleCommit.Builder(commit)
                .clearParents()
                .parents(parents)
                .build();
    }
}
