package com.atlassian.webdriver.bitbucket.page;

import com.atlassian.pageobjects.Page;
import com.atlassian.pageobjects.PageBinder;
import com.atlassian.pageobjects.binder.PageBindingWaitException;
import com.atlassian.pageobjects.binder.WaitUntil;
import com.atlassian.pageobjects.elements.ElementBy;
import com.atlassian.pageobjects.elements.PageElement;
import com.atlassian.pageobjects.elements.PageElementFinder;
import com.atlassian.pageobjects.elements.query.Conditions;
import com.atlassian.pageobjects.elements.query.Queries;
import com.atlassian.pageobjects.elements.query.TimedCondition;
import com.atlassian.pageobjects.elements.timeout.Timeouts;
import com.atlassian.webdriver.bitbucket.util.ElementUtils;
import com.atlassian.webdriver.bitbucket.util.EventUtils;
import com.atlassian.webdriver.bitbucket.util.ScrollInfo;
import com.google.common.base.Supplier;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;

import javax.inject.Inject;

import static com.atlassian.pageobjects.elements.query.Poller.waitUntilTrue;
import static com.atlassian.bitbucket.test.DefaultFuncTestData.getBaseURL;
import static java.lang.String.format;
import static org.apache.commons.lang3.StringUtils.removeStart;

/**
 * Base class for the Bitbucket and non-Bitbucket pages (ex: plugin pages).
 *
 * @see BitbucketPage
 */
public abstract class AbstractPage implements Page {

    @Inject
    protected WebDriver driver;

    @Inject
    protected JavascriptExecutor javascriptExecutor;

    @Inject
    protected PageBinder pageBinder;

    @Inject
    protected PageElementFinder elementFinder;

    @Inject
    protected Timeouts timeouts;

    @ElementBy(tagName="html")
    protected PageElement html;

    @ElementBy(tagName="body")
    protected PageElement body;

    public static boolean isOnPage(WebDriver driver, Page page) {
        String actualUrl = driver.getCurrentUrl();
        String expectedUrl = page.getUrl();
        return getUrlWithoutVariables(actualUrl).endsWith(getUrlWithoutVariables(expectedUrl));
    }

    protected static String getUrlWithoutVariables(String url) {
        return url.substring(0, url.contains("?") ?
                url.indexOf('?') :
                (url.contains("#") ?
                        url.indexOf("#") :
                        url.length())
        );
    }

    @WaitUntil
    public void doWait() {
        try {
            waitUntilTrue(isHereTimed());
        } catch (AssertionError e) {
            throw new PageBindingWaitException(format("Page failed to load at expected URL." +
                    "\n\t\tExpected: %s\n\t\tActual:   %s", getUrl(), removeStart(driver.getCurrentUrl(), getBaseURL())), this, e);
        }
        waitUntilTrue("Expected html tag to be present", elementFinder.find(By.tagName("html")).timed().isPresent());
    }

    public boolean isHere() {
        return isOnPage(driver, this);
    }

    public TimedCondition isHereTimed() {
        return Conditions.forSupplier(timeouts, () -> {
            return isHere();
        });
    }

    public void scrollToTop() {
        ElementUtils.scrollDocument(body, 0);
    }

    public void scrollToBottom() {
        ElementUtils.scrollDocument(body);
    }

    public ScrollInfo getScrollInfo() {
        return ElementUtils.getScrollInfo(html);
    }

    /**
     * Runs some code and then waits for navigation to a new page.
     */
    public void waitForPageLoad(final Runnable runnable) {
        waitForPageLoad(() -> {
            runnable.run();
            return null;
        });
    }

    /**
     * Runs some code and then waits for navigation to a new page.
     */
    public <T> T waitForPageLoad(Supplier<T> supplier) {
        javascriptExecutor.executeScript("window.pagePopExpected = true;");
        T t = supplier.get();
        waitUntilTrue(Queries.forSupplier(timeouts, () ->
                (Boolean) javascriptExecutor.executeScript("return window && window.pagePopExpected !== true;")));
        return t;
    }

    public void waitUntilEventFired(String eventName, Runnable runnable) {
        EventUtils.waitUntilEventFired(eventName, runnable, javascriptExecutor, timeouts);
    }
}
