package com.github.nonorc.saladium.selenium.driver;

import java.io.File;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.junit.Assert;
import org.junit.runner.Result;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.nonorc.saladium.core.Saladium;
import com.github.nonorc.saladium.dbunit.worker.AbstractWorker;
import com.github.nonorc.saladium.exception.SaladiumException;
import com.github.nonorc.saladium.exception.UtilitaireException;
import com.github.nonorc.saladium.selenium.BySelec;
import com.github.nonorc.saladium.utils.FileUtil;
import com.github.nonorc.saladium.utils.PropsUtils;

import cucumber.api.PendingException;

public abstract class SaladiumDriver extends WebDriverRemote implements IDriver {

    public SaladiumDriver(URL url, Capabilities dc) {
        super(url, dc);
    }

    protected WebDriverWait wait = new WebDriverWait(this, Saladium.EXPLICITLY_WAIT, Saladium.SLEEP_IN_MILLIS);
    private final Logger logger = LoggerFactory.getLogger(SaladiumDriver.class);

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#isElementPresent(org.openqa.selenium.By, long, long)
     */
    @Override
    public boolean isElementPresent(By locator) {
        boolean retour = false;
        try {
            wait.until(ExpectedConditions.presenceOfElementLocated(locator));
            retour = true;
        } catch (Exception e) {
            logger.debug("L'élément" + locator + " est absent : " + e.getMessage());
        }
        return retour;
    }

    @Override
    public boolean isElementImmediatPresent(By by) {
        return this.findElements(by).size() != 0;
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#confirmation()
     */
    @Override
    public String confirmation() {
        Alert alert = this.switchTo().alert();
        String alertText = alert.getText();
        alert.accept();
        return alertText;
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#accueil()
     */
    @Override
    public void accueil() {
        String url = "";
        try {
            url = PropsUtils.getProperties().getProperty("application.url");
            this.logger.info("Accueil URL : " + url);
            Assert.assertTrue(true);
        } catch (UtilitaireException e) {
            Assert.fail("Impossible d'accéder à la variable application.url dans le fichier saladium.properties");
        }
        try {
            this.get(url);
            // this.navigate().to(url);
        } catch (Exception ex) {
            Assert.fail("Problème de navigation");
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#raffraichir(java.lang.String, java.lang.String)
     */
    @Override
    public void raffraichir(String type, String selector) {
        wait.until(ExpectedConditions.refreshed(
            ExpectedConditions.presenceOfElementLocated(
                BySelec.get(type, selector))));
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#html5Erreur(java.lang.String)
     */
    @Override
    public void html5Erreur(String selector) {
        this.logger.debug("Appel du test messageErreur()");
        JavascriptExecutor js = this;
        Boolean s = (Boolean) js.executeScript("return document.querySelector('"
            + selector + "').validity.valid");
        if (!s) {
            Assert.fail(selector + " " + s
                + " Un message de sevérité ERROR devrait être présent!");
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#cliquer(java.lang.String, java.lang.String)
     */
    @Override
    public void cliquer(String type, String selector) {
        this.logger.info("cliquer type:" + type + " selector:" + selector);
        try {
            By locator = BySelec.get(type, selector);
            wait.until(ExpectedConditions.elementToBeClickable(locator)).click();
        } catch (TimeoutException e) {
            e.printStackTrace();
            String pathScreenShot = takeScreenShot();
            Assert.fail("Cliquer impossible ! (type" + type + ", selector" + selector + ") pathScreenShot=" + pathScreenShot);
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#remplir(java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public void remplir(String type, String selector, String valeur) {
        this.logger.info("remplir type:" + type + " selector:" + selector + " valeur:" + valeur);
        WebElement elt = null;
        By locator = BySelec.get(type, selector);
        try {
            elt = wait.until(ExpectedConditions.presenceOfElementLocated(locator));
        } catch (TimeoutException e) {
            String pathScreenShot = takeScreenShot();
            Assert.fail("Remplir impossible ! Elément non présent : (type" + type + ", selector" + selector + ", valeur" + valeur + ") pathScreenShot=" + pathScreenShot);
        }
        try {
            wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
            elt.clear();
            elt.sendKeys(valeur);
        } catch (TimeoutException e) {
            String pathScreenShot = takeScreenShot();
            Assert.fail("Remplir impossible ! Elément invisible ! (type" + type + ", selector" + selector + ", valeur" + valeur + ") pathScreenShot=" + pathScreenShot);
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#selectionner(java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public void selectionner(String type, String selector, String valeur) {
        this.logger.info("selectionner(type" + type + ", selector" + selector + ", valeur" + valeur + ")");
        try {
            By locator = BySelec.get(type, selector);
            WebElement elem = wait.until(ExpectedConditions.presenceOfElementLocated(locator));
            String tagName = elem.getTagName();
            if (tagName.equals("select")) {
                Select select = new Select(elem);
                // List<WebElement> options = select.getAllSelectedOptions();
                // Iterator<WebElement> it = options.iterator();
                // while (it.hasNext()) {
                // WebElement webElement = it.next();
                // if ("auto".equals(webElement.getCssValue("z-index"))) {
                // if (!webElement.isDisplayed()) {
                // System.out.println("################### !isDisplayed()");
                // }
                // System.out.println("################### cssValue " + webElement.getCssValue("z-index"));
                // }
                // }
                select.selectByVisibleText(valeur);
                // Thread.sleep(1000);
            } else {
                elem.click();
                List<WebElement> children = elem.findElements(
                    By.xpath("//div[@class='listComboBoxElement']/li"));
                Iterator<WebElement> it = children.iterator();
                boolean flag = false;
                do {
                    WebElement webElement = it.next();
                    if (webElement.getText().startsWith(valeur)) {
                        if (webElement.isDisplayed()) {
                            webElement.click();
                            flag = true;
                        }
                    }
                } while (!flag && it.hasNext());
                if (!flag) {
                    Assert.fail("Impossible de trouver la valeur :" + valeur
                        + " pour le champs :" + type + ":" + selector);
                }
            }
        } catch (NoSuchElementException | TimeoutException e) {
            String pathScreenShot = takeScreenShot();
            Assert.fail("Sélection impossible ! (type" + type + ", selector" + selector + ", valeur" + valeur + ") pathScreenShot=" + pathScreenShot);
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#etreDesactiver(java.lang.String, java.lang.String)
     */
    @Override
    public void etreDesactiver(String type, String selector) {
        this.logger.info("etreDesactiver(String id)");
        if (this.findElement(BySelec.get(type, selector)).isEnabled()) {
            Assert.fail("l'élément devrait être désactivé!");
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#etreActiver(java.lang.String, java.lang.String)
     */
    @Override
    public void etreActiver(String type, String selector) {
        this.logger.info("etreActiver(String id)");
        if (!this.findElement(BySelec.get(type, selector)).isEnabled()) {
            Assert.fail("l'élément devrait être activé!");
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#etrePlein(java.lang.String, java.lang.String)
     */
    @Override
    public void etrePlein(String type, String selector) {
        this.logger.info("etrePlein(String id)");
        By locator = BySelec.get(type, selector);
        WebElement elem = wait.until(ExpectedConditions.presenceOfElementLocated(locator));

        if (elem.getAttribute("value") != null) {
            if (elem.getAttribute("value").toString().trim().isEmpty()) {
                Assert.fail("l'élément ne devrait pas être vide!");
            }
        } else {
            if (elem.getText().trim().isEmpty()) {
                Assert.fail("l'élément ne devrait pas être vide!");
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#etreVide(java.lang.String, java.lang.String)
     */
    @Override
    public void etreVide(String type, String selector) {
        this.logger.info("etrePlein(String id)");
        WebElement elem = this.findElement(BySelec.get(type, selector));
        if (elem.getAttribute("value") != null) {
            if (!elem.getAttribute("value").toString().trim().isEmpty()) {
                Assert.fail("l'élément " + type + ":" + selector
                    + " devrait être vide!");
            }
        } else {
            if (!elem.getText().trim().isEmpty()) {
                Assert.fail("l'élément " + type + ":" + selector
                    + " devrait être vide!");
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#etreVisible(java.lang.String, java.lang.String)
     */
    @Override
    public void etreVisible(String type, String selector) {
        this.logger.info("etreVisible(String type, String selector)");
        try {
            wait.until(ExpectedConditions.visibilityOfElementLocated(BySelec.get(type, selector)));
        } catch (Exception e) {
            String pathScreenShot = takeScreenShot();
            this.logger.debug("L'élément devrait être visible : " + e.getMessage());
            Assert.fail("L'élément devrait être visible ! pathScreenShot=" + pathScreenShot);
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#etreInvisible(java.lang.String, java.lang.String)
     */
    @Override
    public void etreInvisible(String type, String selector) {
        this.logger.info("etreInvisible(type:" + type + " selector:" + selector + ")");
        try {
            wait.until(ExpectedConditions.invisibilityOfElementLocated(BySelec.get(type, selector)));
        } catch (Exception e) {
            String pathScreenShot = takeScreenShot();
            this.logger.debug("L'élément devrait être invisible : " + e.getMessage());
            Assert.fail("L'élément devrait être invisible ! pathScreenShot=" + pathScreenShot);
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#cocher(java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public void cocher(String type, String selector, String valeur) {
        this.logger.info("cocher(String type:" + type + " selector:" + selector + " valeur:" + valeur);
        try {
            By locator = BySelec.get(type, selector);
            if ("id".equalsIgnoreCase(type)) {
                ExpectedCondition<WebElement> condition = ExpectedConditions.elementToBeClickable(locator);
                WebElement elt = wait.until(condition);
                elt.click();
            } else {
                ExpectedCondition<List<WebElement>> conditions = ExpectedConditions.presenceOfAllElementsLocatedBy(locator);
                List<WebElement> elts = wait.until(conditions);
                boolean clicEffectue = false;
                String valeurCase = "";
                Iterator<WebElement> it = elts.iterator();
                while (it.hasNext()) {
                    WebElement cb = it.next();
                    valeurCase = cb.getAttribute("value").toString();
                    if (valeurCase.equals(valeur)) {
                        cb.click();
                        clicEffectue = true;
                    }
                }
                if (!clicEffectue) {
                    String pathScreenShot = takeScreenShot();
                    Assert.fail("La case à cocher n'est pas cliquable ! valeur attendue=" + valeur + "; valeur case à cocher=" + valeurCase + " pathScreenShot=" + pathScreenShot);
                }
            }
        } catch (TimeoutException e) {
            String pathScreenShot = takeScreenShot();
            Assert.fail("La case à cocher n'est pas cliquable !" + e.getMessage() + " pathScreenShot=" + pathScreenShot);
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#etreSelectionne(java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public void etreSelectionne(String type, String selector, String valeur) {
        WebElement elem = this.findElement(BySelec.get(type, selector));
        String tagName = elem.getTagName();
        if (tagName.equals("select")) {
            Select listProfile = new Select(elem);
            if (!listProfile.getFirstSelectedOption().getText().equals(valeur)) {
                Assert.fail("la valeur de l'élément ne correspond pas à la valeur " + valeur + "!");
            }
        } else {
            WebElement item = elem.findElement(By.cssSelector("li > div > div:first-child()"));
            if (!item.getText().equals(valeur)) {
                Assert.fail("la valeur de l'élément ne correspond pas à la valeur " + valeur + "!");
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#hasValue(java.lang.String, java.lang.String, int)
     */
    @Override
    public void avoirValeur(String type, String selector, int attendu) {
        WebElement elem = this.findElement(BySelec.get(type, selector));
        Assert.assertTrue("Le champs input " + type + ":" + selector + " ne contient pas la valeur " +
            attendu, elem.getAttribute("value").equals(String.valueOf(attendu)));
    }

    /*
     * (non-Javadoc)
     * @see saladium.selenium.driver.IDriver#hasClass(java.lang.String, java.lang.String, int)
     */
    @Override
    public void avoirClasse(String type, String selector, String value) {
        WebElement elem = this.findElement(BySelec.get(type, selector));
        Assert.assertTrue("L'élément " + type + ":" + selector + " ne contient pas la class " +
            value, elem.getAttribute("class").matches(value));
    }

    @Override
    public void accepterPopupWindow() {
        this.switchTo().alert().accept();
    }

    @Override
    public void refuserPopupWindow() {
        this.switchTo().alert().dismiss();
    }

    /**
     * Méthode switchOnglet.
     */
    @Override
    public void changerOnglet(String titreOnglet) {
        Set<String> windows = this.getWindowHandles();
        for (String window : windows) {
            this.switchTo().window(window);
            if (this.getCurrentUrl().contains(titreOnglet)) {
                return;
            }
        }
    }

    /**
     * Compte le nombre de ligne dans une table.
     * @param type
     * @param selector
     * @param attendu
     */
    public void countRowsTable(String type, String selector, int attendu) {
        logger.trace("countRowsTable(String id)");
        try {
            int count = this.countRowsTable(type, selector);
            logger.info("Nombre de ligne " + count);
            if (attendu != count) {
                String pathScreenShot = takeScreenShot();
                Assert.fail("il devrait y avoir " + attendu
                    + " ligne(s) dans le tableau alors qu'il y en a : "
                    + count + " pathScreenShot=" + pathScreenShot);
            }
        } catch (TimeoutException e) {
            String pathScreenShot = takeScreenShot();
            Assert.fail("countRowsTable impossible ! (type" + type + ", selector" + selector + ", attendu" + attendu + ")"
                + e.getMessage() + " pathScreenShot=" + pathScreenShot);
        }
    }

    /**
     * Méthode countRowsTable. Prévoir une DIV avant la déclaration du tableau <display:table />.
     * @param type
     * @param selector
     * @return
     */
    private int countRowsTable(String type, String selector) throws TimeoutException {
        int retour = 0;
        try {
            By locator = By.cssSelector(".pagebanner");
            WebElement pagebanner = wait.until(ExpectedConditions.presenceOfElementLocated(locator));
            String pageNumber = pagebanner.getText().trim().split(" ")[0];
            if (!pageNumber.equals("Aucun")) {
                return Integer.valueOf(pageNumber);
            }
        } catch (TimeoutException e) {
            String pathScreenShot = takeScreenShot();
            Assert.fail("countRowsTable impossible ! (type" + type + ", selector" + selector + ")"
                + e.getMessage() + " pathScreenShot=" + pathScreenShot);
        }
        return retour;
    }

    /**
     * Méthode takeScreenShot.
     * @throws SaladiumException
     */
    public String takeScreenShot() {
        String pathScreenShot = "";
        try {
            String capturePath = AbstractWorker.capturePath;
            File scrFile = ((TakesScreenshot) this).getScreenshotAs(OutputType.FILE);
            FileUtil.upload(scrFile.getAbsolutePath(), capturePath + "/" + scrFile.getName());
            pathScreenShot = capturePath + "/" + scrFile.getName();
            this.logger.info("ScreenShot effectué" + pathScreenShot);
        } catch (Exception e1) {
            this.logger.info("takeScreenShot échoué");
            e1.printStackTrace();
        }
        // try {
        // // déconnexion
        // By decoLocator = By.cssSelector("#logoff > a");
        // WebElement webElement = wait.until(ExpectedConditions.elementToBeClickable(decoLocator));
        // webElement.click();
        // } catch (TimeoutException e) {
        // Assert.fail("Tentative de forçage de déconnexion après un sreenshot échouée");
        // }
        // A activer si vous souhaitez arrêter les tests dès le premier screenshot effectué (idéal en mode dev ou debug)
        RunNotifier notifier = new RunNotifier();
        Result result = new Result();
        RunListener listener = result.createListener();
        notifier.addFirstListener(listener);
        notifier.pleaseStop();
        return pathScreenShot;
    }
    
    public void todo() {
        throw new PendingException();
    }

    /**
     * Méthode cocherParLabel.
     * @param label
     */
    public void cocherParLabel(String label) {
        cocher(null, null, label);
    }

    /**
     * Méthode cocher sans valeur, uniquement avec l'id.
     * @param type : id de la case à cocher.
     * @param selector
     */
    public void cocherParIdRadioButton(String selector) {
        cocher("id", selector, null);
    }
}