package com.atlassian.stash.internal.content;

import com.atlassian.bitbucket.content.*;
import org.junit.Test;

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

import static org.junit.Assert.assertEquals;

public class DiffContentCallbackFilterTest {

    @Test
    public void testFilter() throws IOException {
        MockDiffContentCallbackFilter callback = new MockDiffContentCallbackFilter();
        DiffContentCallbackFilter filter = new DiffContentCallbackFilter(callback, new DiffContentFilter() {
            @Override
            public boolean apply(@Nonnull String line) {
                return line.contains("foo");
            }
        });
        addDiff(filter, "a", 0, DiffSegmentType.ADDED, new String[]{"a", "foo1", "b"}, new String[]{"a", "foo2", "b"});
        addDiff(filter, "b", 2, DiffSegmentType.REMOVED, new String[]{"a", "foo1", "b"}, new String[]{"a", "foo2", "b"});
        assertEquals("\n" +
                "diffStart:a\n" +
                "  hunkStart:0\n" +
                "    segmentStart:ADDED\n" +
                "      line:foo1\n" +
                "    segmentEnd\n" +
                "  hunkEnd\n" +
                "  hunkStart:1\n" +
                "    segmentStart:ADDED\n" +
                "      line:foo2\n" +
                "    segmentEnd\n" +
                "  hunkEnd\n" +
                "diffEnd\n" +
                "diffStart:b\n" +
                "  hunkStart:3\n" +
                "    segmentStart:REMOVED\n" +
                "      line:foo1\n" +
                "    segmentEnd\n" +
                "  hunkEnd\n" +
                "  hunkStart:3\n" +
                "    segmentStart:REMOVED\n" +
                "      line:foo2\n" +
                "    segmentEnd\n" +
                "  hunkEnd\n" +
                "diffEnd\n", callback.b.toString());
    }

    @Test
    public void testFilterComplex() throws IOException {
        MockDiffContentCallbackFilter callback = new MockDiffContentCallbackFilter();
        DiffContentCallbackFilter filter = new DiffContentCallbackFilter(callback, new DiffContentFilter() {
            @Override
            public boolean apply(@Nonnull String line) {
                return line.contains("foo");
            }
        });
        filter.onDiffStart(null, new SimplePath("a"));
        filter.onHunkStart(2, 0, 0, 0);
        filter.onSegmentStart(DiffSegmentType.CONTEXT);
        filter.onSegmentLine("a", null, false);
        filter.onSegmentLine("foo1", null, false);
        filter.onSegmentLine("foo2", null, false);
        filter.onSegmentLine("b", null, false);
        filter.onSegmentEnd(false);
        filter.onSegmentStart(DiffSegmentType.ADDED);
        filter.onSegmentLine("a", null, false);
        filter.onSegmentLine("foo3", null, false);
        filter.onSegmentLine("b", null, false);
        filter.onSegmentLine("foo4", null, false);
        filter.onSegmentEnd(false);
        filter.onSegmentStart(DiffSegmentType.REMOVED);
        filter.onSegmentLine("a", null, false);
        filter.onSegmentLine("foo5", null, false);
        filter.onSegmentLine("b", null, false);
        filter.onSegmentLine("foo6", null, false);
        filter.onSegmentEnd(false);
        filter.onHunkEnd(false);

        filter.onHunkStart(100, 0, 0, 0);
        filter.onSegmentStart(DiffSegmentType.CONTEXT);
        filter.onSegmentLine("a", null, false);
        filter.onSegmentLine("foo7", null, false);
        filter.onSegmentLine("b", null, false);
        filter.onSegmentEnd(false);
        filter.onHunkEnd(false);
        filter.onDiffEnd(false);

        filter.onDiffStart(null, new SimplePath("b"));
        filter.onHunkStart(2, 0, 0, 0);
        filter.onSegmentStart(DiffSegmentType.CONTEXT);
        // No match
        filter.onSegmentLine("z", null, false);
        filter.onSegmentEnd(false);
        filter.onHunkEnd(false);
        filter.onDiffEnd(false);

        assertEquals("\n" +
                "diffStart:a\n" +
                "  hunkStart:3\n" +
                "    segmentStart:CONTEXT\n" +
                "      line:foo1\n" +
                "    segmentEnd\n" +
                "  hunkEnd\n" +
                "  hunkStart:4\n" +
                "    segmentStart:CONTEXT\n" +
                "      line:foo2\n" +
                "    segmentEnd\n" +
                "  hunkEnd\n" +
                "  hunkStart:6\n" +
                "    segmentStart:ADDED\n" +
                "      line:foo3\n" +
                "    segmentEnd\n" +
                "  hunkEnd\n" +
                "  hunkStart:6\n" +
                "    segmentStart:ADDED\n" +
                "      line:foo4\n" +
                "    segmentEnd\n" +
                "  hunkEnd\n" +
                "  hunkStart:7\n" +
                "    segmentStart:REMOVED\n" +
                "      line:foo5\n" +
                "    segmentEnd\n" +
                "  hunkEnd\n" +
                "  hunkStart:9\n" +
                "    segmentStart:REMOVED\n" +
                "      line:foo6\n" +
                "    segmentEnd\n" +
                "  hunkEnd\n" +
                "  hunkStart:101\n" +
                "    segmentStart:CONTEXT\n" +
                "      line:foo7\n" +
                "    segmentEnd\n" +
                "  hunkEnd\n" +
                "diffEnd\n", callback.b.toString());
    }

    private void addDiff(DiffContentCallback callback, String path, int srcLine, DiffSegmentType type,
                         String[] segmentLines, String[] hunkLines) throws IOException {
        callback.onDiffStart(null, new SimplePath(path));
        callback.onHunkStart(srcLine, 0, 0, 0);
        callback.onSegmentStart(type);
        for (String line : segmentLines) {
            callback.onSegmentLine(line, null, false);
        }
        callback.onSegmentEnd(false);
        callback.onHunkEnd(false);

        // Make sure we handle the different combinations of nesting hunks/segments
        for (String line : hunkLines) {
            callback.onHunkStart(srcLine + 1, 0, 0, 0);
            callback.onSegmentStart(type);
            callback.onSegmentLine(line, null, false);
            callback.onSegmentEnd(false);
            callback.onHunkEnd(false);
        }

        callback.onDiffEnd(false);
    }

    private static class MockDiffContentCallbackFilter extends AbstractDiffContentCallback {

        // Use \n here to trick IDEA formatting to make the expected value more readable
        private final StringBuilder b = new StringBuilder("\n");

        @Override
        public void onDiffStart(@Nullable Path src, @Nullable Path dst) throws IOException {
            // Just to shut IDEA up
            if (dst != null) {
                b.append("diffStart:").append(dst.toString()).append("\n");
            }
        }

        @Override
        public void onDiffEnd(boolean truncated) throws IOException {
            b.append("diffEnd\n");
        }

        @Override
        public void onHunkStart(int srcLine, int srcSpan, int dstLine, int dstSpan) throws IOException {
            b.append("  hunkStart:").append(srcLine).append("\n");
        }

        @Override
        public void onHunkEnd(boolean truncated) throws IOException {
            b.append("  hunkEnd\n");
        }

        @Override
        public void onSegmentStart(@Nonnull DiffSegmentType type) throws IOException {
            b.append("    segmentStart:").append(type).append("\n");
        }

        @Override
        public void onSegmentEnd(boolean truncated) throws IOException {
            b.append("    segmentEnd\n");
        }

        @Override
        public void onSegmentLine(@Nonnull String line, @Nullable ConflictMarker marker, boolean truncated) throws IOException {
            b.append("      line:").append(line).append("\n");
        }
    }
}
