/*
 * Copyright (C) 2005-2014 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */
package org.alfresco.po;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.alfresco.po.exception.ElementExpectedConditions;
import org.alfresco.po.exception.PageOperationException;
import org.alfresco.po.exception.PageRenderTimeException;
import org.alfresco.po.share.FactoryPage;
import org.alfresco.po.share.util.PageUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Action;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.beans.factory.annotation.Autowired;

import ru.yandex.qatools.htmlelements.element.HtmlElement;

import com.google.common.base.Predicate;

/**
 * Abstract that holds all common functions and helper method
 * that applies to components of a page. These building blocks
 * constructs the page and interacts with the dom using selenium.
 * 
 * @author Michael Suzuki
 *
 */
public abstract class PageElement extends HtmlElement implements WebDriverAware
{
    private Log logger = LogFactory.getLog(PageElement.class);
    private static final String LOCATOR_REQUIRED_ERR_MSG = "A locator is required";
	protected long defaultWaitTime = 4000;
    protected long maxPageLoadingTime = 30000;
    @Autowired
    protected FactoryPage factoryPage;
    protected WebDriver driver;
    public void setWebDriver(WebDriver driver) 
    { 
        this.driver = driver; 
    }
    public WebDriver getWebDriver()
    {
        return driver;
    }
    /**
     * Checks if page event action completed. For further information see:
     * https:
     * //dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview
     * .html
     * 
     * @return String time took to complete event
     */
    public String domEventCompleted()
    {
        String js = "try{window.performance = window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance || {};"
                    + "return(parseInt(window.performance.timing.domContentLoadedEventEnd)-parseInt(window.performance.timing.navigationStart));}catch(e){}";
                        Object val = ((JavascriptExecutor) driver).executeScript(js);
        return val.toString();
    }

    public void navigate(String ...url)
    {
        driver.navigate().to(url[0]);
    }
    
    public boolean isDisplayed(WebElement element)
    {
        try
        {
            return element.isDisplayed();
        }
        catch(Exception e)
        {
            //Ignore.
        }
        return false;
    }
    public boolean isDisplayed(By by)
    {
        try
        {
            return driver.findElement(by).isDisplayed();
        }
        catch(Exception e)
        {
        }
        return false;
    }
    
    public HtmlPage getCurrentPage() 
    {
        return factoryPage.getPage(driver);
    }
    /**
     * Basic render that checks if the page has rendered.
     *
     * @param timer {@link RenderTime}
     */
    public void basicRender(RenderTime timer)
    {
        try
        {
            timer.start();
            isRenderComplete(timer.timeLeft());
        }
        finally
        {
            timer.end();
        }
    }
    /**
     * Verify if loading of the page is complete using java script to validate state of the HTML DOM.
     * 
     * @param waitTime max time to look for an element
     * @return true if page has been loaded
     */
    public boolean isRenderComplete(final long waitTime)
    {
        FluentWait<String> fluentWait = new FluentWait<String>("complete");
        fluentWait.pollingEvery(100, TimeUnit.MILLISECONDS);
        fluentWait.withTimeout(waitTime, TimeUnit.MILLISECONDS);
        try
        {
            fluentWait.until(new Predicate<String>()
            {
                @Override
                public boolean apply(String input)
                {
                    String response = (String) executeJavaScript("return document.readyState;");
                    return response.equals(input);
                }
            });
            return true;
        }
        catch (TimeoutException te)
        {
        }
        return false;
    }
    /**
     * Injects java script directly to the page to invoke a change on the page's java script
     * 
     * @param js String script
     */
    public Object executeJavaScript(final String js)
    {
        if(js == null || js.isEmpty())
        {
            throw new IllegalArgumentException("JavaScript is required");
        }
        return ((JavascriptExecutor) driver).executeScript(js);
    }
    /**
     * Execute java script in resepect of object (i.e. WebElement)  
     * @param js String script
     */
    public Object executeJavaScript(final String js, Object...args)
    {
        if(js == null || js.isEmpty())
        {
            throw new IllegalArgumentException("JS script is required");
        }
        return ((JavascriptExecutor) driver).executeScript(js, args);
    }
    /**
     * Helper method to find a {@link WebElement} with a time limit in milliseconds. During the wait period it will check for the element every 100 millisecond.
     * 
     * @param by {@link By} criteria to search by
     * @param limit millisecond time limit
     * @return {@link WebElement} HTML element
     */
    public WebElement findAndWait(final By by, final long limit)
    {
        return findAndWait(by, limit, 100);
    }
    /**
     * Helper method to find a {@link WebElement} with a time limit in milliseconds within the page element. 
     * During the wait period it will check for the element every 100 millisecond.
     * 
     * @param by {@link By} criteria to search by
     * @param limit millisecond time limit
     * @return {@link WebElement} HTML element
     */
    public WebElement findAndWaitInNestedElement(final By by, final long limit)
    {
        return findAndWaitInNestedElement(by, limit, 100);
    }

    /**
     * Helper method to find a {@link WebElement} with a time limit in milliseconds. During the wait period it will check for the element every 100 millisecond.
     * 
     * @param by {@link By} criteria to search by
     * @param limit time limit
     * @param interval polling frequency
     * @return {@link WebElement} HTML element
     */
    public WebElement findAndWait(final By by, final long limit, final long interval)
    {
        FluentWait<By> fluentWait = new FluentWait<By>(by);
        fluentWait.pollingEvery(interval, TimeUnit.MILLISECONDS);
        fluentWait.withTimeout(limit, TimeUnit.MILLISECONDS);
        fluentWait.until(new Predicate<By>()
        {
            public boolean apply(By by)
            {
                try
                {
                    return driver.findElement(by).isDisplayed();
                }
                catch (NoSuchElementException ex)
                {
                    return false;
                }
            }
        });
        return driver.findElement(by);
    }
    /**
     * Helper method to find a {@link WebElement} with a time limit in milliseconds. During the wait period it will check for the element every 100 millisecond.
     * 
     * @param by {@link By} criteria to search by
     * @param limit time limit
     * @param interval polling frequency
     * @return {@link WebElement} HTML element
     */
    public WebElement findAndWaitInNestedElement(final By by, final long limit, final long interval)
    {
        FluentWait<By> fluentWait = new FluentWait<By>(by);
        fluentWait.pollingEvery(interval, TimeUnit.MILLISECONDS);
        fluentWait.withTimeout(limit, TimeUnit.MILLISECONDS);
        fluentWait.until(new Predicate<By>()
        {
            public boolean apply(By by)
            {
                try
                {
                    return getWrappedElement().findElement(by).isDisplayed();
                }
                catch (NoSuchElementException ex)
                {
                    return false;
                }
            }
        });
        return getWrappedElement().findElement(by);
    }
    /**
     * Helper method to find and return a slow loading {@link WebElement}.
     * 
     * @param criteria By search criteria
     * @return {@link WebElement} HTML element
     */
    public WebElement findAndWait(final By criteria)
    {
        return findAndWait(criteria, defaultWaitTime);
    }
    
    public long getDefaultWaitTime()
    {
        return defaultWaitTime;
    }

    /**
     * Wait until the element is visible for the specified amount of time.
     * @param locator CSS Locator
     * @param timeOutInSeconds Timeout In Seconds
     */
    public void waitForElement(By locator, long timeOutInSeconds)
    {
        if(locator == null)
        {
            throw new IllegalArgumentException(LOCATOR_REQUIRED_ERR_MSG);
        }
        WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds);
        wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
    }
    /**
     * Wait until the Clickable of given Element for given seconds.
     * 
     * @param locator CSS Locator
     * @param timeOutInSeconds Timeout In Seconds
     */
    public void waitUntilElementClickable(By locator, long timeOutInSeconds)
    {
        if(locator == null)
        {
            throw new IllegalArgumentException(LOCATOR_REQUIRED_ERR_MSG);
        }
        WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds);
        wait.until(ExpectedConditions.elementToBeClickable(locator));
    }
    /**
     * Wait until the invisibility of given Element for given seconds.
     * @param locator CSS Locator
     * @param timeOutInSeconds Timeout In Seconds
     */
    public void waitUntilElementDisappears(By locator, long timeOutInSeconds)
    {
        if(locator == null)
        {
            throw new IllegalArgumentException(LOCATOR_REQUIRED_ERR_MSG);
        }
        WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds);
        wait.until(ExpectedConditions.invisibilityOfElementLocated(locator));
    }
    /**
     * Wait until the invisibility of given Element for given seconds.
     * 
     * @param locator CSS Locator
     * @param text - The Text to find in the Locator
     * @param timeOutInSeconds Timeout In Seconds
     */
    public void waitUntilNotVisible(By locator, String text, long timeOutInSeconds)
    {
        if(locator == null)
        {
            throw new IllegalArgumentException(LOCATOR_REQUIRED_ERR_MSG);
        }
        if(text == null || text.isEmpty())
        {
            throw new IllegalArgumentException("Text value is required");
        }
        WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds);
        wait.until(ExpectedConditions.invisibilityOfElementWithText(locator, text));
    }
    /**
     * Wait until the visibility of given Element for given seconds.
     * 
     * @param locator CSS Locator
     * @param timeOutInSeconds Timeout In Seconds
     */
    public void waitUntilElementPresent(By locator, long timeOutInSeconds)
    {
        if (locator == null)
        {
            throw new IllegalArgumentException(LOCATOR_REQUIRED_ERR_MSG);
        }
        WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds);
        wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(locator));
    }
    /**
     * Wait Until element successfully deleting from DOM.
     * 
     * @param locator - CSS Locator
     * @param timeOutInSeconds - Timeout In Seconds
     */
    public void waitUntilElementDeletedFromDom(By locator, long timeOutInSeconds)
    {
        WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds);
        try
        {
            wait.until(ExpectedConditions.stalenessOf(driver.findElement(locator)));
        }
        catch (NoSuchElementException e) { /* if element already not in DOM! */}
    }
    /**
     * Wait document.readyState to return completed.
     * @param timeOutInSeconds time duration
     */
    public void waitForPageLoad(long timeOutInSeconds)
    {
        ExpectedCondition<Boolean> expectation = new ExpectedCondition<Boolean>()
        {
            public Boolean apply(WebDriver driver)
            {
                return ((JavascriptExecutor) driver).executeScript("return document.readyState").equals("complete");
            }
        };
        try
        {
            new WebDriverWait(driver, timeOutInSeconds).until(expectation);
        }
        catch (TimeoutException exception) { }
    }
    /**
     * Helper method to find and return a slow loading collection of {@link WebElement}.
     * 
     * @param criteria {@link By} search criteria
     * @param waitTime milliseconds to wait
     * @return Collection of {@link WebElement} HTML elements
     */
    public List<WebElement> findAndWaitForElements(final By criteria, final long waitTime)
    {
        if(criteria == null)
        {
            throw new IllegalArgumentException("By criteria is required");
        }
        findAndWait(criteria, waitTime);
        return driver.findElements(criteria);
    }
    /**
     * Helper method to find and return a slow loading collection of {@link WebElement}.
     * 
     * @param criteria {@link By} search criteria
     * @return Collection of {@link WebElement} HTML elements
     */
    public List<WebElement> findAndWaitForElements(final By criteria)
    {
        if(criteria == null)
        {
            throw new IllegalArgumentException("By criteria is required");
        }
        findAndWait(criteria, getDefaultWaitTime());
        return driver.findElements(criteria);
    }
    /**
     * Helper method to find and return a slow loading {@link WebElement} by id.
     * 
     * @param id String identifier
     * @return {@link WebElement} HTML element
     */
    public WebElement findAndWaitById(final String id)
    {
        if(StringUtils.isEmpty(id))
        {
            throw new IllegalArgumentException("element id is required");
        }
        By criteria = By.id(id);
        findAndWait(criteria, defaultWaitTime);
        return driver.findElement(criteria);
    }
    /**
     * Drag the source element and drop into target element.
     * 
     * @param source - Source Element
     * @param target - Target Element
     */
    public void dragAndDrop(WebElement source, WebElement target)
    {
        PageUtils.checkMandotaryParam("source element", source);
        PageUtils.checkMandotaryParam("target element", target);
        Actions builder = new Actions(driver);   
        Action dragAndDrop = builder.clickAndHold(source)
           .moveToElement(target)
           .release(target)
           .build();      
        dragAndDrop.perform();
    }
    /**
     * Recreating the action of hovering over a particular HTML element on a page.
     * 
     * @param element {@link WebElement} target
     */
    public void mouseOver(WebElement element)
    {
        if(element == null)
        {
            throw new IllegalArgumentException("A web element is required");
        }
        new Actions(driver).moveToElement(element).perform();
    }
    /**
     * This function will return list of visible elements found with the specified selector
     * 
     * @param selector {@link By} identifier
     * @return {@link List} of {@link WebElement}
     */
    public List<WebElement> findDisplayedElements(By selector)
    {
        PageUtils.checkMandotaryParam("Locator", selector);
        List<WebElement> elementList = driver.findElements(selector);
        List<WebElement> displayedElementList = new ArrayList<WebElement>();
        for (WebElement elementSelected : elementList)
        {
            if (elementSelected.isDisplayed())
            {
                displayedElementList.add(elementSelected);
            }
        }
        return displayedElementList;
    }
    public WebElement findFirstDisplayedElement(By selector)
    {
        try
        {
            List<WebElement> elementList = findDisplayedElements(selector);
            if(elementList != null && elementList.size() > 0)
            {
                return elementList.get(0);
            }
        } 
        catch (NoSuchElementException e)
        {
           throw new  NoSuchElementException("Not able find element for give locator " + e);
        }
        throw new NoSuchElementException("Not able find any displayed elemment for given locator " + selector.toString());
    }
    public void waitUntilVisible(By locator, String text, long timeOutInSeconds)
    {
        if (locator == null)
        {
            throw new IllegalArgumentException(LOCATOR_REQUIRED_ERR_MSG);
        }
        if(text == null || text.isEmpty())
        {
            throw new IllegalArgumentException("Text value is required");
        }
        WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds);
        wait.until(ExpectedConditions.textToBePresentInElementLocated(locator, text));
    }
    /**
     * Double click on an element
     * 
     * @param element {@link WebElement}
     */
    public void doubleClickOnElement(WebElement element)
    {
        PageUtils.checkMandotaryParam("doubleclick element", element);
        Actions builder = new Actions(driver);
        Action doubleClick = builder.doubleClick(element).build();
        doubleClick.perform();
    }
    /**
     * Wait until the invisibility of given Element for given seconds.
     * 
     * @param locator CSS Locator
     * @param text - The Text to find in the Locator
     * @param timeOutInSeconds Timeout In Seconds
     */
    public void waitUntilNotVisibleWithParitalText(By locator, String text, long timeOutInSeconds)
    {
        if (locator == null)
        {
            throw new IllegalArgumentException(LOCATOR_REQUIRED_ERR_MSG);   
        }
        if(text == null || text.isEmpty())
        {
            throw new IllegalArgumentException("Text value is required");
        }
        WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds);
        wait.until(ElementExpectedConditions.invisibilityOfElementWithPartialText(driver, locator, text));
    }
    /**
     * Helper method to find a {@link WebElement} with a time limit in
     * milliseconds. During the wait period it will check for the element every
     * 100 millisecond. If the element is not displayed, refresh the page.
     *
     * @param by {@link By} criteria to search by
     * @return {@link WebElement}
     */
    public WebElement findAndWaitWithRefresh(final By by, final long limit)
    {
        if(null == by)
        {
            throw new IllegalArgumentException("A search By criteria is required");
        }
        FluentWait<By> fluentWait = new FluentWait<By>(by);
        fluentWait.pollingEvery(100, TimeUnit.MILLISECONDS);
        fluentWait.withTimeout(limit, TimeUnit.MILLISECONDS);
        fluentWait.ignoring(NoSuchElementException.class);
        fluentWait.until(new Predicate<By>()
        {
            public boolean apply(By by)
            {
                try
                {
                    return driver.findElement(by).isDisplayed();
                }
                catch (NoSuchElementException ex)
                {
                    driver.navigate().refresh();
                    return false;
                }
            }
        });
        return driver.findElement(by);
    }
    /**
     * Returns true if the element is displayed else false.
     * @param locator {@link By} query
     * @return boolean true if displayed
     */
    public boolean isElementDisplayed(By locator)
    {
        if(locator == null)
        {
            throw new IllegalArgumentException("Element locator strategy is required");
        }
        try
        {
            return driver.findElement(locator).isDisplayed();
        }
        catch (NoSuchElementException nse)
        {
            // no log needed due to negative cases.
        }
        catch (TimeoutException toe)
        {
            // no log needed due to negative cases.
        }
        catch (StaleElementReferenceException ste)
        {
            // no log needed due to negative cases.
        }
        
        return false;
    }
    /**
     * Recreating the action of click on a sub element that should be displayed once parent element is selected.
     * The coordinates provided specify the offset from the top-left corner of these elements.
     *
     * @param parentElement {@link WebElement} parentElement
     * @param parentXOffset {@link int} x coordinate for parentElement
     * @param parentYOffset {@link int} y coordinate for parentElement
     * @param subElement {@link WebElement} subElement
     * @param subXOffset {@link int} x coordinate for subElement
     * @param subYOffset {@link int} y coordinate for subElement
     */
    public void clickOnSubElementOffSet(WebElement parentElement, int parentXOffset, int parentYOffset, WebElement subElement, int subXOffset, int subYOffset)
    {
        if (parentElement == null || subElement == null)
        {
            throw new IllegalArgumentException("A web elements are required");
        }
        else
        {
            (new Actions(this.driver)).moveToElement(parentElement, parentXOffset, parentYOffset).moveToElement(subElement, subXOffset, subYOffset).click()
                    .perform();
        }
    }
    public void clearAndType(By by, String value)
    {
        if(by == null)
        {
            throw new IllegalArgumentException("Element Locator Can't be null."); 
        }
        if(value == null)
        {
            throw new IllegalArgumentException("Value Can't be null.");
        }
        try
        {
            WebElement element = driver.findElement(by);
            element.click();
            element.clear();
            element.sendKeys(value);
        }
        catch (TimeoutException e)
        {
            throw new PageOperationException("Not able to find the element", e);
        }
    }
    public WebElement findByKey(final String id)
    {
        By criteria = By.id(getValue(id));
        return driver.findElement(criteria);
    }
    
    public final String getValue(final String key)
    {
        PageUtils.checkMandotaryParam("key", key);
        return factoryPage.getValue(key);
    }
    /**
     * Opens the new tab in the same browser.
     */
    public void createNewTab()
    {
        driver.findElement(By.cssSelector("body")).sendKeys(getOsKey() +"t");
    }
    /**
     * Returns COMMAND {@link Keys} if the OS is MAC OS X else CONTROL key.
     * @return {@link Keys}
     */
    private Keys getOsKey()
    {
        Keys keys = Keys.CONTROL;
        String osName = System.getProperty("os.name");
        if(osName != null && !osName.isEmpty() && osName.toLowerCase().startsWith("mac"))
        {
            keys = Keys.COMMAND;
        }
        return keys;
    }
    /**
     * Drag and drop element by x,y
     * @param source html element
     * @param x coordinate
     * @param y coordinate
     */
    public void dragAndDrop(WebElement source, int x, int y)
    {
        PageUtils.checkMandotaryParam("source element", source);
        Actions builder = new Actions(driver);   
        Action dragAndDrop = builder.dragAndDropBy(source, x, y).build();
        dragAndDrop.perform();
     }
    /**
     * Closes the recently opened tab in the same browser.
     */
    public void closeTab()
    {
        driver.findElement(By.cssSelector("body")).sendKeys(getOsKey() +"w");
    }
    /**
     * Waits for given {@link ElementState} of all render elements when rendering a page.
     * If the given element not reach element state, it will time out and throw {@link TimeoutException}. If operation to find all elements
     * times out a {@link PageRenderTimeException} is thrown
     *
     * @param renderTime render timer
     * @param elements   collection of {@link RenderElement}
     */
    public void elementRender(RenderTime renderTime, RenderElement... elements)
    {
        if (renderTime == null)
        {
            throw new UnsupportedOperationException("RenderTime is required");
        }
        if (elements == null || elements.length < 1)
        {
            throw new UnsupportedOperationException("RenderElements are required");
        }
        for (RenderElement element : elements)
        {
            try
            {
                renderTime.start();
                long waitSeconds = TimeUnit.MILLISECONDS.toSeconds(renderTime.timeLeft());
                element.render(driver, waitSeconds);
            }
            catch (TimeoutException e)
            {
                throw new PageRenderTimeException("element not rendered in time.",e);
            }
            finally
            {
                renderTime.end(element.getLocator().toString());
            }
        }
    }
    /**
     * Waits for given {@link ElementState} of all render elements when rendering a page.
     * If the given element not reach element state, it will time out and throw {@link TimeoutException}. 
     * If operation to find all elements times out a {@link PageRenderTimeException} is thrown
     * Renderable elements will be scanned from class using {@link RenderWebElement} annotation.
     *
     * @param renderTime render timer
     * @throws IllegalAccessException 
     * @throws IllegalArgumentException 
     */
    public void webElementRender(RenderTime renderTime)
    {
        if (renderTime == null)
        {
            throw new UnsupportedOperationException("RenderTime is required");
        }
        List<RenderElement> elements = new ArrayList<RenderElement>();
        Field[] fields = this.getClass().getDeclaredFields();
        for (Field field : fields)
        {
            if (field.isAnnotationPresent(RenderWebElement.class))
            {
                Class<?> type = field.getType();
                field.setAccessible(true);
                Object fieldVal = null;
                try
                {
                    fieldVal = field.get(this);
                } 
                catch (IllegalArgumentException | IllegalAccessException e)
                {
                    logger.error("Unable to set field", e);
                }
                
                //Handle page elements, extracts the @FindBy from the component.
                if(PageElement.class.isAssignableFrom(type))
                {
                    Annotation a = type.getAnnotation(FindBy.class);
                    if (a != null)
                    {
                        try 
                        {
                            By by = buildFromFindBy((FindBy)a);
                            elements.add(new RenderElement(by,ElementState.VISIBLE));
                        } 
                        catch (Exception ex)
                        {
                        }
                    }
                }
                if (type.equals(By.class))
                {
                    /*FIXME Below is the old way which we need to remove and use the web element instead.
                     *This is kept until we refactor the sharepo to use webelement instead of
                     *By.class. 
                     * @RenderWebelement By css = By.cssSelector("div.t");
                     * to 
                     * @RenderWebelement WebElement css;
                     * 
                     */
                    RenderWebElement webElement = (RenderWebElement) field.getAnnotation(RenderWebElement.class);
                    elements.add(new RenderElement((By) fieldVal, webElement.state()));
                }
                else
                {
                    Annotation[] list = field.getDeclaredAnnotations();
                    By by = null;
                    for(Annotation a : list)
                    {
                        if(a instanceof FindBy)
                        {
                            by = extractSelector((FindBy)a);
                        }
                    }
                    if(by != null)
                    {
                        elements.add(new RenderElement(by, ElementState.VISIBLE));
                    }
                }
            }
        }
        if(!elements.isEmpty())
        {
            elementRender(renderTime, elements.toArray(new RenderElement[elements.size()]));
        }
    }
    /**
     * Extract the selector query from annotation.
     * @param findBy
     * @return By selector with value from annotation.
     */
    private By extractSelector(FindBy findBy)
    {
        if(!StringUtils.isEmpty(findBy.css()))
        { 
            return By.cssSelector(findBy.css());
        }
        if(!StringUtils.isEmpty(findBy.id()))
        {
            return By.id(findBy.id());
        }
        if(!StringUtils.isEmpty(findBy.xpath()))
        {
            return By.xpath(findBy.xpath());
        }
        throw new PageOperationException("Select by is not supported" + findBy.toString());
    }
    private By buildFromFindBy(FindBy findBy)
    {
        if (!"".equals(findBy.className()))
        {
          return By.className(findBy.className());
        }
        if (!"".equals(findBy.css()))
        {
            return By.cssSelector(findBy.css());
        }
        if (!"".equals(findBy.id()))
        {
            return By.id(findBy.id());
        }
        if (!"".equals(findBy.linkText()))
        {
            return By.linkText(findBy.linkText());
        }

        if (!"".equals(findBy.name()))
        {
            return By.name(findBy.name());
        }

        if (!"".equals(findBy.partialLinkText()))
        {
            return By.partialLinkText(findBy.partialLinkText());
        }

        if (!"".equals(findBy.tagName()))
        {
            return By.tagName(findBy.tagName());
        }

        if (!"".equals(findBy.xpath()))
        {
            return By.xpath(findBy.xpath());
        }

        // Fall through
        return null;
    }
	public void setDefaultWaitTime(long defaultWaitTime) {
		this.defaultWaitTime = defaultWaitTime;
	}
	public void setMaxPageLoadingTime(long maxPageLoadingTime) {
		this.maxPageLoadingTime = maxPageLoadingTime;
	}
    
}
