/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *  
 *    http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License. 
 *  
 */

package org.apache.directory.studio.test.integration.ui.utils;


import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.widgetOfType;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.swt.widgets.Tree;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable;
import org.eclipse.swtbot.swt.finder.results.VoidResult;
import org.eclipse.swtbot.swt.finder.waits.DefaultCondition;
import org.eclipse.swtbot.swt.finder.waits.ICondition;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotButton;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
import org.eclipse.swtbot.swt.finder.widgets.TimeoutException;


/**
 * Helpers for using SWTBot.
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 * @version $Rev$, $Date$
 */
public class SWTBotUtils
{

    /**
     * Gets the connections tree.
     * 
     * @param bot
     *            the bot
     * 
     * @return the connections tree
     * 
     * @throws Exception
     *             the exception
     */
    public static SWTBotTree getConnectionsTree( SWTWorkbenchBot bot ) throws Exception
    {
        SWTBotView view = bot.viewByTitle( "Connections" );
        view.show();

        Tree tree = ( Tree ) bot.widget( widgetOfType( Tree.class ), view.getWidget() );
        return new SWTBotTree( tree );
    }


    /**
     * Gets the ldap browser tree.
     * 
     * @param bot
     *            the bot
     * 
     * @return the ldap browser tree
     * 
     * @throws Exception
     *             the exception
     */
    public static SWTBotTree getLdapBrowserTree( SWTWorkbenchBot bot )
    {
        SWTBotView view = bot.viewByTitle( "LDAP Browser" );
        view.show();
        Tree tree = ( Tree ) bot.widget( widgetOfType( Tree.class ), view.getWidget() );
        return new SWTBotTree( tree );
    }


    /**
     * Gets the entry editor tree.
     * 
     * @param bot
     *            the bot
     * 
     * @return the entry editor tree
     * 
     * @throws Exception
     *             the exception
     */
    public static SWTBotTree getEntryEditorTree( final SWTWorkbenchBot bot, String title ) throws Exception
    {
        SWTBotEditor editor = bot.editorByTitle( title );
        SWTBotTree tree = editor.bot().tree();
        return tree;
    }


    /**
     * Clicks a button asynchronously and waits till the given condition is
     * fulfilled.
     * 
     * @param bot
     *            the SWT bot
     * @param button
     *            the button to click
     * @param waitCondition
     *            the condition to wait for, may be null
     * 
     * @throws TimeoutException
     */
    public static void asyncClick( final SWTWorkbenchBot bot, final SWTBotButton button, final ICondition waitCondition )
        throws TimeoutException
    {
        bot.waitUntil( new DefaultCondition()
        {
            public boolean test() throws Exception
            {
                return button.isEnabled();
            }


            public String getFailureMessage()
            {
                return "Button isn't enabled.";
            }
        } );

        UIThreadRunnable.asyncExec( bot.getDisplay(), new VoidResult()
        {
            public void run()
            {
                button.click();
            }
        } );

        if ( waitCondition != null )
        {
            bot.waitUntil( waitCondition );
        }
    }


    /**
     * Clicks a menu item asynchronously and waits till the given condition is
     * fulfilled.
     * 
     * @param bot
     *            the SWT bot
     * @param button
     *            the button to click
     * @param waitCondition
     *            the condition to wait for, may be null
     * 
     * @throws TimeoutException
     */
    public static void asyncClick( final SWTWorkbenchBot bot, final SWTBotMenu menu, final ICondition waitCondition )
        throws TimeoutException
    {
        UIThreadRunnable.asyncExec( bot.getDisplay(), new VoidResult()
        {
            public void run()
            {
                menu.click();
            }
        } );

        if ( waitCondition != null )
        {
            bot.waitUntil( waitCondition );
        }
    }


    /**
     * Clicks a tree item asynchronously and waits till the given condition is
     * fulfilled.
     * 
     * @param bot
     *            the SWT bot
     * @param item
     *            the tree item to click
     * @param waitCondition
     *            the condition to wait for, may be null
     * 
     * @throws TimeoutException
     *             the timeout exception
     */
    public static void asyncClick( final SWTWorkbenchBot bot, final SWTBotTreeItem item, final ICondition waitCondition )
        throws TimeoutException
    {
        UIThreadRunnable.asyncExec( bot.getDisplay(), new VoidResult()
        {
            public void run()
            {
                item.click();
            }
        } );

        if ( waitCondition != null )
        {
            bot.waitUntil( waitCondition );
        }
    }


    /**
     * Selects an entry in the browser tree and optionally expands the selected
     * entry. Takes care that all attributes and child entries are initialized
     * so that there are no pending background actions and event notifications.
     * This is necessary to avoid race conditions.
     * 
     * @param bot
     *            the SWT bot
     * @param tree
     *            the browser tree
     * @param expandChild
     *            true to expand the child entry
     * @param path
     *            the path to the entry
     * 
     * @return the selected entry as SWTBotTreeItem
     * 
     * @throws Exception
     *             the exception
     */
    public static SWTBotTreeItem selectEntry( final SWTWorkbenchBot bot, final SWTBotTree tree,
        final boolean expandChild, final String... path )
    {
        List<String> pathList = new ArrayList<String>( Arrays.asList( path ) );
        SWTBotTreeItem entry = null;

        while ( !pathList.isEmpty() )
        {
            String currentPath = pathList.remove( 0 );

            if ( entry == null )
            {
                currentPath = adjustNodeName( tree, currentPath );
                entry = tree.getTreeItem( currentPath );
            }
            else
            {
                // adjust current path, because the label is decorated with the
                // number of children
                currentPath = adjustNodeName( entry, currentPath );
                entry = entry.getNode( currentPath );
            }
            entry.click();

            if ( !pathList.isEmpty() || expandChild )
            {
                // expand entry and wait till
                // - children are displayed
                // - next child is visible
                final String nextName = !pathList.isEmpty() ? pathList.get( 0 ) : null;
                expandEntry( bot, entry, nextName );
            }

            entry.select();
        }
        return entry;
    }


    /**
     * Expands the entry. Takes care that all attributes and child entries are
     * initialized so that there are no pending background actions and event
     * notifications. This is necessary to avoid race conditions.
     * 
     * @param bot
     *            the bot
     * @param entry
     *            the entry to expand
     * @param nextName
     *            the name of the entry that must become visible, may be null
     * 
     * @throws Exception
     *             the exception
     */
    public static void expandEntry( final SWTWorkbenchBot bot, final SWTBotTreeItem entry, final String nextName )
    {
        UIThreadRunnable.asyncExec( bot.getDisplay(), new VoidResult()
        {
            public void run()
            {
                if ( !entry.isExpanded() )
                {
                    entry.expand();
                }
            }
        } );

        bot.waitUntil( new DefaultCondition()
        {
            public boolean test() throws Exception
            {
                if ( nextName != null )
                {
                    String adjustedNodeName = nextName != null ? adjustNodeName( entry, nextName ) : null;
                    SWTBotTreeItem node = entry.getNode( adjustedNodeName );
                    if ( node == null )
                    {
                        return false;
                    }
                }
                return !entry.getNodes().contains( "Fetching Entries..." );
            }


            public String getFailureMessage()
            {
                return "Could not find entry " + entry.getText() + " -> " + nextName;
            }
        } );
    }


    private static String adjustNodeName( SWTBotTreeItem child, String nodeName )
    {
        List<String> nodes = child.getNodes();
        for ( String node : nodes )
        {
            if ( node.toUpperCase().startsWith( nodeName.toUpperCase() ) )
            {
                return node;
            }
        }
        return null;
    }


    private static String adjustNodeName( SWTBotTree tree, String nodeName )
    {
        SWTBotTreeItem[] allItems = tree.getAllItems();
        for ( SWTBotTreeItem item : allItems )
        {
            String node = item.getText();
            if ( node.toUpperCase().startsWith( nodeName.toUpperCase() ) )
            {
                return node;
            }
        }
        return null;
    }

}
