package com.intland.codebeamer.functionaltest.helper;


import com.codeborne.selenide.*;
import com.codeborne.selenide.ex.ElementNotFound;
import com.codeborne.selenide.ex.TextsMismatch;
import com.codeborne.selenide.ex.UIAssertionError;
import com.codeborne.selenide.impl.Html;
import com.codeborne.selenide.impl.WebElementsCollection;
import com.google.common.base.Function;
import com.intland.codebeamer.functionaltest.page.LoginPage;
import com.intland.codebeamer.functionaltest.page.mystart.MyStartPage;
import org.apache.commons.io.FileUtils;
import org.frontendtest.components.ImageComparison;
import org.openqa.selenium.*;
import org.openqa.selenium.Point;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.testng.internal.Nullable;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.codeborne.selenide.Condition.visible;
import static com.codeborne.selenide.Selenide.*;
import static com.codeborne.selenide.WebDriverRunner.getWebDriver;
import static com.codeborne.selenide.WebDriverRunner.source;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;

public class Helper {

    private static final Logger log = Logger.getLogger(Helper.class.getName());

    public final static String username = "bond";

    public final static String password = "007";

    public static MyStartPage loginWithUser(String username, String password) {
//        FluentWait<WebDriver> fluentWait = new FluentWait<>(WebDriverRunner.getWebDriver())
//                .withTimeout(1, TimeUnit.MINUTES)
//                .pollingEvery(200, TimeUnit.MILLISECONDS)
//                .ignoring(NoSuchElementException.class);
//        fluentWait.until(ExpectedConditions.urlContains("data:,"));
        getWebDriver().manage().window().maximize();
        LoginPage loginPage = open("/login.spr", LoginPage.class);
        loginPage.setUsername(username);
        loginPage.setPassword(password);
        MyStartPage myStartPage = loginPage.clickOnLogin();
        myStartPage.getMyStartMenu().shouldBe(visible);
        return myStartPage;
    }

    public static MyStartPage loginWithSystemAdmin() {
        return loginWithUser(username, password);
    }

    public static void Wait(int millis) {
        try {
            Thread.currentThread().sleep(millis);
        } catch (Exception e) {
        }
    }

    public static void ClearCache() {
        getWebDriver().manage().deleteAllCookies();
    }

    /**
     * @param tree tree container element
     */
    public static void openAllTreeItems(SelenideElement tree) {
        ElementsCollection closedTreeItems = tree.findAll(".jstree-closed");
        sleep(1000);
        if (closedTreeItems.isEmpty()) return;
        for (SelenideElement closedItem : closedTreeItems) {
            if (closedItem.getAttribute("class").contains("jstree-closed")) {
                // closedItem.find(By.tagName("i")).click();
                closedItem.contextClick();
                $(By.xpath(".//a[text()='Expand Node']")).click();
            }
        }
        //openAllTreeItems(tree);
    }

    public static void openAllRootNodes(SelenideElement treePane) {
        final int[] i = {1};
        Selenide.Wait().withTimeout(30, TimeUnit.SECONDS)
                .pollingEvery(3, TimeUnit.SECONDS)
                .ignoring(NoSuchElementException.class)
                .ignoring(UIAssertionError.class)
                .until((Function<WebDriver, Boolean>) driver -> {
                    System.out.println("try " + i[0]++);
                    ElementsCollection closedTreeItems = treePane.$$(".jstree-closed");
                    if (closedTreeItems.isEmpty()) {
                        System.out.println("No closed at first");
                        return true;
                    }
                    ElementsCollection rootNodes = treePane.$$(By.xpath("./ul[@class='jstree-container-ul jstree-children']/*/a"));
                    for (SelenideElement rootNode : rootNodes) {
                        rootNode.click();
                        //rootNode.contextClick();
                        int x = rootNode.getSize().getWidth() / 3;
                        int y = rootNode.getSize().getHeight() / 2;
                        actions().moveToElement(rootNode, x, y).contextClick().perform();
                        //sleep(1000);
                        ElementsCollection e = $$(By.xpath(".//a[text()='Expand Node']"));
                        if (!e.isEmpty()) {
                            System.out.println("Clicked on expand");
                            e.first().click();
                            sleep(2000); //wait for nodes to open
                        }
                    }
                    if (treePane.$$(".jstree-closed").isEmpty()) {//check if any closed nodes have remained
                        System.out.println("No closed at second");
                        return true;
                    } else return false;
                });
    }

    public static void clearClipboard() {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(new Transferable() {
            public DataFlavor[] getTransferDataFlavors() {
                return new DataFlavor[0];
            }

            public boolean isDataFlavorSupported(DataFlavor flavor) {
                return false;
            }

            public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
                throw new UnsupportedFlavorException(flavor);
            }
        }, null);
    }

    public static String getClipboardContents() {
        String result = "";
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        // odd: the Object param of getContents is not currently used
        Transferable contents = clipboard.getContents(null);
        boolean hasTransferableText = (contents != null)
                && contents.isDataFlavorSupported(DataFlavor.stringFlavor);
        if (hasTransferableText) {
            try {
                result = (String) contents
                        .getTransferData(DataFlavor.stringFlavor);
            } catch (UnsupportedFlavorException | IOException ex) {
                System.out.println(ex);
                ex.printStackTrace();
            }
        }
        return result;
    }

    public static Boolean doesPageSourceContain(String textToContain) {
        return Selenide.Wait().until(new Function<WebDriver, Boolean>() {
            @Override
            public Boolean apply(@Nullable WebDriver input) {
                return source().contains(textToContain);
            }
        });
    }

    public static Condition css(final String propName, final String propValue) {
        return new Condition("css") {
            @Override
            public boolean apply(WebElement element) {
                return propValue.equalsIgnoreCase(element.getCssValue(propName));
            }

            @Override
            public String actualValue(WebElement element) {
                return element.getCssValue(propName);
            }
        };
    }

    public static CollectionCondition matchesText(String... regexs) {
        return matchText(regexs);
    }


    public static CollectionCondition matchText(String... regexs) {
        return new MatchText(regexs);
    }

    public static CollectionCondition matchTitle(String... titles) {
        return new MatchTitle(titles);
    }

    /**
     * Checks whether any element of the collection contains the given text
     * @param text
     * @return
     */
    public static CollectionCondition containsText(String text) {
        return new ContainsText(text);
    }

    public static void waitForReload() {
        Selenide.Wait().until(ExpectedConditions.stalenessOf($("html")));
        Selenide.Wait().until((ExpectedCondition<Boolean>) wd -> executeJavaScript("return document.readyState").equals("complete"));
    }


    public static class MatchText extends CollectionCondition {

        protected final List<String> regexs;

        public MatchText(String... regexs) {
            this(asList(regexs));
        }

        public MatchText(List<String> regexs) {
            if (regexs.isEmpty()) {
                throw new IllegalArgumentException("No regexs given");
            }
            this.regexs = unmodifiableList(regexs);
        }

        @Override
        public void fail(WebElementsCollection collection, List<WebElement> elements, Exception lastError, long timeoutMs) {
            if (elements == null || elements.isEmpty()) {
                ElementNotFound elementNotFound = new ElementNotFound(collection, regexs, lastError);
                elementNotFound.timeoutMs = timeoutMs;
                throw elementNotFound;
            } else {
                throw new TextsMismatch(collection, ElementsCollection.texts(elements), regexs, timeoutMs);
            }
        }

        @Override
        public boolean apply(@Nullable List<WebElement> elements) {
            if (elements.size() != regexs.size()) {
                return false;
            }

            for (int i = 0; i < regexs.size(); i++) {
                WebElement element = elements.get(i);
                String regex = regexs.get(i);
                if (!Html.text.matches(element.getText(), regex)) {
                    return false;
                }
            }
            return true;
        }
    }

    public static class MatchTitle extends CollectionCondition {

        protected final List<String> titles;

        public MatchTitle(String... titles) {
            this(asList(titles));
        }

        public MatchTitle(List<String> titles) {
            if (titles.isEmpty()) {
                throw new IllegalArgumentException("No titles given");
            }
            this.titles = unmodifiableList(titles);
        }

        @Override
        public void fail(WebElementsCollection collection, List<WebElement> elements, Exception lastError, long timeoutMs) {
            if (elements == null || elements.isEmpty()) {
                ElementNotFound elementNotFound = new ElementNotFound(collection, titles, lastError);
                elementNotFound.timeoutMs = timeoutMs;
                throw elementNotFound;
            } else {
                throw new TextsMismatch(collection, ElementsCollection.texts(elements), titles, timeoutMs);
            }
        }

        @Override
        public boolean apply(@Nullable List<WebElement> elements) {
            if (elements.size() != titles.size()) {
                return false;
            }

            for (int i = 0; i < titles.size(); i++) {
                WebElement element = elements.get(i);
                String title = titles.get(i);
                if (!Html.text.equals(element.getAttribute("title"), title)) {
                    return false;
                }
            }
            return true;
        }
    }

    public static class ContainsText extends CollectionCondition {

        protected final List<String> text;


        public ContainsText(String text) {
            this.text = Arrays.asList(text);
        }

        @Override
        public void fail(WebElementsCollection collection, List<WebElement> elements, Exception lastError, long timeoutMs) {
            if (elements == null || elements.isEmpty()) {
                ElementNotFound elementNotFound = new ElementNotFound(collection, text, lastError);
                elementNotFound.timeoutMs = timeoutMs;
                throw elementNotFound;
            } else {
                throw new TextsMismatch(collection, ElementsCollection.texts(elements), text, timeoutMs);
            }
        }

        @Override
        public boolean apply(@Nullable List<WebElement> elements) {
            if (elements == null || elements.isEmpty()) {
                return false;
            }

            for (WebElement element : elements) {
                if (Html.text.matches(element.getText(), text.get(0))) {
                    return true;
                }
            }
            return false;
        }
    }

    public static String getCurrentFrame() {
        return executeJavaScript(
                "var frame = window.frameElement;\n" +
                        "        if (!frame) {\n" +
                        "            return '';\n" +
                        "        }\n" +
                        "        if (frame.name) {\n" +
                        "            return frame.name;\n" +
                        "        }\n" +
                        "        if (frame.id) {\n" +
                        "            return frame.id;\n" +
                        "        }\n" +
                        "       return '';");
    }

    public static String getCurrentWindow() {
        return WebDriverRunner.getWebDriver().getWindowHandle();

    }

    public static void clickAway() {
        SelenideElement header = $("#headerPane");
        int x = header.getSize().getWidth();
        int y = header.getSize().getHeight();
        actions().moveToElement($("#headerPane"), x, y).click().perform();
    }

    public static void switchToWindowByRegex(String regex) {
        String currentWindowHandle = WebDriverRunner.getWebDriver().getWindowHandle();
        Set<String> availableWindowHandles = WebDriverRunner.getWebDriver().getWindowHandles();

        for (String handle : availableWindowHandles) {
            if (!handle.equals(currentWindowHandle)) {
                switchTo().window(handle);
                Pattern pattern = Pattern.compile(regex);
                Matcher matcher = pattern.matcher(title());
                if (matcher.matches()) {
                    break;
                }
            } else {
                switchTo().window(currentWindowHandle);
            }
        }
    }

    public static Image takeScreenshotOfElement(WebElement element, String pathOut) throws IOException {
        WebDriver driver = WebDriverRunner.getWebDriver();

// Get entire page screenshot
        File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        BufferedImage fullImg = ImageIO.read(screenshot);

// Get the location of element on the page
        Point point = element.getLocation();

// Get width and height of the element
        int eleWidth = element.getSize().getWidth()-10; //TODO remove this dirty hack
        int eleHeight = element.getSize().getHeight();

// Crop the entire page screenshot to get only element screenshot
        BufferedImage elementScreenshot = fullImg.getSubimage(point.getX(), point.getY(),
                eleWidth, eleHeight);
        if (pathOut != null) {
            ImageIO.write(elementScreenshot, "png", screenshot);
// Copy the element screenshot to disk
            File screenshotLocation = new File(pathOut);
            FileUtils.copyFile(screenshot, screenshotLocation);
        }

        return elementScreenshot;
    }

    public static boolean compareImages(Image imgOriginal, Image imgToCompareWithOriginal, String pathOut) throws IOException {
        ImageComparison imageComparison = new ImageComparison(10, 10, 0.1);
        return imageComparison.fuzzyEqual(imgOriginal, imgToCompareWithOriginal, pathOut);
    }

    public static void insertImageIntoClipboard(String path) throws IOException {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(new TransferableImage(ImageIO.read(new File(path))), null);
    }
}
