package com.atlassian.webdriver.bitbucket.element;

import com.atlassian.pageobjects.elements.PageElement;
import com.atlassian.pageobjects.elements.WebDriverElement;
import com.atlassian.pageobjects.elements.WebDriverLocatable;
import com.atlassian.pageobjects.elements.timeout.TimeoutType;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.openqa.selenium.By;

import javax.annotation.Nonnull;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Abstract base class for all Stash page objects extending {@link WebDriverElement}. It provides a constructor that
 * allows binding the page object using an instance of {@link PageElement}, which is useful for migrating some
 * page objects off the {@code WebDriverElement} pattern.
 * <p>
 * NOTE: extending of {@code WebDriverElement} is <i>not</i> the proper way of building page objects. Instead, page
 * objects should be (internally) <i>composed</i> of page elements and not expose the underlying page elements in their
 * API (other than to subclasses in protected scope). This class is merely a utility to provide a "copy constructor" to
 * create instances of legacy page objects extending {@code WebDriverElement} from a {@code PageElement}.
 * <p>
 * <b>Migration path:</b> to migrate a legacy page object extending from this class:
 * <ul>
 *     <li>change it to extend {@link AbstractElementPageObject} instead</li>
 *     <li>remove all constructors except the ones that accept an instance of {@code PageElement} (the container) and
 *     pass it on to the super class</li>
 *     <li>fix the remaining compilation errors by "prefixing" calls to the {@code PageElement} API with
 *     {@code container} provided from {@link AbstractElementPageObject} - essentially replacing inheritance with
 *     delegation</li>
 *     <li>make sure the places where the migrated page object is
 *     {@link com.atlassian.pageobjects.PageBinder#bind(Class, Object...) bound} use the constructor that accepts an
 *     instance of {@link PageElement}. That may require replacing calls to
 *     {@link com.atlassian.pageobjects.elements.PageElementFinder#find(By)} with
 *     {@link com.atlassian.pageobjects.PageBinder#bind(Class, Object...)}</li>
 * </ul>
 * @deprecated this class exists only as a temporary utility to help migrate legacy page objects extending
 * {@code WebDriverElement}. It will be removed once all the legacy page objects are migrated to the composition
 * pattern.
 * @see AbstractElementPageObject
 */
@Deprecated
public abstract class LegacyElementPageObject extends WebDriverElement {

    protected LegacyElementPageObject(@Nonnull PageElement container) {
        // copy the "internals" of the page element (it must be an instance of WebDriverElement)
        super(getLocatable(checkNotNull(container, "element")), getTimeoutType(container));
    }

    // expose all the other constructors
    protected LegacyElementPageObject(By locator) {
        super(locator);
    }

    protected LegacyElementPageObject(By locator, TimeoutType timeoutType) {
        super(locator, timeoutType);
    }

    protected LegacyElementPageObject(By locator, WebDriverLocatable parent) {
        super(locator, parent);
    }

    protected LegacyElementPageObject(By locator, WebDriverLocatable parent, TimeoutType timeoutType) {
        super(locator, parent, timeoutType);
    }

    protected LegacyElementPageObject(WebDriverLocatable locatable, TimeoutType timeoutType) {
        super(locatable, timeoutType);
    }

    private static WebDriverLocatable getLocatable(PageElement element) {
        return (WebDriverLocatable) getFieldValue(element, "locatable");
    }

    private static TimeoutType getTimeoutType(PageElement element) {
        return (TimeoutType) getFieldValue(element, "defaultTimeout");
    }

    private static Object getFieldValue(PageElement element, String name) {
        try {
            return FieldUtils.getDeclaredField(WebDriverElement.class, name, true).get(element);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}
