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

import com.atlassian.bitbucket.content.*;
import com.atlassian.bitbucket.pull.PullRequestDiffCommentAnchor;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Ordering;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;

/**
 * Information about the place in the diff where comments are anchored to.
 */
final class DiffAnchorInfo implements Comparable<DiffAnchorInfo> {

    private final int sourceLine;
    private final int destinationLine;
    private final DiffSegmentType lineType;
    private final String text;

    @VisibleForTesting
    DiffAnchorInfo(int sourceLine, int destinationLine, @Nullable DiffSegmentType lineType, @Nonnull String text) {
        this.sourceLine = sourceLine;
        this.destinationLine = destinationLine;
        this.lineType = lineType;
        this.text = text;
    }

    /**
     * @param anchor the comment's anchor
     * @param diff the file diff the comment is anchored to
     * @return info about the anchor of a comment, with additional data from the diff (if available)
     */
    static DiffAnchorInfo create(PullRequestDiffCommentAnchor anchor, Diff diff) {
        int lineNumber = anchor.getLine();

        DiffSegmentType lineType = anchor.getLineType();
        if (diff != null && lineType != null) {
            DiffAnchorInfo inDiff = findAnchorInDiff(diff, lineType, lineNumber);
            if (inDiff != null) {
                return inDiff;
            }
        }

        // Didn't find line in diff, fall back to what we know from anchor
        return new DiffAnchorInfo(lineNumber, lineNumber, lineType, "");
    }

    public int getSourceLine() {
        return sourceLine;
    }

    public int getDestinationLine() {
        return destinationLine;
    }

    /**
     * @return {@code true} if this is a comment on a line, {@code false} if it is a file comment
     */
    public boolean isLineComment() {
        return lineType != null;
    }

    /**
     * @return the type of the line or null, in case of a file comment; see {@link #isLineComment()}
     */
    @Nullable
    public DiffSegmentType getLineType() {
        return lineType;
    }

    @Nonnull
    public String getText() {
        return text;
    }

    @Override
    public int compareTo(DiffAnchorInfo o) {
        return ComparisonChain.start()
                .compare(sourceLine, o.sourceLine)
                .compare(destinationLine, o.destinationLine)
                .compare(lineType, o.lineType, Ordering.natural().nullsFirst())
                .compare(text, o.text)
                .result();
    }

    @Override
    public int hashCode() {
        return Objects.hash(sourceLine, destinationLine, lineType, text);
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof DiffAnchorInfo)) {
            return false;
        }
        DiffAnchorInfo that = (DiffAnchorInfo) obj;
        return this.sourceLine == that.sourceLine &&
                this.destinationLine == that.destinationLine &&
                Objects.equals(this.lineType, that.lineType) &&
                Objects.equals(this.text, that.text);
    }

    @Nullable
    private static DiffAnchorInfo findAnchorInDiff(Diff diff, DiffSegmentType lineType, int lineNumber) {
        for (DiffHunk hunk : diff.getHunks()) {
            for (DiffSegment segment : hunk.getSegments()) {
                if (segment.getType() == lineType) {
                    for (DiffLine line : segment.getLines()) {
                        int checkLineNumber = lineType.isInSource() ? line.getSource() : line.getDestination();
                        if (lineNumber == checkLineNumber) {
                            return new DiffAnchorInfo(line.getSource(), line.getDestination(), lineType, line.getLine());
                        }
                    }
                }
            }
        }
        return null;
    }
}
