package com.atlassian.webdriver.bitbucket.page;

import com.atlassian.pageobjects.elements.ElementBy;
import com.atlassian.pageobjects.elements.PageElement;
import com.atlassian.pageobjects.elements.query.Poller;
import com.atlassian.pageobjects.elements.timeout.TimeoutType;
import com.atlassian.webdriver.bitbucket.element.*;
import com.atlassian.webdriver.bitbucket.util.ElementUtils;
import com.atlassian.webdriver.bitbucket.util.KeyboardShortcuts;
import org.openqa.selenium.By;

import javax.annotation.Nonnull;

import static com.atlassian.pageobjects.elements.query.Poller.waitUntilTrue;

public abstract class CommitPage<P extends CommitPage<P, F>, F extends DiffFileContent> extends BitbucketPage {

    public static final String COLLAPSED_CSS_CLASS = "collapsed";
    public static final String COMMIT_FILE_CONTAINER_CSS_CLASS = "commit-files";

    @ElementBy(className = CommitMetadata.className, pageElementClass = CommitMetadata.class)
    protected CommitMetadata metadata;

    @ElementBy(className = COMMIT_FILE_CONTAINER_CSS_CLASS)
    protected PageElement treeAndDiffView;

    @ElementBy(className = FileTree.FILE_TREE_CONTAINER_CSS_CLASS, pageElementClass = FileTree.class)
    protected FileTree tree;

    @ElementBy(className ="collapse-file-tree")
    protected PageElement collapseFileTreeButton;

    @ElementBy(className = "file-toolbar", pageElementClass = FileToolbar.class)
    protected FileToolbar fileToolbar;

    @ElementBy(className = "user-avatar")
    protected PageElement avatar;

    protected final String projectKey;
    protected final String repoSlug;
    protected final String commitId;
    protected final String parentId;
    protected final String changePath;
    protected final Class<P> selfClass;

    public CommitPage(String projectKey, String repoSlug, String commitId, @Nonnull Class<P> selfClass) {
        this(projectKey, repoSlug, commitId, null, null, selfClass);
    }

    public CommitPage(String projectKey, String repoSlug, String commitId, String parentId,
                      @Nonnull Class<P> selfClass) {
        this(projectKey, repoSlug, commitId, parentId, null, selfClass);
    }

    public CommitPage(String projectKey, String repoSlug, String commitId, String parentId, String changePath,
                      @Nonnull Class<P> selfClass) {
        this.projectKey = projectKey;
        this.repoSlug = repoSlug;
        this.commitId = commitId;
        this.parentId = parentId;
        this.changePath = changePath;
        this.selfClass = selfClass;
    }

    public abstract F getFileContent();

    public CommitMetadata getMetadata() {
        return metadata;
    }

    public FileToolbar getFileToolbar() {
        waitUntilFileContentLoaded();
        waitUntilTrue(fileToolbar.timed().isVisible());
        return fileToolbar;
    }

    public FileTree getTree() {
        tree.waitUntilLoaded();
        return tree;
    }

    public P waitUntilDiffTreeLoaded() {
        waitUntilTrue(elementFinder.find(By.className("file-tree")).find(By.tagName("ul")).timed().isVisible());
        ensureFuncTestClass();
        return self();
    }

    public P waitUntilFileContentLoaded() {
        waitUntilTrue(elementFinder.find(By.id("commit-file-content")).find(By.className("content-view")).timed().isVisible());
        return self();
    }

    public BranchInfo getBranchInfo() {
        return pageBinder.bind(BranchInfo.class, elementFinder.find(By.className("branch-info-details")), projectKey, repoSlug);
    }

    @Override
    public void doWait() {
        super.doWait();
        waitUntilDiffTreeLoaded();
    }


    private void ensureFuncTestClass() {
        javascriptExecutor.executeScript("jQuery('." + COMMIT_FILE_CONTAINER_CSS_CLASS + "').removeClass('" + COLLAPSED_CSS_CLASS + "')");
    }

    private <PP extends BitbucketPage> PP clickFileInternal(String path, boolean wait, Class<PP> nextPage, Object... args) {
        waitUntilDiffTreeLoaded();

        PageElement pageElement = tree.find(By.cssSelector("[href=\"#" + path + "\"]"));
        Poller.waitUntilTrue(pageElement.timed().isVisible());
        pageElement.click();

        if (wait) {
            waitUntilFileContentLoaded();
        }

        return pageBinder.bind(nextPage, args);
    }

    public P clickFile(String path) {
        return clickFileInternal(path, false, selfClass, this.projectKey, this.repoSlug, this.commitId, this.parentId, path);
    }

    public <PP extends BitbucketPage> PP clickFile(String path, Class<PP> nextPage, Object... args) {
        return clickFileInternal(path, false, nextPage, args);
    }

    @Override
    public String getUrl() {
        String url = "/projects/" + projectKey + "/repos/" + repoSlug + "/commits/" + this.commitId;

        if (parentId != null) {
            url += "?to=" + parentId;
        }

        if (changePath != null) {
            url += "#" + changePath;
        }

        return url;
    }

    public void moveToNextChange(boolean expectMovement) {
        navigateDiff(expectMovement, new Runnable() {
            @Override
            public void run() {
                KeyboardShortcuts.moveToNextSecondary(body);
            }
        });
    }

    public void moveToNextComment(boolean expectMovement) {
        navigateDiff(expectMovement, new Runnable() {
            @Override
            public void run() {
                KeyboardShortcuts.moveToNextTertiary(body);
            }
        });
    }

    public void moveToPreviousChange(boolean expectMovement) {
        navigateDiff(expectMovement, new Runnable() {
            @Override
            public void run() {
                KeyboardShortcuts.moveToPreviousSecondary(body);
            }
        });
    }

    public void moveToPreviousComment(boolean expectMovement) {
        navigateDiff(expectMovement, new Runnable() {
            @Override
            public void run() {
                KeyboardShortcuts.moveToPreviousTertiary(body);
            }
        });
    }

    /**
     * @return this object
     */
    private P self() {
        return selfClass.cast(this);
    }

    private void navigateDiff(boolean expectMovement, final Runnable doNavigation) {
        if (expectMovement) {
            ElementUtils.waitUntilUpdated(getFileContent().find(By.className("content-view")), doNavigation);
        } else {
            doNavigation.run();
        }
    }

    public static class FileToolbar extends LegacyElementPageObject {

        public FileToolbar(@Nonnull PageElement element) {
            super(element);
        }

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

        public boolean hasSourceView() {
            return find(By.className("source-view-link")).timed().isVisible().byDefaultTimeout();
        }
    }
}
