package com.atlassian.stash.internal.notification.pull.activity;

import com.atlassian.bitbucket.comment.DiffCommentAnchor;
import com.atlassian.bitbucket.content.DiffSegmentType;
import com.atlassian.bitbucket.pull.PullRequestCommentAnchorDiffType;
import com.atlassian.bitbucket.pull.PullRequestDiffCommentAnchor;
import com.atlassian.stash.internal.notification.util.AnchorUtils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;

import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

import static com.atlassian.bitbucket.util.MoreCollectors.toImmutableList;
import static java.util.Objects.requireNonNull;

/**
 * Represents a single file with all the comments that happened in the file (and context).
 */
public class BatchEmailActivityFile extends AbstractBatchEmailActivity {

    private final Map<DiffAnchorInfo, CommentThreadCollection> comments = Maps.newTreeMap();

    private final String path;
    private final String name;
    private final String fromHash;
    private final String toHash;
    private final PullRequestCommentAnchorDiffType diffType;

    public BatchEmailActivityFile(PullRequestDiffCommentAnchor anchor) {
        path = AnchorUtils.getFilePath(anchor);
        name = AnchorUtils.getFileName(anchor);
        fromHash = anchor.getFromHash();
        toHash = anchor.getToHash();
        diffType = anchor.getDiffType();
    }

    /**
     * Gets all the comment threads anchored on a particular line of a file. The returned object contains all these
     * comment threads and is used for adding new ones.
     */
    @Nonnull
    public CommentThreadCollection getCommentThreadCollection(@Nonnull DiffAnchorInfo anchor) {
        return comments.computeIfAbsent(requireNonNull(anchor, "anchor"), a -> new CommentThreadCollection());
    }

    @Nonnull
    public Date getDate() {
        return getDateOfOldestNonContextComment(comments.values());
    }

    @Nonnull
    public BatchEmailActivityType getType() {
        return BatchEmailActivityType.FILE;
    }

    @Override
    void populateData(ImmutableMap.Builder<String, Object> dataBuilder) {
        dataBuilder.put("path", path)
                .put("name", name)
                .put("fromHash", fromHash)
                .put("toHash", toHash)
                .put("diffType", diffType)
                .put("lines", getLines());
    }

    private static Date getDateOfOldestNonContextComment(Iterable<CommentThreadCollection> threadCollections) {
        Ordering<CommentThread> ordering = Ordering.natural().nullsLast();
        CommentThread oldest = null;
        for (CommentThreadCollection threadCollection : threadCollections) {
            for (CommentThread thread : threadCollection.getThreads()) {
                CommentThread candidate = thread.isContext() ? ordering.min(thread.getReplies()) : thread;
                oldest = ordering.min(oldest, candidate);
            }
        }
        if (oldest == null) {
            throw new IllegalStateException("File without any non-context comments");
        }
        return oldest.getDate();
    }

    /**
     * @return a list of {@link BatchEmailActivityFile.FileLineData} objects representing for the line of a file
     */
    private List<FileLineData> getLines() {
        return comments.entrySet().stream()
                .map(entry -> new FileLineData(entry.getKey(), entry.getValue()))
                .collect(toImmutableList());
    }

    // required to be public for Soy templates
    public static class FileLineData {

        private final DiffAnchorInfo anchorInfo;
        private final CommentThreadCollection commentsCollection;

        public FileLineData(DiffAnchorInfo anchorInfo, CommentThreadCollection commentsCollection) {
            this.anchorInfo = anchorInfo;
            this.commentsCollection = commentsCollection;
        }

        public int getLine() {
            if (anchorInfo.isLineComment()) {
                DiffSegmentType type = anchorInfo.getLineType();
                // Prefer source line number for display (e.g. for context lines, this is same as in side-by-side diff)
                return type.isInSource() ? anchorInfo.getSourceLine() : anchorInfo.getDestinationLine();
            } else {
                return DiffCommentAnchor.NO_LINE;
            }
        }

        public DiffSegmentType getType() {
            return anchorInfo.getLineType();
        }

        public String getText() {
            return anchorInfo.getText();
        }

        @SuppressWarnings("unused") // used in templates
        @Nonnull
        public Collection<CommentThread> getThreads() {
            return commentsCollection.getThreads();
        }

    }

}
