package com.intland.codebeamer.functionaltest.page.wiki;

import com.codeborne.selenide.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.intland.codebeamer.functionaltest.helper.Helper;
import com.intland.codebeamer.functionaltest.page.BasePage;
import com.intland.codebeamer.functionaltest.page.common.ConfirmationDialogPage;
import com.intland.codebeamer.functionaltest.page.common.MoreMenuHelper;
import com.intland.codebeamer.functionaltest.page.mystart.MyStartPage;
import com.intland.codebeamer.functionaltest.page.trackers.documentview.SettingsDialog;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.testng.Assert;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.codeborne.selenide.Condition.cssClass;
import static com.codeborne.selenide.Condition.disappear;
import static com.codeborne.selenide.Selenide.*;

/**
 * Copyright by Intland Software
 * <p>
 * All rights reserved.
 * <p>
 * This software is the confidential and proprietary information
 * of Intland Software. ("Confidential Information"). You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Intland.
 */
public class LeftPane {

    public SelenideElement leftPaneSelector = $("#west");


    public SelenideElement leftTreePaneSelector = $("#treePane");


    public SelenideElement treeFilter() {
        return $(".treeFilterBox");
    }

    private SelenideElement rootNode = leftPaneSelector.$(By.xpath(".//ul[@class='jstree-container-ul jstree-children']/*/a/.."));

    private final String leftTreeItemSelector = ".//li[@role='treeitem' and not(contains(@class, 'jstree-hidden'))]/a";

    public enum LeftTreeContextMenuItem {
        COLLAPSE_NODE("Collapse Node", null),
        EXPAND_NODE("Expand Node", null),
        RENAME("Rename", null);

        private final String text;
        public final Class<?> pageObjectClass;

        LeftTreeContextMenuItem(String s, Class<?> pageObjectClass) {
            text = s;
            this.pageObjectClass = pageObjectClass;
        }

        public boolean equalsName(String otherSelector) {
            return (otherSelector == null) ? false : text.equals(otherSelector);
        }

        public String toString() {
            return this.text;
        }
    }

    public enum DragAndDropAction {
        BEFORE,
        INTO,
        AFTER;
    }


    /**
     * @param itemLabel
     * @param index     If the label is not unique enough there are multiple results so with this parameter you can choose one of those
     * @return
     */
    public SelenideElement leftTreeItem(String itemLabel, int index) {
        openAllLeftTreeNodes();
        return leftTreePaneSelector.find(By.xpath("(" + leftTreeItemSelector + "[contains(text(), '" + itemLabel + "')])" + "[" + index + "]"));
    }

    public int getWikipagesCount() {
        openAllLeftTreeNodes();
        sleep(2000);//wait fot nodes to open
        return leftTreePaneSelector.findAll("i[style*='wiki']").size();
    }

    public int getDashboardCount() {
        openAllLeftTreeNodes();
        return leftTreePaneSelector.findAll("i[style*='dashboard']").size();
    }

    public ElementsCollection getAllLeftTreeItems() {
        openAllLeftTreeNodes();
        return leftTreePaneSelector.findAll(By.xpath(leftTreeItemSelector));
    }

    public ElementsCollection getAllLeftTreeItems(boolean doOpenAll) {
        if (doOpenAll) openAllLeftTreeNodes();
        return leftTreePaneSelector.findAll(By.xpath(leftTreeItemSelector));
    }

    public ElementsCollection getAllFirstLevelLeftTreeItems(boolean doOpenAll) {
        if (doOpenAll) openAllLeftTreeNodes();
        ////ul[@class='jstree-container-ul jstree-children']/*/a/../*/li[@role='treeitem' and not(contains(@class, 'jstree-hidden'))]/a
        return rootNode.findAll(By.xpath("./*/li[@role='treeitem' and not(contains(@class, 'jstree-hidden'))]/a"));
    }

    public SelenideElement clickOnLeftTreeItem(String itemLabel, int index) {
        SelenideElement item = leftTreeItem(itemLabel, index);
        item.click();
        return item;
    }

    public void clickOnMultipleLeftTreeItems(String[] itemLabels, int[] indices) {
        Assert.assertEquals(itemLabels.length, indices.length, "itemLabels's size not equals to indices's size");
        actions().keyDown(Keys.LEFT_CONTROL).perform();
        int i = 0;
        for (; i < itemLabels.length; ++i){
            actions().click(leftTreeItem(itemLabels[i], indices[i])).perform();
        }
        actions().keyUp(Keys.LEFT_CONTROL).perform();
    }

    public BasePage clickOnLeftTreeContextMenuItem(String itemLabel, int index, LeftTreeContextMenuItem leftTreeContextMenuItem) {
        clickOnLeftTreeItem(itemLabel, index).contextClick();
        $(By.xpath("//ul[contains(@class, 'jstree-contextmenu')]//a[contains(text(), '" + leftTreeContextMenuItem.text + "')]")).click();
        return leftTreeContextMenuItem.pageObjectClass != null ? page((Class<BasePage>) leftTreeContextMenuItem.pageObjectClass) : null;
    }

    public void dragAndDrop(String[] itemLabelFrom, int[] itemLabelFromIndex, String itemLabelTo, int itemLabelToIndex, DragAndDropAction dragAndDropAction) {
        Assert.assertEquals(itemLabelFrom.length, itemLabelFromIndex.length, "itemLabelFrom's size not equals to itemLabelFromIndex's size");
        openAllLeftTreeNodes();
        SelenideElement to = leftTreeItem(itemLabelTo, itemLabelToIndex);
        int xOffset = to.getSize().getWidth() / 2;
        int yOffset = 0;
        if (dragAndDropAction.equals(DragAndDropAction.BEFORE)) {
            yOffset = 0;
        } else if (dragAndDropAction.equals(DragAndDropAction.INTO)) {
            yOffset = to.getSize().getHeight() / 2;
        } else if (dragAndDropAction.equals(DragAndDropAction.AFTER)) {
            yOffset = to.getSize().getHeight() - 1;
        }
        actions().keyDown(Keys.LEFT_CONTROL).perform();
        int i = 0;
        for (; i < itemLabelFrom.length; ++i){
            actions().click(leftTreeItem(itemLabelFrom[i], itemLabelFromIndex[i])).perform();
        }
        actions().keyUp(Keys.LEFT_CONTROL).perform();
        actions().clickAndHold(leftTreeItem(itemLabelFrom[i-1], itemLabelFromIndex[i-1]).$(By.tagName("i"))).moveToElement(to, xOffset, yOffset).release().perform();
        sleep(1000);
    }

    public void fillTreeFilter(String searchQuery) {
        treeFilter().setValue(searchQuery);
        $("#go_treePane").click();
        sleep(1000);
    }

    public void filter(String text) {
        $(".treeFilterBox").setValue(text);
        sleep(1000);
    }

    public SettingsDialog clickOnSettingsIcon() {
        leftPaneSelector.find(By.xpath(".//img[@title='Settings']")).click();
        return page(SettingsDialog.class);
    }


    public boolean isLeftPaneHidden() {
        return leftPaneSelector.getCssValue("display").equals("none");
    }

    public int getLeftPaneWidth() {
        return Integer.parseInt(leftPaneSelector.getCssValue("width").split("px")[0]);
    }

    public void checkIfContextMenuItems(String itemLabel, int index, Condition condition, LeftTreeContextMenuItem... items) {
        clickOnLeftTreeItem(itemLabel, index).contextClick();
        if (Condition.visible.getClass().equals(condition.getClass())) {
            $$(By.xpath(".//ul[contains(@class, 'jstree-contextmenu')]//li[not(contains(@class, 'vakata-context'))]")).shouldHave(CollectionCondition.size(items.length));
        }
        for (LeftTreeContextMenuItem item : items) {
            $(By.xpath("//ul[contains(@class, 'jstree-contextmenu')]//a[contains(text(), '" + item.text + "')]")).shouldBe(condition);
        }

    }

    public void checkIfRootNode(boolean isOpen) {
        if (isOpen) {
            checkIfRootNode(cssClass("jstree-open"));
        } else {
            checkIfRootNode(cssClass("jstree-closed"));
        }
    }

    /**
     * @param expectedBranchInJsonFormat e.g.
     *                                   [{
     *                                   "text":"bond's Personal Wiki",
     *                                   "children":[
     *                                   {
     *                                   "text":"hierarchy2017-05-30-16-51-25",
     *                                   "children":[
     *                                   {
     *                                   "text":"hierarchy2017-05-30-16-51-33",
     *                                   "children":[]
     *                                   }
     *                                   ]
     *                                   }
     *                                   ]
     *                                   }]
     * @param itemIndex                  1 based
     */
    public void checkIfBranchIsPartOfLeftTree(String labelOfRootNodeOfBranch, int itemIndex, Pattern expectedBranchInJsonFormat) {
        waitForLeftTreeToLoad();
        String treeInJson = executeJavaScript("return JSON.stringify($('#treePane').jstree(true)" +
                ".get_json($('#treePane').find(\"a:contains('" + labelOfRootNodeOfBranch + "')\")[" + (itemIndex - 1) + "]," +
                " { 'flat': false }));");
        Gson gson = new GsonBuilder().disableHtmlEscaping().create();
        String filteredTreeInJsonFormat;
        if (treeInJson.startsWith("[")) {
            Type datasetListType = new TypeToken<Collection<JsTreeNode>>() {
            }.getType();
            List<JsTreeNode> treeInObjectFormat = gson.fromJson(treeInJson, datasetListType);
            filteredTreeInJsonFormat = gson.toJson(treeInObjectFormat);
        }else {
            JsTreeNode treeInObjectFormat = gson.fromJson(treeInJson, JsTreeNode.class);
            gson.toJson(treeInObjectFormat);
            filteredTreeInJsonFormat = gson.toJson(treeInObjectFormat).trim();
        }
        Matcher m = expectedBranchInJsonFormat.matcher(filteredTreeInJsonFormat);
        //System.out.println(expectedBranchInJsonFormat + " | " + filteredTreeInJsonFormat);
        Assert.assertTrue(m.matches(), expectedBranchInJsonFormat + " | " + filteredTreeInJsonFormat);
    }

    public void checkIfRootNode(Condition condition) {
        rootNode.shouldHave(condition);
    }

    public void openAllLeftTreeNodes() {
        waitForLeftTreeToLoad();
        Helper.openAllRootNodes(leftTreePaneSelector);
    }

    public void deleteAllLeftTreeItemExceptDummy() {
        MyStartPage myStartPage = page(MyStartPage.class);
        ElementsCollection firstLevelNodes = getAllFirstLevelLeftTreeItems(true);
        //it's a trick to force selenide to update collection size
        while (firstLevelNodes.shouldHave(CollectionCondition.sizeGreaterThan(0)).size() > 1) {
            firstLevelNodes.get(1).click();
            myStartPage.centerPane.waitForCenterPaneToLoad();
            ConfirmationDialogPage confirmationDialogPage = (ConfirmationDialogPage) myStartPage.centerPane.clickOnMoreMenuItem(MoreMenuHelper.MoreMenuItem.DELETE);
            confirmationDialogPage.clickOnYes();
            //sleep(2000);
        }

    }

    public void rename(String itemLabel, int index, String name){
        clickOnLeftTreeContextMenuItem(itemLabel, index, LeftPane.LeftTreeContextMenuItem.RENAME);
        $(By.className("jstree-rename-input")).sendKeys(name);
        Helper.clickAway();
    }

    public void waitForLeftTreeToLoad() {
        Wait().withTimeout(1, TimeUnit.MINUTES)
                .pollingEvery(1, TimeUnit.SECONDS)
                .until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector("#j1_loading")));
        //$("#j1_loading").should(disappear);
    }


    class JsTreeNode {

        private String text;
        private List<JsTreeNode> children;
    }
}
