package cz.moneta.test.harness.connectors.web;

import cz.moneta.test.harness.support.web.Lookup;
import cz.moneta.test.harness.support.web.Until;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;

import java.util.List;
import java.util.Optional;

public class ElementsActions {

    private static final int DEFAULT_TIMEOUT_FOR_WAIT = 30;
    private static final Logger logger = LogManager.getLogger(ElementsActions.class);

    private final SeleniumWebConnector connector;
    private final String JS = "var evObj = document.createEvent('MouseEvents');"
            + "evObj.initMouseEvent(\"mouseover\",true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);"
            + "arguments[0].dispatchEvent(evObj);";

    public ElementsActions(SeleniumWebConnector connector) {
        this.connector = connector;
    }

    public void moveToElement(String path, Lookup lookup) {
        Actions actions = new Actions(connector.getDriver());
        actions.moveToElement(waitForLazyElement(path, lookup))
                .build()
                .perform();
    }

    public void moveToElementAndClick(String hoverElementPath, String clickElementPath, Lookup lookup) {
        Optional.of(connector.getDriver())
                .filter(JavascriptExecutor.class::isInstance)
                .map(JavascriptExecutor.class::cast)
                .ifPresent(e -> {
                    e.executeScript(JS, waitForLazyElement(hoverElementPath, lookup));
                    connector.clicks().click(() -> clickElementPath, lookup);
                });
    }

    public String getText(String path, Lookup lookup) {
        return waitForLazyElement(path, lookup).getText();
    }

    public boolean isElementVisible(String path, Lookup lookup) {
        return isElementVisible(DEFAULT_TIMEOUT_FOR_WAIT, path, lookup);
    }

    public boolean isElementVisible(int timeout, String path, Lookup lookup) {
        try {
            waitForElement(timeout, path, lookup, Until.VISIBLE);
            return true;
        } catch (TimeoutException e) {
            logger.warn("Element {} is not visible after {}s.", () -> path, () -> timeout);
            return false;
        }
    }

    public void dragAndDropElement(String source, String target, Lookup lookup) {
        Actions actions = new Actions(connector.getDriver());
        actions.dragAndDrop(waitForLazyElement(source, lookup), findElement(target, lookup))
                .build()
                .perform();
    }

    public void dragAndDropBy(String source, int xOffset, int yOffset, Lookup lookup) {
        Actions actions = new Actions(connector.getDriver());
        actions.dragAndDropBy(waitForLazyElement(source, lookup), xOffset, yOffset)
                .build()
                .perform();
    }

    private WebElement waitForLazyElement(String path, Lookup lookup) {
        return connector.waits().waitForLazyElement(path, lookup);
    }

    private WebElement findElement(String path, Lookup lookup) {
        return connector.getDriver()
                .findElement(connector.resolveLookup(lookup).apply(path));
    }

    private void waitForElement(int timeout, String path, Lookup lookup, Until until) {
        connector.waits().waitForElements(timeout, lookup, until, path);
    }

    public void scrollIntoView(String path, Lookup lookup) {
        WebElement element = connector.getDriver().findElement(connector.resolveLookup(lookup).apply(path));
        connector.scrolling().scrollIntoView(element);
    }

    public List<WebElement> findElements(String path, Lookup lookup) {
        return connector.getDriver().findElements(connector.resolveLookup(lookup).apply(path));
    }
}