package com.atlassian.webdriver.bitbucket.element;

import com.atlassian.pageobjects.elements.WebDriverLocatable;
import com.atlassian.pageobjects.elements.query.Poller;
import com.atlassian.pageobjects.elements.timeout.TimeoutType;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.*;
import org.apache.commons.lang3.StringUtils;
import org.openqa.selenium.By;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.junit.Assert.fail;

public class SideBySideDiffFileContent extends DiffFileContent {

    public SideBySideDiffFileContent(By locator, TimeoutType timeoutType) {
        super(locator, timeoutType);
    }

    @Override
    public void waitUntilFileContentLoaded() {
        Poller.waitUntilTrue(getContentView().timed().hasClass("side-by-side-diff"));
        super.waitUntilFileContentLoaded();
    }

    public static class Line extends DiffFileContent.Line {

        public Line(By locator, TimeoutType timeoutType) {
            super(locator, timeoutType);
        }

        public Line(WebDriverLocatable locatable, TimeoutType timeoutType) {
            super(locatable, timeoutType);
        }

        public String getFileType() {
            return this.find(By.className("line-number-marker")).getAttribute("data-file-type");
        }
    }

    private List<Line> getLines(final String fileType) {
        return waitForSourceContainer(new Supplier<List<Line>>() {
            @Override
            public List<Line> get() {
                return findAll(By.cssSelector(".side-by-side-diff-editor-" + fileType + " .line"), Line.class);
            }
        });
    }

    public List<Line> getFromLines() {
        return getLines("from");
    }

    public List<Line> getToLines() {
        return getLines("to");
    }

    public Lineset getLines(int fromStart, int fromEnd, int toStart, int toEnd) {
        return new Lineset(this, fromStart, fromEnd, toStart, toEnd);
    }

    public static class Lineset {
        private SideBySideDiffFileContent fileContent;
        private int fromStart;
        private int fromEnd;
        private int toStart;
        private int toEnd;

        private Lineset(SideBySideDiffFileContent fileContent, int fromStart, int fromEnd, int toStart, int toEnd) {
            this.fileContent = fileContent;
            this.fromStart = fromStart;
            this.fromEnd = fromEnd;
            this.toStart = toStart;
            this.toEnd = toEnd;
        }

        public void assertHasFocus() {
            if (!hasFocus(fileContent, "from", fromStart, fromEnd) ||
                !hasFocus(fileContent, "to", toStart, toEnd)) {
                // We only try to identify the bad eggs if we know there are any. It's too slow to get the attributes for each line aggressively.
                String fromRange = fromStart > -1 ? fromStart + "," + fromEnd : ",";
                String toRange = toStart > -1 ? toStart + "," + toEnd : ",";
                try {
                    String extraFromFocus = StringUtils.join(getExtraFocus(fileContent, "from", fromStart, fromEnd), ",");
                    String extraToFocus = StringUtils.join(getExtraFocus(fileContent, "to", toStart, toEnd), ",");
                    String missingFromFocus = fromStart > -1 ? StringUtils.join(getMissingFocus(fileContent, "from", fromStart, fromEnd), ",") : "";
                    String missingToFocus = toStart > -1 ? StringUtils.join(getMissingFocus(fileContent, "to", toStart, toEnd), ",") : "";
                    fail("Lineset FROM[" + fromRange + "]:TO[" + toRange + "] has focus. " +
                            "Extra focus: FROM(" + extraFromFocus + "):TO(" + extraToFocus + "); " +
                            "Missing focus: FROM(" + missingFromFocus + "):TO(" + missingToFocus + ")");
                } catch (Exception e) {
                    fail("Lineset FROM[" + fromRange + "]:TO[" + toRange + "] has focus. " +
                            "Actual from count: " + fileContent.findAll(By.cssSelector(".side-by-side-diff-editor-from .last-focus")).size() + " " +
                            "Actual to count: " + fileContent.findAll(By.cssSelector(".side-by-side-diff-editor-to .last-focus")).size() + " ");
                }
            }
        }

        private static Iterable<Integer> getExtraFocus(SideBySideDiffFileContent fileContent, final String side, final int start, final int end) {
            return Iterables.filter(getFocusedLineNumbers(fileContent, side), new Predicate<Integer>() {
                @Override
                public boolean apply(Integer input) {
                    return input < start || input > end;
                }
            });
        }

        private static Iterable<Integer> getFocusedLineNumbers(SideBySideDiffFileContent fileContent, String side) {
            List<Line> all = fileContent.findAll(By.cssSelector(".side-by-side-diff-editor-" + side + " .last-focus"), Line.class);
            return Iterables.transform(all, new Function<Line, Integer>() {
                @Override
                public Integer apply(Line input) {
                    return input.getLine();
                }
            });
        }

        private static Iterable<Integer> getMissingFocus(SideBySideDiffFileContent fileContent, final String side, int start, int end) {
            HashSet<Integer> actualFocusedNumbers = Sets.newHashSet(getFocusedLineNumbers(fileContent, side));
            Set<Integer> expectedFocusedNumbers = ContiguousSet.create(Range.closed(start, end), DiscreteDomain.integers());
            Iterables.removeAll(expectedFocusedNumbers, actualFocusedNumbers);
            return expectedFocusedNumbers;
        }

        private static boolean hasFocus(SideBySideDiffFileContent fileContent, final String side, int start, int end) {
            int expectedFocus = start > -1 ? 1 + end - start : 0;
            int focused = fileContent.findAll(By.cssSelector(".side-by-side-diff-editor-" + side + " .last-focus")).size();

            if (focused != expectedFocus) {
                return false;
            }
            if (start > -1) {
                String selector = StringUtils.join(Iterables.transform(ContiguousSet.create(Range.closed(start, end), DiscreteDomain.integers()), new Function<Integer, String>() {
                    @Override
                    public String apply(Integer i) {
                        return ".side-by-side-diff-editor-" + side + " .last-focus .line-number-marker[data-line-number=\"" + i + "\"]";
                    }
                }), ',');
                int matchingLines = fileContent.findAll(By.cssSelector(selector)).size();
                if (matchingLines != expectedFocus) {
                    return false;
                }
            }
            return true;
        }
    }
}
