package com.atlassian.webdriver.bitbucket.element;

import com.atlassian.pageobjects.elements.PageElement;
import com.atlassian.pageobjects.elements.WebDriverLocatable;
import com.atlassian.pageobjects.elements.query.Conditions;
import com.atlassian.pageobjects.elements.query.Poller;
import com.atlassian.pageobjects.elements.query.TimedCondition;
import com.atlassian.pageobjects.elements.timeout.TimeoutType;
import com.atlassian.webdriver.bitbucket.element.tasks.Task;
import com.atlassian.webdriver.bitbucket.util.ElementUtils;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import org.openqa.selenium.By;

import java.util.List;

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

public class Comment extends LegacyElementPageObject {

    public static final String COMMENT_CLASS = "comment";

    private long id = -1; //lazy loaded
    private int version = -1; //lazy loaded

    public Comment(PageElement pageElement) {
        super(pageElement);
    }

    public Comment(By locator) {
        super(locator);
    }

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

    public Comment(WebDriverLocatable locatable, TimeoutType timeoutType) {
        super(locatable, timeoutType);
    }

    public Comment(By locator, WebDriverLocatable parent, TimeoutType timeoutType) {
        super(locator, parent, timeoutType);
    }

    public Comment(By locator, WebDriverLocatable parent) {
        super(locator, parent);
    }

    public String getAuthor() {
        return find(By.cssSelector(".content .user-name")).getText();
    }

    public String getCommentText() {
        return findCommentText().getText();
    }

    public String getCommentHtml() {
        return ElementUtils.getHtml(findCommentText());
    }

    public long getCommentId() {
        if (id == -1) {
            //load
            id = Long.parseLong(getAttribute("data-id"));
        }
        return id;
    }

    public int getCommentVersion() {
        if (version == -1) {
            //load
            version = Integer.parseInt(getAttribute("data-version"));
        }
        return version;
    }

    public ReplyCommentForm reply() {
        find(By.className("reply")).click();
        return getReplyCommentForm();
    }

    public Comment replyWith(String text) {
        Comment.ReplyCommentForm replyForm = reply();
        replyForm.setText(text);
        return replyForm.submit();
    }

    public ReplyCommentForm getReplyCommentForm() {
        return find(By.cssSelector(".replies .comment-form-container"), ReplyCommentForm.class, defaultTimeout);
    }

    public EditCommentForm edit() {
        find(By.cssSelector(".actions .edit")).click();
        return getEditForm();
    }

    public Comment toggleLike() {
        getLikeButton().click();
        return this;
    }

    protected PageElement getLikeButton() {
        return find(By.cssSelector(".info .comment-likes-button"));
    }

    protected PageElement getLikeDialogButton() {
        return find(By.cssSelector(".info .comment-likes-info button"));
    }

    public boolean hasLikeButton() {
        return getLikeButton().getText().equals("Like");
    }

    public TimedCondition canLike() {
        return getLikeButton().timed().isPresent();
    }

    public String getLikeInfo() {
        return find(By.cssSelector(".info .comment-likes-info")).getText();
    }

    public TimedCondition isLikeDialogAvailable() {
        return getLikeDialogButton().timed().isPresent();
    }

    public LikesInfoDialog openLikeDialog() {
        getLikeDialogButton().click();
        LikesInfoDialog dialog = pageBinder.bind(LikesInfoDialog.class, By.id("inline-dialog-likes-info-panel"));
        Poller.waitUntilTrue(dialog.isOpen());
        return dialog;
    }

    public EditCommentForm getEditForm() {
        return find(locatable.getLocator(), EditCommentForm.class, defaultTimeout);
    }

    public DeleteCommentDialog delete() {
        final PageElement deleteLink = find(By.cssSelector(".actions .delete"));
        deleteLink.click();
        return pageBinder.bind(DeleteCommentDialog.class, By.id("delete-repository-dialog"), new Supplier<Void>() {
            @Override
            public Void get() {
                waitUntilFalse(deleteLink.timed().isVisible());
                return null;
            }
        });
    }

    public List<Comment> getReplies() {
        return findAll(By.cssSelector(".replies > li"), Comment.class);
    }

    public boolean hasReplies() {
        return find(By.className(".replies")).isPresent();
    }

    private PageElement findCommentText() {
        return find(By.cssSelector(".content .message"));
    }

    public static class DeleteCommentDialog extends Dialog {

        private final Supplier<Void> after;

        public DeleteCommentDialog(By locator, Supplier<Void> after) {
            super(locator);
            this.after = after;
        }

        public void confirmDelete() {
            super.clickMainAction();
            after.get();
        }
    }

    /**
     * Creates a new task under this comment. Assumes the new task will be the task
     * at the top in order to find it after it has been saved.
     *
     * @param text text to use for the task
     */
    public Task createTask(String text) {
        openTaskForm().setTaskText(text).save();

        // in order to be able to hold on to the right task element between re-renders
        // we get the task by position so we can read it's new id, and then we return that
        // element instead.
        Task newTask = getTaskAt(0);
        return getTaskById(newTask.getTaskId());
    }

    public Task getTaskAt(int index) {
        List<Task> tasks = getTasks();
        return tasks.get(index);
    }

    public Task getTaskById(long id) {
        return find(By.cssSelector(".task[data-task-id='" + id + "']"), Task.class);
    }

    public int getTaskCount() {
        return getTasks().size();
    }

    protected List<Task> getTasks() {
        return findAll(By.cssSelector(".content > .extras > .task-list .task"), Task.class);
    }

    /**
     * Open the task form to add a task.
     *
     * @return a {@link Task} which can be used to set the task's text and persist it
     */
    public Task openTaskForm() {
        find(By.className("task-create")).click();
        return find(By.cssSelector(".task[data-task-id='']"), Task.class);
    }

    public static class EditCommentForm extends CommentForm<EditCommentForm> {

        public EditCommentForm(By locator, WebDriverLocatable locatable, TimeoutType timeoutType) {
            super(locatable, timeoutType);
        }

        @Override
        public void cancel() {
            super.cancel();

            waitUntilFalse(getCancelLink().timed().isPresent());
        }

        @Override
        public Comment submit() {
            getCommentButton().click();
            waitUntilFalse(getCommentButton().timed().isPresent());
            return pageBinder.bind(Comment.class, locatable, defaultTimeout);
        }
    }

    public static class ReplyCommentForm extends CommentForm<ReplyCommentForm> {

        public ReplyCommentForm(By locator, WebDriverLocatable locatable, TimeoutType timeoutType) {
            super(locatable, timeoutType);
        }

        @Override
        public Comment submit() {
            int count = getComments().size();
            getCommentButton().click();
            waitUntilTrue(moreCommentsThan(count));
            return Iterables.getLast(getComments());
        }

        private List<Comment> getComments() {
            return findAll(By.className(COMMENT_CLASS), Comment.class);
        }

        private TimedCondition moreCommentsThan(final int before) {
            return Conditions.forSupplier(timeouts, new Supplier<Boolean>() {
                @Override
                public Boolean get() {
                    return before < getComments().size();
                }
            });
        }
    }

}
