package com.intland.codebeamer.functionaltest.page;

import com.codeborne.selenide.Condition;
import org.apache.commons.lang3.time.DateUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;

import com.intland.codebeamer.functionaltest.helper.FilterValues;
import com.intland.codebeamer.functionaltest.helper.FilterValues.ChoiceFilterValue;
import com.intland.codebeamer.functionaltest.helper.FilterValues.DateFilterValue;
import com.intland.codebeamer.functionaltest.helper.FilterValues.DurationFilterValue;
import com.intland.codebeamer.functionaltest.helper.FilterValues.FieldType;
import com.intland.codebeamer.functionaltest.helper.FilterValues.FilterValue;
import com.intland.codebeamer.functionaltest.helper.FilterValues.MemberFilterValue;
import com.intland.codebeamer.functionaltest.helper.FilterValues.NumberFilterValue;
import com.intland.codebeamer.functionaltest.helper.FilterValues.OrderType;
import com.intland.codebeamer.functionaltest.helper.FilterValues.TextFilterValue;

import com.codeborne.selenide.ElementsCollection;
import com.codeborne.selenide.SelenideElement;

import com.google.common.collect.Ordering;
import org.openqa.selenium.support.ui.ExpectedConditions;

import static com.codeborne.selenide.Selenide.*;
import static com.codeborne.selenide.WebDriverRunner.getWebDriver;
import static com.codeborne.selenide.Condition.*;
import static com.codeborne.selenide.CollectionCondition.*;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 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 productLicense agreement
 * you entered into with Intland.
 */
public class ReportsPage extends BasePage {

    final int shortTimeOut = 2000;
    final int mediumTimeOut = 4000;
    final int longTimeOut = 8000;

    public FieldTree fieldTree = new FieldTree();
    public ReportEditor reportEditor = new ReportEditor();
    public ResultTable resultTable = new ResultTable();
    public ActionBar actionBar = new ActionBar();
    public MyReports myReports = new MyReports();
    public ReportFinder reportFinder = new ReportFinder();
    public ReportProperties reportProperties = new ReportProperties();

    @Override
    public boolean isPageLoaded() throws UnsupportedOperationException {
        try {
            Wait().withTimeout(5, TimeUnit.SECONDS)
                    .ignoring(NoSuchElementException.class)
                    .until(ExpectedConditions.visibilityOf($(".name")));
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    public List<String> getRecentReports() {
        $(By.id("queries_ToolBarItem")).$(By.tagName("img")).shouldBe(visible).click();
        sleep(500);
        List<String> recentReportNames = $(By.id("queries_ToolBarItempopup")).shouldBe(visible).$$(By.className("ui-menu")).last()
                .shouldBe(visible).$$(By.tagName("span")).shouldHave(sizeGreaterThan(0)).texts();
        $(By.id("queries_ToolBarItem")).$(By.tagName("img")).shouldBe(visible).click();
        sleep(500);
        $(By.id("queries_ToolBarItempopup")).shouldNotBe(visible);
        return recentReportNames;
    }

    public void checkIfNewReport(Condition condition) {
        $(By.xpath(".//a[text()='New Report']")).shouldBe(condition);
    }

    public class ReportEditor {

        public void selectProjectsByName(List<String> projectNames) {
            SelenideElement projectSelector = $(By.id("queryWidgetProjectSelectorDiv"));
            projectSelector.click();
            // must be open; clicks are valid on visible
            // items only
            for (String projectName : projectNames) {
                $$(".ui-multiselect-checkboxes label span").find(exactText(projectName)).click();
            }
            projectSelector.click();
        }

        public void selectTrackersByName(List<String> trackerNames) {
            SelenideElement trackerSelector = $(By.id("queryWidgetTrackerSelectorDiv"));
            trackerSelector.click();
            // must be open; clicks are valid on visible
            // items only
            for (String trackerName : trackerNames) {
                $$(".ui-multiselect-checkboxes label span").find(exactText(trackerName)).click();
            }
            trackerSelector.click();
        }

        public void clickOnGoButton() {
            $(By.id("queryWidgetGoButton")).shouldBe(visible).click();
            $(By.id("queryResultTable")).shouldBe(visible);
        }

        public void clickOnAdvancedButton() {
            $(By.id("queryWidgetAdvancedButton")).shouldBe(visible).click();
            $(By.className("CodeMirror")).shouldBe(visible);
            $(By.className("selectorSection")).shouldNotBe(visible);
        }

        public List<String> getSelectedProjectValues() {
            List<String> selectedProjectValues = new ArrayList<String>();
            ElementsCollection selectedProjects = $$(By.name("multiselect_queryWidgetProjectSelector"))
                    .filterBy(attribute("aria-selected", "true"));
            for (int i = 0; i < selectedProjects.size(); i++) {
                selectedProjectValues.add(selectedProjects.get(i).getValue());
            }
            // removing value of "Any" if it exists
            if(selectedProjectValues.remove("0")) {
            	System.out.println("Removed value of \"Any\" from projects");
            }
            return selectedProjectValues;
        }

        public List<String> getSelectedTrackerValues() {
            List<String> selectedTrackerValues = new ArrayList<String>();
            ElementsCollection selectedTrackers = $$(By.name("multiselect_queryWidgetTrackerSelector"))
                    .filterBy(attribute("aria-selected", "true"));
            for (int i = 0; i < selectedTrackers.size(); i++) {
                selectedTrackerValues.add(selectedTrackers.get(i).getValue());
            }
            // removing value of "Any" if it exists
            if(selectedTrackerValues.remove("0")) {
            	System.out.println("Removed value of \"Any\" from trackers");
            }
            return selectedTrackerValues;
        }
        
        public List<String> getSelectedProjectNames() {
            List<String> selectedProjectNames = new ArrayList<String>();
            ElementsCollection selectedProjects = $$(By.name("multiselect_queryWidgetProjectSelector"))
                    .filterBy(attribute("aria-selected", "true"));
            for (int i = 0; i < selectedProjects.size(); i++) {
            	selectedProjectNames.add(selectedProjects.get(i).parent().$(By.tagName("span")).innerText());
            }
            // removing "Any" if it exists
            if(selectedProjectNames.remove("Any")) {
            	System.out.println("Removed \"Any\" from projects");
            }
            return selectedProjectNames;
        }

        public List<String> getSelectedTrackerNames() {
            List<String> selectedTrackerNames = new ArrayList<String>();
            ElementsCollection selectedTrackers = $$(By.name("multiselect_queryWidgetTrackerSelector"))
                    .filterBy(attribute("aria-selected", "true"));
            for (int i = 0; i < selectedTrackers.size(); i++) {
            	selectedTrackerNames.add(selectedTrackers.get(i).parent().$(By.tagName("span")).innerText());
            }
            // removing "Any" if it exists
            if(selectedTrackerNames.remove("Any")) {
            	System.out.println("Removed \"Any\" from trackers");
            }
            return selectedTrackerNames;
        }

        public void setDefaultFieldFilterByType(FieldType fieldType, FilterValue values) {
            SelenideElement justAddedFilter = $(By.id("queryWidgetFieldArea")).$(By.className("droppableArea"))
                    .$$(By.tagName("button")).last();
            switch (fieldType) {
                case Choice:
                    setChoiceFilterValues(values);
                    break;
                case Date:
                    setDateFilterValues(justAddedFilter, values);
                    break;
                case Duration:
                    setDurationFilterValues(justAddedFilter, values);
                    break;
                case Member:
                    setMemberFilterValues(values);
                    break;
                case Number:
                    setNumberFilterValues(justAddedFilter, values);
                    break;
                case Text:
                    setTextFilterValue(justAddedFilter, values);
                    break;
                default:
                    break;
            }
        }

        private void setNumberFilterValues(SelenideElement justAddedFilter, FilterValue values) {
            String dataUniqueId = justAddedFilter.getAttribute("data-uniqueid");
            NumberFilterValue numberFilterValue = (NumberFilterValue) values;
            SelenideElement operatorType = $$(By.tagName("div")).findBy(attribute("data-uniqueid", dataUniqueId))
                    .$(By.className("operatorType"));
            SelenideElement numberInput = $$(By.tagName("div")).findBy(attribute("data-uniqueid", dataUniqueId))
                    .$(By.className("numberInput"));
            operatorType.selectOption(numberFilterValue.getOperatorType().name().replace('_', ' '));
            numberInput.setValue(numberFilterValue.getNumber());
            // click anywhere else than filter
            $(By.className("buttonContainer")).click();
        }

        private void setMemberFilterValues(FilterValue values) {
            MemberFilterValue memberFilterValue = (MemberFilterValue) values;
            for (String filterValue : memberFilterValue.getSelectedValues()) {
                $$(".ui-multiselect-checkboxes label span").find(exactText(filterValue)).click();
            }
            // click anywhere else than filter
            $(By.className("buttonContainer")).click();
        }

        private void setChoiceFilterValues(FilterValue values) {
            ChoiceFilterValue choiceFilterValue = (ChoiceFilterValue) values;
            for (String filterValue : choiceFilterValue.getSelectedValues()) {
                ElementsCollection options = $$(".ui-multiselect-checkboxes label span")
                        .shouldHave(sizeGreaterThanOrEqual(1)).filterBy(exactText(filterValue));
                for (SelenideElement option : options) {
                    option.click();
                }
            }
            // click anywhere else than filter
            $(By.className("buttonContainer")).shouldBe(visible).click();
        }

        private void setDateFilterValues(SelenideElement justAddedFilter, FilterValue values) {
            String filterId = justAddedFilter.getAttribute("id").split("_")[1];
            String selectorDialogId = String.format("dateRangeSelectorDialog_%1$s", filterId);
            DateFilterValue dateFilterValue = (DateFilterValue) values;
            // initialized with CustomDateRange
            if (dateFilterValue.getFromDate().isEmpty() && dateFilterValue.getToDate().isEmpty()) {
                $(By.id(selectorDialogId)).$(By.className("rangeContainer")).$(By.tagName("select"))
                        .selectOption(dateFilterValue.getCustomDateRange().name().replace('_', ' '));
                // click anywhere else than filter
                $(By.className("name")).click();
                $(By.id(selectorDialogId)).shouldNotBe(visible);
            }
            // initialized with fromDate and toDate
            else {
                SelenideElement fromDate = $(By.id(selectorDialogId)).$(By.className("dateContainer"))
                        .$(By.id(selectorDialogId + "_fromDate"));
                SelenideElement toDate = $(By.id(selectorDialogId)).$(By.className("dateContainer"))
                        .$(By.id(selectorDialogId + "_toDate"));
                fromDate.setValue(dateFilterValue.getFromDate()).pressTab().pressTab();//2nd press to get past selector popup
                $(By.id("ui-datepicker-div")).shouldNotBe(visible);
                toDate.setValue(dateFilterValue.getToDate()).pressTab().pressTab();//2nd press to get past selector popup
                $(By.id("ui-datepicker-div")).shouldNotBe(visible);
                // click anywhere else than filter
                $(By.className("buttonContainer")).click();
                $(By.id(selectorDialogId)).shouldNotBe(visible);
            }
        }

        private void setDurationFilterValues(SelenideElement justAddedFilter, FilterValue values) {
            String dataUniqueId = justAddedFilter.getAttribute("data-uniqueid");
            DurationFilterValue durationFilterValue = (DurationFilterValue) values;
            SelenideElement operatorType = $$(By.tagName("div")).findBy(attribute("data-uniqueid", dataUniqueId))
                    .$(By.className("operatorType"));
            SelenideElement numberInput = $$(By.tagName("div")).findBy(attribute("data-uniqueid", dataUniqueId))
                    .$(By.className("numberInput"));
            SelenideElement timeType = $$(By.tagName("div")).findBy(attribute("data-uniqueid", dataUniqueId))
                    .$(By.className("timeType"));
            operatorType.selectOption(durationFilterValue.getOperatorType().name().replace('_', ' '));
            numberInput.setValue(durationFilterValue.getNumber());
            timeType.selectOption(durationFilterValue.getTimeType().name() + "(s)");
            // click anywhere else than filter
            $(By.className("buttonContainer")).click();
        }

        private void setTextFilterValue(SelenideElement justAddedFilter, FilterValue values) {
            String dataUniqueId = justAddedFilter.getAttribute("data-uniqueid");
            TextFilterValue textFilterValue = ((TextFilterValue) values);
            SelenideElement input = $$(By.tagName("div")).findBy(attribute("data-uniqueid", dataUniqueId))
                    .$(By.tagName("input"));
            input.setValue(textFilterValue.getFilterValue()).pressEnter();
            // click anywhere else than filter
            $(By.className("buttonContainer")).click();
        }

        public void setReferenceFieldFilterByChooseItemPopup(String[] trackersToSelect, String[] values) {
            // we'll need a webDriver to switch to frame content
            WebDriver driver = getWebDriver();
            // get uniqueId to find exact div
            SelenideElement justAddedFilter = $(By.id("queryWidgetFieldArea")).$(By.className("droppableArea"))
                    .$$(By.tagName("button")).last();
            String dataUniqueId = justAddedFilter.getAttribute("data-uniqueid");
            // first popup elements
            SelenideElement pencilIcon = $$(By.tagName("div")).findBy(attribute("data-uniqueid", dataUniqueId))
                    .$$(By.tagName("span")).findBy(attribute("data-uniqueid", dataUniqueId))
                    .$(By.className("popupButton"));
            SelenideElement okButton = $$(By.tagName("div")).findBy(attribute("data-uniqueid", dataUniqueId)).parent()
                    .$(By.className("ui-dialog-buttonset")).$$(By.tagName("button")).findBy(exactText("OK"));
            SelenideElement trackerSelector = $$(By.tagName("div")).findBy(attribute("data-uniqueid", dataUniqueId))
                    .$$(By.tagName("span")).findBy(text("Tracker:"));
            // choose item frame elements
            SelenideElement itemSelector = $(By.id("command")).$(By.id("itemSelector"));
            SelenideElement setButton = $(By.id("command")).$(By.name("setButton"));
            // Action starts here!
            // Select trackers if we have to
            if (trackersToSelect.length != 0) {
                trackerSelector.click();
                for (String trackerName : trackersToSelect) {
                    $$(".ui-multiselect-checkboxes label span").find(exactText(trackerName)).setSelected(true);
                }
                trackerSelector.click();
            }
            // click pencil icon to item show selector frame
            pencilIcon.shouldBe(visible).click();
            // switching to frame
            driver.switchTo().frame("inlinedPopupIframe");
            itemSelector.shouldBe(visible);
            // finding all items by name, finding their checkboxes through their
            // parents, click
            for (String value : values) {
                itemSelector.$(By.xpath("./tbody//tr[.//div[contains(., '"+value+"')]]/td[1]/input[@type='checkbox']")).setSelected(true);
            }
            setButton.shouldBe(visible).click();
            itemSelector.shouldNotBe(visible);
            // switch back to default content
            driver.switchTo().defaultContent();
            okButton.shouldBe(visible).click();
        }

        public void clickFieldOrderTypeButton(String fieldName) {
            $(By.id("queryWidgetOrderByArea")).$(By.className("droppableArea")).$(By.className("selectorContainer"))
                    .$$(By.className("droppableField")).findBy(text(fieldName)).$(By.className("descDroppableButton"))
                    .click();
        }

        public String getFieldOrderTypeButtonText(String fieldName) {
            return $(By.id("queryWidgetOrderByArea")).$(By.className("droppableArea"))
                    .$(By.className("selectorContainer")).$$(By.className("droppableField")).findBy(text(fieldName))
                    .$(By.className("descDroppableButton")).getText();
        }

        public int getFieldCountInGroupByArea() {
            return $(By.id("queryWidgetGroupByArea")).$(By.className("droppableArea"))
                    .$(By.className("selectorContainer")).$$(By.className("droppableField")).size();
        }

        public int getFieldCountInOrderByArea() {
            return $(By.id("queryWidgetOrderByArea")).$(By.className("droppableArea"))
                    .$(By.className("selectorContainer")).$$(By.className("droppableField")).size();
        }

        public void addDefaultFieldToFilterArea(String fieldName) {
            $(By.id("queryWidgetFieldArea")).$(By.className("droppableArea")).$(By.className("addButton")).click();
            $$(By.className("context-menu-submenu")).findBy(exactText("Default Fields")).hover();
            $$(By.className("context-menu-list")).findBy(visible).$$(By.tagName("span")).findBy(exactText(fieldName))
                    .click();
        }

        public void addReferenceFieldToFilterArea(String fieldName) {
            $(By.id("queryWidgetFieldArea")).$(By.className("droppableArea")).$(By.className("addButton")).click();
            $$(By.className("context-menu-submenu")).findBy(exactText("Common Reference Fields")).hover();
            $$(By.className("context-menu-list")).findBy(visible).$$(By.tagName("span")).findBy(exactText(fieldName))
                    .click();
        }

        public void addCustomFieldToFilterArea(String projectName, String trackerName, String fieldName) {
            $(By.id("queryWidgetFieldArea")).$(By.className("droppableArea")).$(By.className("addButton")).click();
            $$(By.className("context-menu-submenu")).findBy(exactText(projectName)).hover();
            $$(By.className("context-menu-submenu")).findBy(exactText(trackerName)).hover();
            $$(By.className("context-menu-list")).findBy(visible).$$(By.tagName("span")).findBy(exactText(fieldName))
                    .click();
        }

        public void addFieldToGroupByArea(String fieldName) {
            $(By.id("queryWidgetGroupByArea")).$(By.className("droppableArea")).$(By.className("addGroupByButton"))
                    .click();
            switch (fieldName) {
                case "Children":
                case "Parent":
                case "Release":
                case "Subject":
                case "Team":
                    $$(By.className("context-menu-submenu")).findBy(exactText("Common Reference Fields")).hover();
                    break;
                default:
                    $$(By.className("context-menu-submenu")).findBy(exactText("Default Fields")).hover();
                    break;
            }
            $$(By.className("context-menu-list")).findBy(visible).$$(By.tagName("span")).findBy(exactText(fieldName))
                    .click();
        }

        public void addFieldToOrderByArea(String fieldName) {
            $(By.id("queryWidgetOrderByArea")).$(By.className("droppableArea")).$(By.className("addOrderByButton"))
                    .click();
            switch (fieldName) {
                case "Children":
                case "Parent":
                case "Release":
                case "Subject":
                case "Team":
                    $$(By.className("context-menu-submenu")).findBy(exactText("Common Reference Fields")).hover();
                    break;
                default:
                    $$(By.className("context-menu-submenu")).findBy(exactText("Default Fields")).hover();
                    break;
            }
            $$(By.className("context-menu-list")).findBy(visible).$$(By.tagName("span")).findBy(exactText(fieldName))
                    .click();
        }

        public boolean isAddLinkVisibleInGroupByArea() {
            return $(By.id("queryWidgetGroupByArea")).$(By.className("droppableArea"))
                    .$(By.className("addGroupByButton")).is(visible);
        }

        public boolean isAddLinkVisibleInOrderByArea() {
            return $(By.id("queryWidgetOrderByArea")).$(By.className("droppableArea"))
                    .$(By.className("addOrderByButton")).is(visible);
        }

        public void removeFieldFromGroupByArea(String fieldName) {
            $(By.id("queryWidgetGroupByArea")).$(By.className("droppableArea")).$(By.className("selectorContainer"))
                    .$$(By.className("droppableField")).findBy(exactText(fieldName))
                    .$(By.className("removeDroppableButton")).click();
        }

        public void removeFieldFromOrderByArea(String fieldName) {
            $(By.id("queryWidgetOrderByArea")).$(By.className("droppableArea")).$(By.className("selectorContainer"))
                    .$$(By.className("droppableField")).findBy(text(fieldName)).$(By.className("removeDroppableButton"))
                    .click();
        }
        
        public void saveReportAs(String reportName) {
        	saveReportAs(reportName, null, null);
        }
        
        public void saveReportAs(String reportName, HashMap<String, List<String>> projectRolesMap) {
        	saveReportAs(reportName, null, projectRolesMap);
        }

        public void saveReportAs(String reportName, String description, HashMap<String, List<String>> projectRolesMap) {
            // we'll need a webDriver to switch to frame content
            WebDriver driver = getWebDriver();
            $(By.className("queryContainer")).$(By.id("saveButton")).shouldBe(visible).click();
            // switching to iFrame
            driver.switchTo().frame("inlinedPopupIframe");
            // set data in Report Properties overlay, handle confirm dialog as default overwrite
            reportProperties.saveReport(reportName, description, projectRolesMap, true);
            // switch back to default content
            driver.switchTo().defaultContent();
        }

        public boolean isSavedReportDisplayed(String reportName) {
            SelenideElement title = $(By.className("queryContainer")).$(By.className("header"))
                    .$(By.className("headerTop")).$(By.className("title"));
            boolean reportNameIsAsExpected = title.$(By.className("name")).shouldBe(visible).getText()
                    .equals(reportName);
            boolean contextMenuIsDisplayed = title.$(By.id("querySettingContextMenu")).shouldBe(visible).isDisplayed();
            return (reportNameIsAsExpected && contextMenuIsDisplayed);
        }

        public String getTextFilterValue() {
            // TODO: currently works only if the last added filter is a Text
            // field
            SelenideElement justAddedFilter = $(By.id("queryWidgetFieldArea")).$(By.className("droppableArea"))
                    .$$(By.tagName("button")).last();
            return justAddedFilter.$(By.className("labelValueText")).getText().substring(5);
            // ignore "like " at the beginning of string
        }

        public void deleteReportIfExists(String reportName) {
            actionBar.clickFindReports();
            boolean reportExists = reportFinder.loadSavedReportFromLastPageIfExists(reportName);
            if (reportExists) {
                reportEditor.deleteCurrentlyLoadedSavedReportByContextMenu();
            }
            actionBar.clickNewReport();
        }

        public void deleteCurrentlyLoadedSavedReportByContextMenu() {
        	$(By.id("querySettingContextMenu")).shouldBe(visible).click();
            $$(By.className("context-menu-list")).findBy(visible).$$(By.tagName("span")).findBy(exactText("Delete"))
                    .click();
            sleep(2000);
            $$(By.className("ui-dialog")).findBy(visible).$(By.className("ui-dialog-buttonset"))
                    .$$(By.tagName("button")).findBy(exactText("Yes")).click();
            // sync to field tree
            $(By.id("treePane")).shouldBe(visible).$(By.id("default")).shouldHave(cssClass("jstree-open"));
        }
        
        public void cancelDeletionOfCurrentlyLoadedSavedReport(){
        	SelenideElement title = $(By.className("queryContainer")).$(By.className("header"))
                    .$(By.className("headerTop")).$(By.className("title"));
            title.$(By.id("querySettingContextMenu")).shouldBe(visible).click();
            $$(By.className("context-menu-list")).findBy(visible).$$(By.tagName("span")).findBy(exactText("Delete"))
                    .click();
            sleep(2000);
            $$(By.className("ui-dialog")).findBy(visible).$(By.className("ui-dialog-buttonset"))
                    .$$(By.tagName("button")).findBy(exactText("Cancel")).click();
        }

        public void editCurrentlyLoadedSavedReportByContextMenu() {
            SelenideElement title = $(By.className("queryContainer")).$(By.className("header"))
                    .$(By.className("headerTop")).$(By.className("title"));
            title.$(By.id("querySettingContextMenu")).shouldBe(visible).click();
            $$(By.className("context-menu-list")).findBy(visible).$$(By.tagName("span")).findBy(exactText("Edit"))
                    .click();
            // sync to field tree
            $(By.id("treePane")).$(By.id("default")).shouldHave(cssClass("jstree-open"));
        }

        public String getNumberFilterValue() {
            // TODO: currently works only if the last added filter is a Number
            // field
            SelenideElement justAddedFilter = $(By.id("queryWidgetFieldArea")).shouldBe(visible)
            		.$(By.className("droppableArea")).shouldBe(visible).$$(By.tagName("button")).last();
            return justAddedFilter.$(By.className("labelValueText")).getText();
        }

        public String getChoiceFilterValue() {
            // TODO: currently works only if the last added filter is a Choice
            // field
            SelenideElement justAddedFilter = $(By.id("queryWidgetFieldArea")).$(By.className("droppableArea"))
                    .$$(By.tagName("button")).last();
            return justAddedFilter.$(By.className("labelValueText")).getText();
        }

        public String getMemberFilterValue() {
            // TODO: currently works only if the last added filter is a Member
            // field
            SelenideElement justAddedFilter = $(By.id("queryWidgetFieldArea")).$(By.className("droppableArea"))
                    .$$(By.tagName("button")).last();
            return justAddedFilter.$(By.className("labelValueText")).getText();
        }

        public String getCbqlStringFromAdvancedEditor() {
            return $(By.className("CodeMirror")).shouldBe(visible).$(By.className("CodeMirror-code")).shouldBe(visible)
                    .$(By.className("CodeMirror-line")).shouldBe(visible).$(By.tagName("span")).text();
        }

        public void setCbqlStringToAdvancedEditor(String cbqlString) {
            JavascriptExecutor executor = ((JavascriptExecutor) getWebDriver());
            String script =
                    "codebeamer.QueryWidget.getAdvancedEdtor().getDoc().setValue(arguments[0]);" +
                            "codebeamer.QueryWidget.getAdvancedEdtor().refresh();";
            executor.executeScript(script, cbqlString);
        }
        
        public void clickReportContextMenuItem(String menuItem) {
        	$(By.id("querySettingContextMenu")).click();
        	$$(By.className("context-menu-list")).findBy(visible)
        	.$$(By.className("context-menu-item")).findBy(exactText(menuItem)).click();
        	$(By.id("queryResultTable")).$(By.id("trackerItems")).$(By.tagName("tbody")).shouldBe(visible);
        	sleep(1500);
        }
        
        public List<String> getReportContextMenuItems() {
        	$(By.id("querySettingContextMenu")).click();
        	List<String> menuItems = $$(By.className("context-menu-list")).findBy(visible)
        	.$$(By.className("context-menu-item")).texts();
        	getWebDriver().navigate().refresh();
        	return menuItems;
        }
        
        public String getReportName() {
        	return $(By.className("queryContainer")).$(By.className("title")).$(By.className("name")).text();
        }
        
        public boolean isContextMenuVisible() {
        	return $(By.className("queryContainer")).$(By.className("title")).$(By.id("querySettingContextMenu")).exists();
        }
    }

    public class FieldTree {

        public boolean textFilterIsDisplayed() {
            return $(By.id("headerDiv")).$(By.id("searchBox_treePane")).isDisplayed();
        }

        public void sendKeysToTextFilter(String text) {
            $(By.id("headerDiv")).$(By.id("searchBox_treePane")).sendKeys(text);
        }

        public boolean isTypeFilterDisplayed() {
            return $(By.id("headerDiv")).$(By.className("fieldTypeSelector")).isDisplayed();
        }

        public void selectOptionInTypeFilterByText(String text) {
            $(By.id("headerDiv")).$(By.className("fieldTypeSelector")).selectOption(text);
        }

        public void selectOptionInTypeFilterByValue(String value) {
            $(By.id("headerDiv")).$(By.className("fieldTypeSelector")).selectOptionByValue(value);
        }

        public boolean isDefaultFieldsFolderDisplayed() {
            return $(By.id("treePane")).$(By.id("default_anchor")).isDisplayed();
        }

        public boolean isCommonReferenceFieldsFolderDisplayed() {
            return $(By.id("treePane")).$(By.id("reference_anchor")).isDisplayed();
        }

        public boolean isDefaultFieldsNodeOpen() {
            return $(By.id("treePane")).$(By.id("default")).shouldHave(cssClass("jstree-open"))
                    .has(cssClass("jstree-open"));
        }

        public boolean isDefaultFieldsNodeClosed() {
            return $(By.id("treePane")).$(By.id("default")).shouldHave(cssClass("jstree-closed"))
                    .has(cssClass("jstree-closed"));
        }

        public void clickOnDefaultFieldsNode() {
            $(By.id("treePane")).$(By.id("default")).$(By.className("jstree-ocl")).click();
        }

        public boolean isReferenceFieldsNodeOpen() {
            return $(By.id("treePane")).$(By.id("reference")).shouldHave(cssClass("jstree-open"))
                    .has(cssClass("jstree-open"));
        }

        public boolean isReferenceFieldsNodeClosed() {
            return $(By.id("treePane")).$(By.id("reference")).shouldHave(cssClass("jstree-closed"))
                    .has(cssClass("jstree-closed"));
        }

        public void clickOnReferenceFieldsNode() {
            $(By.id("treePane")).$(By.id("reference")).$(By.className("jstree-ocl")).click();
        }

        public List<String> getFilteredFieldNames() {
            List<String> filteredDefaultFields = $(By.id("treePane")).$(By.id("default")).$(By.className("jstree-children"))
                    .$$(By.tagName("a")).filterBy(visible).texts();
            List<String> filteredReferenceFields = $(By.id("treePane")).$(By.id("reference"))
                    .$(By.className("jstree-children")).$$(By.tagName("a")).filterBy(visible).texts();
            List<String> filteredFieldNames = new ArrayList<String>();
            filteredFieldNames.addAll(filteredDefaultFields);
            filteredFieldNames.addAll(filteredReferenceFields);
            return filteredFieldNames;
        }

        public List<String> getFieldNamesById(List<String> fieldIds) {
            List<String> fieldNamesById = new ArrayList<String>();
            for (String fieldId : fieldIds) {
                String anchorId = String.format("%1$s_anchor", fieldId);
                fieldNamesById.add($(By.id("treePane")).$(By.id("default")).$(By.className("jstree-children"))
                        .$(By.id(anchorId)).getText());
            }
            return fieldNamesById;
        }

        public boolean isSelectedProjectFoldersDisplayed() {
            boolean result = true;
            List<String> selectedProjectValues = reportEditor.getSelectedProjectValues();
            // sync to visibility of last item
            int lastItem = selectedProjectValues.size() - 1;
            $(By.id("treePane")).$(By.id(String.format("project_%1$s_anchor", selectedProjectValues.get(lastItem))))
                    .shouldBe(visible);
            for (String value : selectedProjectValues) {
                String projectName = String.format("project_%1$s_anchor", value);
                result = result && $(By.id("treePane")).$(By.id(projectName)).isDisplayed();
            }
            return result;
        }

        public boolean isSelectedTrackerFoldersDisplayed() {
            boolean result = true;
            List<String> selectedTrackerValues = reportEditor.getSelectedTrackerValues();
            // sync to visibility of last item
            int lastItem = selectedTrackerValues.size() - 1;
            $(By.id("treePane")).$(By.id(String.format("tracker%1$s_anchor", selectedTrackerValues.get(lastItem))))
                    .shouldBe(visible);
            for (String value : selectedTrackerValues) {
                String trackerName = String.format("tracker%1$s_anchor", value);
                result = result && $(By.id("treePane")).$(By.id(trackerName)).isDisplayed();
            }
            return result;
        }

        public List<String> getCustomFieldNames() {
            List<String> customFieldNames = new ArrayList<String>();
            List<SelenideElement> customFieldListItems = new ArrayList<SelenideElement>();
            List<String> selectedTrackerValues = reportEditor.getSelectedTrackerValues();
            for (String value : selectedTrackerValues) {
                customFieldListItems.addAll(
                        $(By.id("treePane")).$(By.id(String.format("tracker%1$s", value))).$$(By.tagName("li")));
            }
            for (SelenideElement selenideElement : customFieldListItems) {
                customFieldNames.add(selenideElement.$(By.tagName("a")).getText());
            }
            return customFieldNames;
        }
        
        // this method drops all custom fields with same name to filter area
        public void dragAndDropAllSameNameCustomFieldsToFilters(String fieldName, List<String> valuesToSet) {
        	List<String> selectedTrackerValues = reportEditor.getSelectedTrackerValues();
        	List<SelenideElement> customFieldList = new ArrayList<SelenideElement>();
        	for (String value : selectedTrackerValues) {
                customFieldList.addAll($(By.id("treePane")).$(By.id(String.format("tracker%1$s", value)))
                		.$$(By.tagName("li")).filterBy(exactText(fieldName)));
            }
        	SelenideElement target = $(By.id("queryWidgetFieldArea")).$(By.className("droppableArea"));
        	FilterValues values = new FilterValues(); 
        	// TODO: add check if values list's size equals custom field lists's size
        	// TODO: decide what to do when this happens
        	for (int i=0; i < customFieldList.size(); i++) {
				dragAndDrop(customFieldList.get(i), target);
				reportEditor.setDefaultFieldFilterByType(FieldType.Choice,
						values.new ChoiceFilterValue(new String[] { valuesToSet.get(i) }));
			}
        }

        // this method drops the first field with the given name to filter area
        public void dragAndDropFieldToFilterArea(String fieldName) {
            SelenideElement source = $(By.id("treePane")).$$(By.tagName("li")).findBy(exactText(fieldName));
            SelenideElement target = $(By.id("queryWidgetFieldArea")).$(By.className("droppableArea"));
            dragAndDrop(source, target);
        }

        public void dragAndDropFieldToGroupByArea(String fieldName) {
            SelenideElement source = $(By.id("treePane")).$$(By.tagName("li")).findBy(exactText(fieldName));
            SelenideElement target = $(By.id("queryWidgetGroupByArea")).$(By.className("droppableArea"));
            dragAndDrop(source, target);
        }

        public void dragAndDropFieldToOrderByArea(String fieldName) {
            SelenideElement source = $(By.id("treePane")).$$(By.tagName("li")).findBy(exactText(fieldName));
            SelenideElement target = $(By.id("queryWidgetOrderByArea")).$(By.className("droppableArea"));
            dragAndDrop(source, target);
        }

        public void dragAndDropFieldToResultTable(String fieldName) {
            SelenideElement source = $(By.id("treePane")).$$(By.tagName("li")).findBy(exactText(fieldName));
            SelenideElement target = $(By.id("queryResultTable")).$(By.id("trackerItems")).$(By.tagName("thead"));
            dragAndDrop(source, target);
            resultTable.isResultTableDisplayed();
        }
    }

    public class ResultTable {

        public boolean IsExpectedErrorMessageDisplayed() {
            return $(By.id("queryResultTable")).shouldBe(visible).$(By.id("globalMessages")).shouldBe(visible).isDisplayed();
        }

        public boolean isSuccessfulExecution() {
            // no error message is displayed
            return !($(By.id("queryResultTable")).$(By.id("globalMessages")).shouldNotBe(visible).isDisplayed());
        }

        public boolean isResultTableDisplayed() {
            try {
                Wait().withTimeout(60, TimeUnit.SECONDS)
                        .ignoring(NoSuchElementException.class)
                        .until(ExpectedConditions.visibilityOf($(By.xpath(".//div[@id='queryResultTable']//table[@id='trackerItems']/tbody"))));
            } catch (Exception e) {
                return false;
            }
            return true;
        }

        public boolean isNothingFound() {
            return $(By.id("queryResultTable")).$(By.className("scrollable")).shouldBe(visible)
                    .has(exactText("Nothing found to display."));
        }

        public boolean isResultTableGroupedByField(String fieldName) {
            // table should have at least one Group row containing field name
            if (fieldName.equals("Tracker Type")) {
                fieldName = "Type";
            }
            return $(By.id("queryResultTable")).$(By.id("trackerItems")).$(By.tagName("tbody")).shouldBe(visible)
                    .$$(By.className("skipIdent")).filterBy(text(fieldName)).size() >= 1;
        }

        public boolean isResultTableOrderedByField(String fieldName, OrderType orderType) {
            sleep(1000);
            boolean result;
            List<String> fieldValues = new ArrayList<>();
            ElementsCollection rows = $(By.id("queryResultTable")).$(By.id("trackerItems")).$(By.tagName("tbody"))
                    .shouldBe(visible).$$(By.tagName("tr"));
            rows.shouldHave(sizeGreaterThanOrEqual(1));
            // default table fields can not be dropped
            int columnNumber;
            switch (fieldName) {
                case "Summary":
                    columnNumber = 1;
                    break;
                case "Status":
                    columnNumber = 2;
                    break;
                case "Priority":
                    columnNumber = 4;
                    break;
                case "Assigned to":
                    columnNumber = 5;
                    break;
                case "Modified by":
                    columnNumber = 6;
                    break;
                case "Modified at":
                    columnNumber = 7;
                    break;
                case "Submitted by":
                    columnNumber = 8;
                    break;
                case "Submitted at":
                    columnNumber = 9;
                    break;
                default:// field should be dropped behind Summary
                    columnNumber = 2;
                    break;
            }
            // get all rows, dropped field should be at third column, get texts
            for (SelenideElement row : rows) {
                fieldValues.add(row.$$(By.tagName("td")).get(columnNumber).getText());
            }
            // ignoring "--" (null) values
            List<String> toRemove = new ArrayList<String>();
            toRemove.add("--");
            fieldValues.removeAll(toRemove);
            // Date fields need parsing to get correct natural ordering
            // parsing dates
            List<Date> dateValues = new ArrayList<>();
            List<Integer> parentIdValues = new ArrayList<Integer>();
            if (fieldName == "Modified at" || fieldName == "Submitted at" || fieldName == "Assigned at"
                    || fieldName == "End Date" || fieldName == "Start Date") {

                Date parseResult = null;
                int currentYear = Calendar.getInstance().get(Calendar.YEAR);
                for (String fieldValue : fieldValues) {
                    try {
                        Calendar cal = Calendar.getInstance(Locale.ENGLISH);
                        Pattern todayPattern = Pattern.compile("(Today)(.*)");
                        Pattern yesterdayPattern = Pattern.compile("(Yesterday)(.*)");
                        Matcher todayMatcher = todayPattern.matcher(fieldValue);
                        Matcher yesterdayMatcher = yesterdayPattern.matcher(fieldValue);
                        if (todayMatcher.matches() && todayMatcher.groupCount() > 1) {
                            String now = new SimpleDateFormat("MMM dd yyyy", Locale.ENGLISH).format(cal.getTime());
                            fieldValue = now + todayMatcher.group(2);
                        } else if (yesterdayMatcher.matches() && yesterdayMatcher.groupCount() > 1) {
                            cal.add(Calendar.DAY_OF_MONTH, -1);
                            String now = new SimpleDateFormat("MMM dd yyyy", Locale.ENGLISH).format(cal.getTime());
                            fieldValue = now + yesterdayMatcher.group(2);
                        }
                        parseResult = DateUtils.parseDate(fieldValue, Locale.ENGLISH, "MMM dd yyyy HH:mm", "MMM dd HH:mm");
                        //LocalDate localDate = parseResult.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                        cal.setTime(parseResult);
                        //TODO it is not perfect since we can have a date with year 1970 but it's highly unlikely
                        if (cal.get(Calendar.YEAR) == 1970) {
                            cal.set(Calendar.YEAR, currentYear);
                            parseResult = cal.getTime();
                        }
                    } catch (ParseException e) {
                        System.out.print(e.getMessage());
                    }
                    dateValues.add(parseResult);
                }
            } else if (fieldName.equals("Summary")) {
                //because the id is ignored by the sorter we remove the id and then check if the sorting was correct
                ListIterator<String> iter = fieldValues.listIterator();
                while (iter.hasNext()) {
                    String fieldValue = iter.next().replaceFirst("\\[.*\\]\\s", "").replaceFirst("\\n.*", "").trim();
                    iter.set(fieldValue);
                }
            } else if (fieldName.equals("Parent")) {
                //Parent gives ids which cannot be analysed as strings but numbers
                for (String fieldValue : fieldValues) {
                    parentIdValues.add(Integer.valueOf(fieldValue));
                }
            }

            if (!dateValues.isEmpty()) {
                if (orderType == OrderType.Descending) {
                    result = Ordering.natural().reverse().isOrdered(dateValues);
                } else {
                    result = Ordering.natural().isOrdered(dateValues);
                }
            } else if (!parentIdValues.isEmpty()) {
                if (orderType == OrderType.Descending) {
                    result = Ordering.natural().reverse().isOrdered(parentIdValues);
                } else {
                    result = Ordering.natural().isOrdered(parentIdValues);
                }
            } else {
                if (orderType == OrderType.Descending) {
                    result = Ordering.natural().reverse().isOrdered(fieldValues);
                } else {
                    result = Ordering.natural().isOrdered(fieldValues);
                }
            }

            return result;
        }
        
        public boolean isInformationMessageDisplayed() {
        	String expectedInfoMessage = "Please select some conditions or enter cbQL string!";
        	SelenideElement infoMessage = $(By.id("queryResultTable")).shouldBe(visible).$(By.className("information"));
        	return infoMessage.is(visible) && infoMessage.text().equalsIgnoreCase(expectedInfoMessage);
        }
    }

    public class ActionBar {

        public void clickNewReport() {
            if ($(By.id("middleHeaderDiv")).exists()) {
                $(By.id("middleHeaderDiv")).$$(By.tagName("a")).findBy(exactText("New Report")).click();
            } else {
                $(By.className("actionBar")).$$(By.tagName("a")).findBy(exactText("New Report")).click();
            }
            $(By.id("treePane")).$(By.id("default")).shouldHave(cssClass("jstree-open"));
        }

        public void clickFindReports() {
        	if ($(By.id("middleHeaderDiv")).exists()) {
        		$(By.id("middleHeaderDiv")).$$(By.tagName("a")).findBy(exactText("Find Reports")).click();
            } else {
                $(By.className("actionBar")).$$(By.tagName("a")).findBy(exactText("Find Reports")).click();
            }
        }
        
        public void clickEdit() {
        	$(By.className("actionBar")).$$(By.tagName("a")).findBy(exactText("Edit")).click();
        }
        
        public List<String> getActionBarLinks() {
        	if ($(By.id("middleHeaderDiv")).exists()) {
                return $(By.id("middleHeaderDiv")).$$(By.tagName("a")).texts();
            } else {
                return $(By.className("actionBar")).$$(By.tagName("a")).texts();
            }
        }
    }

    public class MyReports {

        public void clickMyReports() {
            $(By.id("queriesAccordion")).shouldBe(visible).click();
        }

        public boolean isMyReportsOpen() {
            return $(By.id("queriesAccordion")).$(By.className("myQueriesAccordion")).is(visible);
        }

        public void clickMyUnresolvedItems() {
            $(By.id("queriesAccordion")).$(By.className("myQueriesAccordion")).$(By.className("predefinedColumn"))
                    .$$(By.tagName("span")).findBy(exactText("My Unresolved Items")).shouldBe(visible).click();
        }

        public void clickSubmittedByMe() {
            $(By.id("queriesAccordion")).$(By.className("myQueriesAccordion")).$(By.className("predefinedColumn"))
                    .$$(By.tagName("span")).findBy(exactText("Submitted by me")).shouldBe(visible).click();
        }
        
        public List<String> getMyStarredReports() {
        	List<String> starredReports = $(By.id("queriesAccordion")).$(By.className("myQueriesAccordion")).$(By.className("starredColumn"))
        	.$(By.className("starredQueries")).$$(By.tagName("li")).texts();
        	for (String reportName : starredReports) {
        		// deleting leading space
				reportName = reportName.substring(1);
			}
        	return starredReports;
        }
    }

    public class ReportFinder {

        // this method defines a complete action which closes the frame
    	// this method needs a driver instance, changeDriver... method defined below is not used
        public void loadSavedReportFromFirstPage(String reportName) {
        	// we'll need a webDriver to switch to frame content
            WebDriver driver = getWebDriver();
            // switching to iFrame
            driver.switchTo().frame("inlinedPopupIframe");
            ElementsCollection col = $(By.id("query")).$(By.tagName("tbody")).$$(By.className("query-link"));
            col.findBy(exactText(reportName)).shouldBe(visible).click();
            // switch back to default content
            driver.switchTo().defaultContent();
            $(By.id("queryResultTable")).$(By.id("trackerItems")).$(By.tagName("tbody")).shouldBe(visible);
        }
    	
    	// this method defines a complete action which closes the frame
    	// this method needs a driver instance, changeDriver... method defined below is not used
        public boolean loadSavedReportFromLastPageIfExists(String reportName) {
        	// we'll need a webDriver to switch to frame content
            WebDriver driver = getWebDriver();
            // switching to iFrame
            driver.switchTo().frame("inlinedPopupIframe");
            $(By.className("displaytag")).$(By.tagName("th")).click();
            ElementsCollection col = $(By.id("query")).$(By.tagName("tbody")).$$(By.className("query-link"));
            if (col.size() == 0) {
                driver.switchTo().defaultContent();
                $(".container-close").click();
                return false;
            }
            SelenideElement reportLink = col.findBy(exactText(reportName));
            if ((reportLink.exists()) && (reportLink.isDisplayed())) {
                reportLink.click();
                driver.switchTo().defaultContent();
                $(By.id("queryResultTable")).$(By.id("trackerItems")).$(By.tagName("tbody")).shouldBe(visible);
                return true;
            } else {
                col.get(0).click();
                driver.switchTo().defaultContent();
                $(By.id("queryResultTable")).$(By.id("trackerItems")).$(By.tagName("tbody")).shouldBe(visible);
                return false;
            }
        }

        // this method defines a complete action which closes the frame
    	// this method needs a driver instance, changeDriver... method defined below is not used
        public void loadSavedReportFromLastPage(String reportName) {
        	// we'll need a webDriver to switch to frame content
            WebDriver driver = getWebDriver();
            // switching to iFrame
            driver.switchTo().frame("inlinedPopupIframe");
            $(By.className("displaytag")).$(By.tagName("th")).click();
            ElementsCollection col = $(By.id("query")).$(By.tagName("tbody")).$$(By.className("query-link"));
            col.findBy(exactText(reportName)).shouldBe(visible).click();
            // switch back to default content
            driver.switchTo().defaultContent();
            $(By.id("queryResultTable")).$(By.id("trackerItems")).$(By.tagName("tbody")).shouldBe(visible);
        }

        // this method defines a complete action which closes the frame
    	// this method needs a driver instance, changeDriver... method defined below is not used
        public void loadSharedReportFromFirstPage(String reportName) {
        	// we'll need a webDriver to switch to frame content
            WebDriver driver = getWebDriver();
            // switching to iFrame
            driver.switchTo().frame("inlinedPopupIframe");
            // uncheck My Reports
            $(By.id("privateQueries")).shouldBe(checked).click();
            ElementsCollection col = $(By.id("query")).$(By.tagName("tbody")).$$(By.className("query-link"));
            col.findBy(exactText(reportName)).shouldBe(visible).click();
            // switch back to default content
            driver.switchTo().defaultContent();
            $(By.id("queryResultTable")).$(By.id("trackerItems")).$(By.tagName("tbody")).shouldBe(visible);
        }

        // this method changes the driver to handle frame content
        // called explicitly from test code
        // must be called once before below methods that check and manipulate Find Reports overlay without closing it
        // close overlay method must be called when finished manipulation of overlay to change driver back to main content
		public void changeDriverToFrameContent() {
			// we'll need a webDriver to switch to frame content
            WebDriver driver = getWebDriver();
            // switching to iFrame
            driver.switchTo().frame("inlinedPopupIframe");
		}
		
        public boolean isMyReportsChecked() {
            return $(By.id("privateQueries")).shouldBe(visible).is(checked);
        }
        
        public boolean isSharedReportsChecked() {
            return $(By.id("sharedQueries")).shouldBe(visible).is(checked);
        }
        
        public void sendKeysToFilter(String filterValue) {
            $(By.id("filter")).shouldBe(visible).sendKeys(filterValue);
            sleep(1500);
        }

        public boolean isFilteringAppliedToReports(String filterValue) {
        	boolean result = true;
        	List<String> reportNames = $(By.id("query")).shouldBe(visible)
        			.$(By.tagName("tbody")).shouldBe(visible).$$(By.className("query-link")).texts();
        	for (String name : reportNames) {
				if(!name.contains(filterValue)) {
					result = false;
					break;
				}
			}
        	return result;
        }
        
        // this method must be called when finished manipulation of Find Reports overlay
        // this method will change back driver to default content
        // do not call after executing a "complete-action" method, they handle it themselves
        public void closeFindReports() {
        	// we'll need a webDriver to switch back to default content
            WebDriver driver = getWebDriver();
            // switch back to default content
            driver.switchTo().defaultContent();
            $(".container-close").click();
        }
        
        public void clickMyReportsCheckBox() {
            $(By.id("privateQueries")).shouldBe(visible).click();
            sleep(1500);
        }
        
        public void clickSharedReportsCheckBox() {
            $(By.id("sharedQueries")).shouldBe(visible).click();
            sleep(1500);
        }
        
        public boolean isReportsListContains(String report) {
        	boolean result = false;
        	List<String> reportNames = $(By.id("query")).shouldBe(visible)
        			.$(By.tagName("tbody")).shouldBe(visible).$$(By.className("query-link")).texts();
        	for (String reportName : reportNames) {
				if(reportName.equalsIgnoreCase(report)) {
					result = true;
					break;
				}
			}
        	return result;
        }
    }
    
    public class ReportProperties {
    	
    	// elements
        private SelenideElement nameTextBox = $(By.id("query")).$(By.id("name"));
        private SelenideElement descriptionTextBox = $(By.id("query")).$(By.id("description"));
        private SelenideElement saveButton = $(By.id("query")).$(By.id("saveButton"));
        private SelenideElement cancelButton = $(By.id("query")).$(By.name("_cancel"));
        private SelenideElement confirmDialogYesButton = $$(By.className("ui-dialog")).findBy(visible)
                .$(By.className("ui-dialog-buttonset")).$$(By.tagName("button")).findBy(exactText("Yes"));
        private SelenideElement confirmDialogCancelButton = $$(By.className("ui-dialog")).findBy(visible)
                .$(By.className("ui-dialog-buttonset")).$$(By.tagName("button")).findBy(exactText("Cancel"));
        private SelenideElement projectSelector = $(By.className("project")).$(By.tagName("button"));
        private SelenideElement roleSelector = $(By.className("role")).$(By.tagName("button"));
        private SelenideElement confirmDialog = $$(By.className("ui-dialog")).findBy(visible);
    	
        // this method changes the driver to handle frame content
        // called explicitly from test code
        // must be called once before below methods that check and manipulate Report Properties overlay without closing it
        // close overlay method must be called when finished manipulation of overlay to change driver back to main content
		public void changeDriverToFrameContent() {
			// we'll need a webDriver to switch to frame content
            WebDriver driver = getWebDriver();
            // switching to iFrame
            driver.switchTo().frame("inlinedPopupIframe");
		}
		
        // this method must be called when finished manipulation of Report Properties overlay
        // this method will change back driver to default content
        // do not call after executing a "complete-action" method, they handle it themselves
        public void closeReportProperties() {
        	// we'll need a webDriver to switch back to default content
            WebDriver driver = getWebDriver();
            // switch back to default content
            driver.switchTo().defaultContent();
            $(".container-close").click();
        }
        
        public void setReportName(String reportName){
        	nameTextBox.shouldBe(visible).setValue(reportName);
        }
        
        public void setReportDescription(String description){
            if (description != null) {
				descriptionTextBox.shouldBe(visible).setValue(description);
			}
        }
        
        public void setReportPermissions(HashMap<String, List<String>> projectRolesMap){
        	if (projectRolesMap != null) {
                for (String projectName : projectRolesMap.keySet()) {
                    projectSelector.click();
                    $$(By.className("ui-multiselect-single")).findBy(visible)
                            .$$(By.tagName("span")).findBy(exactText(projectName)).click();
                    roleSelector.click();
                    for (String roleName : projectRolesMap.get(projectName)) {
                        $$(By.className("ui-multiselect-menu")).findBy(visible)
                                .$$(By.tagName("span")).findBy(exactText(roleName)).click();
                    }
                }
            }
        }
        
        public void handleConfirmationDialog(boolean overWrite){
            if (confirmDialog.exists()) {
            	if (overWrite) {
            		if ((confirmDialogYesButton.exists()) && (confirmDialogYesButton.is(visible))) {
            			confirmDialogYesButton.click();
            		}                    
                } else {
                	if ((confirmDialogCancelButton.exists()) && (confirmDialogCancelButton.is(visible))) {
            			confirmDialogCancelButton.click();
                	}
                }
            }
        }
        
        public void clickSaveButton(){
        	saveButton.shouldBe(visible).click();
        }
        
        public void clickCancelButton(){
        	cancelButton.shouldBe(visible).click();
        }
        
        public void saveReport(String reportName, String description, HashMap<String, List<String>> projectRolesMap, boolean overWrite) {
            setReportName(reportName);
            setReportDescription(description);
            setReportPermissions(projectRolesMap);
            clickSaveButton();
            sleep(2000);
            handleConfirmationDialog(overWrite);
        }
    }
}
