/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @author tags. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */

package org.jboss.as.console.client.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import com.google.common.base.CharMatcher;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.ErrorEvent;
import com.google.gwt.event.dom.client.ErrorHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.layout.client.Layout;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.gwtplatform.mvp.client.proxy.PlaceManager;
import com.gwtplatform.mvp.shared.proxy.PlaceRequest;
import org.jboss.as.console.client.Console;
import org.jboss.as.console.client.ProductConfig;
import org.jboss.as.console.client.core.bootstrap.cors.BootstrapServerSetup;
import org.jboss.as.console.client.core.message.MessageCenter;
import org.jboss.as.console.client.core.message.MessageCenterView;
import org.jboss.as.console.client.search.Harvest;
import org.jboss.as.console.client.search.Index;
import org.jboss.as.console.client.search.SearchTool;
import org.jboss.as.console.client.shared.model.PerspectiveStore;
import org.jboss.as.console.client.widgets.nav.v3.BreadcrumbEvent;
import org.jboss.as.console.client.widgets.nav.v3.BreadcrumbMgr;
import org.jboss.as.console.client.widgets.nav.v3.FinderColumn;
import org.jboss.as.console.client.widgets.popups.DefaultPopup;
import org.jboss.ballroom.client.widgets.window.Feedback;

import static org.jboss.as.console.client.ProductConfig.Profile.COMMUNITY;
import static org.jboss.as.console.client.StringUtils.ELLIPSIS;

/**
 * Top level header, gives access to main applications.
 *
 * @author Heiko Braun
 * @date 1/28/11
 */
public class Header implements ValueChangeHandler<String>, BreadcrumbEvent.Handler {

    private final FeatureSet featureSet;
    private final ToplevelTabs toplevelTabs;
    private final ProductConfig productConfig;
    private final BootstrapContext bootstrap;
    private final MessageCenter messageCenter;
    private final PlaceManager placeManager;

    private final Harvest harvest;
    private final Index index;
    private final PerspectiveStore perspectiveStore;

    private HTMLPanel linksPane;
    private String currentHighlightedSection = null;
    private SearchTool searchTool;
    private LayoutPanel bottom;
    private LayoutPanel outerLayout;
    private LayoutPanel alternateSubNav;
    private ArrayList<PlaceRequest> places = new ArrayList<>();
    private HTML breadcrumb;
    private Map<FinderColumn.FinderId,BreadcrumbMgr> breadcrumbs = new HashMap<>();
    private FinderColumn.FinderId activeFinder;

    @Inject
    public Header(final FeatureSet featureSet, final ToplevelTabs toplevelTabs, MessageCenter messageCenter,
                  ProductConfig productConfig, BootstrapContext bootstrap, PlaceManager placeManager,
                  Harvest harvest, Index index, PerspectiveStore perspectiveStore) {
        this.featureSet = featureSet;
        this.toplevelTabs = toplevelTabs;
        this.messageCenter = messageCenter;
        this.productConfig = productConfig;
        this.bootstrap = bootstrap;
        this.placeManager = placeManager;
        this.harvest = harvest;
        this.index = index;
        this.perspectiveStore = perspectiveStore;
        History.addValueChangeHandler(this);

        placeManager.getEventBus().addHandler(BreadcrumbEvent.TYPE, this);
    }

    public Widget asWidget() {

        outerLayout = new LayoutPanel();
        outerLayout.addStyleName("page-header");

        Widget logo = getProductSection();
        Widget links = getLinksSection();

        LayoutPanel line = new LayoutPanel();
        line.setStyleName("header-line");
        LayoutPanel top = new LayoutPanel();
        top.setStyleName("header-top");
        bottom = new LayoutPanel();
        bottom.setStyleName("header-bottom");


        alternateSubNav = new LayoutPanel();
        alternateSubNav.setStyleName("fill-layout");
        alternateSubNav.getElement().setAttribute("style", "background-color:#F9F9F9!important");

        // breadcrumb
        breadcrumb = new HTML();
        breadcrumb.setStyleName("header-breadcrumb");
        alternateSubNav.add(breadcrumb);

        // Back link
        HTML backLink = new HTML();
        backLink.addStyleName("link-bar-first");
        SafeHtmlBuilder builder = new SafeHtmlBuilder();
        builder.appendHtmlConstant("<i class='icon-double-angle-left'></i>&nbsp;");
        //builder.appendHtmlConstant("<i class='icon-columns'></i>&nbsp;");
        builder.appendHtmlConstant("Back");
        builder.appendHtmlConstant("&nbsp;<span style='color:#F5F5F5'>|</span>&nbsp;");
        backLink.setHTML(builder.toSafeHtml());
        backLink.getElement().setAttribute("style", "font-size:16px; padding-top:10px;cursor:pointer;");
        backLink.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent clickEvent) {
                // placeManager.revealRelativePlace(1);

                // a workaround. see details of toggleNavigation(bool)
                if (places.isEmpty()) {

                    // should not happen
                    Console.error(Console.CONSTANTS.unableToNavigateBack());
                } else {

                    /*
                    hb: Going back to the first place of the hierarchy allows nested presenters
                    to skip their onReset() by comparing the current tokens when that method is invoked.
                    this way they can keep their state (i.e. selections) and allow refresh when being called directly (aka token matches)
                     */
                    //placeManager.revealPlace(places.get(0));

                    List<PlaceRequest> previousPLaces = new ArrayList<>();
                    int i;
                    for (i = 0; i < places.size() - 1; i++) {
                        previousPLaces.add(places.get(i));
                    }
                    // add "backButton" parameter to the place request, to indicate that finder presenter can keep it's selection
                    PlaceRequest lastPlace = previousPLaces.get(i - 1);
                    previousPLaces.set(i - 1, new PlaceRequest.Builder(lastPlace)
                            .with("backButton", "")
                            .build());
                    placeManager.revealPlaceHierarchy(previousPLaces);

                    //placeManager.navigateBack();

                }

            }
        });
        alternateSubNav.add(backLink);

        alternateSubNav.setWidgetLeftWidth(backLink, 15, Style.Unit.PX, 70, Style.Unit.PX);
        alternateSubNav.setWidgetLeftWidth(breadcrumb, 80, Style.Unit.PX, 100, Style.Unit.PCT);

        outerLayout.add(line);
        outerLayout.add(top);
        outerLayout.add(bottom);
        outerLayout.add(alternateSubNav);

        outerLayout.setWidgetTopHeight(line, 0, Style.Unit.PX, 4, Style.Unit.PX);
        outerLayout.setWidgetTopHeight(top, 4, Style.Unit.PX, 32, Style.Unit.PX);
        outerLayout.setWidgetTopHeight(bottom, 36, Style.Unit.PX, 44, Style.Unit.PX);
        outerLayout.setWidgetTopHeight(alternateSubNav, 36, Style.Unit.PX, 44, Style.Unit.PX);
        outerLayout.setWidgetVisible(alternateSubNav, false);

        top.add(logo);
        top.setWidgetLeftWidth(logo, 15, Style.Unit.PX, 700, Style.Unit.PX);
        top.setWidgetTopHeight(logo, 0, Style.Unit.PX, 32, Style.Unit.PX);

        bottom.add(links);



        // Debug tools
        VerticalPanel debugTools = new VerticalPanel();

        bottom.add(debugTools);
        bottom.setWidgetLeftWidth(links, 0, Style.Unit.PX, 800, Style.Unit.PX);
        bottom.setWidgetTopHeight(links, 0, Style.Unit.PX, 44, Style.Unit.PX);

        bottom.setWidgetRightWidth(debugTools, 0, Style.Unit.PX, 50, Style.Unit.PX);
        bottom.setWidgetTopHeight(debugTools, 0, Style.Unit.PX, 44, Style.Unit.PX);


        HorizontalPanel tools = new HorizontalPanel();
        tools.setStyleName("top-level-tools");

        // messages
        MessageCenterView messageCenterView = new MessageCenterView(messageCenter);
        Widget messageCenter = messageCenterView.asWidget();
        tools.add(messageCenter);
        messageCenter.getElement().getParentElement().addClassName("first");

        // global search
        if (featureSet.isSearchEnabled()) {
            searchTool = new SearchTool(harvest, index, placeManager);
            tools.add(searchTool);
        }

        // user menu

        // roles
        Set<String> roles = bootstrap.getRoles();
        SafeHtmlBuilder sb = new SafeHtmlBuilder();
        sb.appendHtmlConstant("<div class='roles-menu'>");
        for(String role : roles)
        {
            sb.appendEscaped(role).appendHtmlConstant("<br/>");
        }
        sb.appendHtmlConstant("<div>");

        // current user
        String userHtml = "<i style='color:#cecece' class='icon-user'></i>&nbsp;"+bootstrap.getPrincipal()+"&nbsp;<i style='color:#cecece' class='icon-angle-down'></i>";

        SafeHtml principal = new SafeHtmlBuilder().appendHtmlConstant("<div class='header-textlink'>"+userHtml+"</div>").toSafeHtml();
        final HTML userButton = new HTML(principal);
        userButton.getElement().setAttribute("style", "cursor:pointer");
        tools.add(userButton);

        final DefaultPopup menuPopup = new DefaultPopup(DefaultPopup.Arrow.NONE);
        menuPopup.setAutoHideEnabled(true);
        ClickHandler clickHandler = new ClickHandler() {
            public void onClick(ClickEvent event) {

                int width = 120;
                int height = 50;

                menuPopup.setPopupPosition(
                        userButton.getAbsoluteLeft() ,
                        userButton.getAbsoluteTop() + 25
                );

                menuPopup.show();

                menuPopup.setWidth(width+"px");
                menuPopup.setHeight(height+"px");
            }
        };

        userButton.addClickHandler(clickHandler);

        HTML logoutHtml = new HTML(Console.CONSTANTS.common_label_logout());
        logoutHtml.setStyleName("menu-item");
        logoutHtml.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {

                menuPopup.hide();

                Feedback.confirm(
                        Console.CONSTANTS.common_label_logout(),
                        Console.CONSTANTS.logout_confirm(),
                        new Feedback.ConfirmationHandler() {
                            @Override
                            public void onConfirmation(boolean isConfirmed) {
                                if (isConfirmed) {
                                    new LogoutCmd(bootstrap.isSsoEnabled()).execute();
                                }
                            }
                        }
                );
            }
        });


        VerticalPanel usermenu = new VerticalPanel();
        usermenu.setStyleName("fill-layout-width");
        usermenu.addStyleName("top-level-menu");

        usermenu.add(new HTML(Console.CONSTANTS.common_label_roles() + ":"));
        usermenu.add(new HTML(sb.toSafeHtml()));


        if(bootstrap.isSuperUser())
        {
            usermenu.add(new HTML("<hr/>"));
            HTML runAsBtn = new HTML();
            runAsBtn.addStyleName("menu-item");

            SafeHtmlBuilder runAsRole = new SafeHtmlBuilder();
            runAsRole.appendEscaped(Console.CONSTANTS.runAs());
            if (bootstrap.getRunAs()!=null) {
                runAsRole.appendHtmlConstant("&nbsp;").appendEscaped(bootstrap.getRunAs());
            } else {
                runAsRole.appendEscaped(ELLIPSIS);
            }

            runAsBtn.setHTML(runAsRole.toSafeHtml());
            runAsBtn.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {

                    menuPopup.hide();

                    placeManager.revealPlace(
                            new PlaceRequest(NameTokens.ToolsPresenter).with("name", "run-as-role"), false
                    );


                }
            });
            usermenu.add(runAsBtn);
        }

        usermenu.add(logoutHtml);
        menuPopup.setWidget(usermenu);

        // Reconnect to a different WildFly server / domain
        if (productConfig.getProfile() == COMMUNITY && !bootstrap.isSameOrigin()) {
            SafeHtml globe = new SafeHtmlBuilder().appendHtmlConstant("<div class='header-textlink'><i style='color:#cecece' class='icon-globe'></i></div>").toSafeHtml();
            HTML connectTo = new HTML(globe);
            String desc = bootstrap.isStandalone() ? Console.CONSTANTS.connecto_to_desc_standalone() : Console.CONSTANTS.connecto_to_desc_domain();
            connectTo.setTitle(desc);
            connectTo.getElement().setAttribute("style", "cursor:pointer");
            connectTo.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    // Without "/App.hml" we would lose the query parameter because "/" redirects to "/App.html"
                    // TODO Why is this redirect necessary?
                    String url = GWT.getHostPageBaseURL() + "App.html?" + BootstrapServerSetup.CONNECT_PARAMETER;
                    Window.Location.replace(url);
                }
            });
            tools.add(connectTo);
        }

        top.add(tools);
        top.setWidgetRightWidth(tools, 15, Style.Unit.PX, 700, Style.Unit.PX);
        top.setWidgetTopHeight(tools, 2, Style.Unit.PX, 32, Style.Unit.PX);
        top.setWidgetHorizontalPosition(tools, Layout.Alignment.END);

        outerLayout.getElement().setAttribute("role", "navigation");
        outerLayout.getElement().setAttribute("aria-label", "Toplevel Categories");
        return outerLayout;
    }

    private Widget getProductSection() {

        final HorizontalPanel panel = new HorizontalPanel();
        panel.getElement().setAttribute("role", "presentation");
        panel.getElement().setAttribute("aria-hidden", "true");

        final Image logo = new Image();
        logo.getElement().setAttribute("style", "cursor:pointer");
        logo.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent clickEvent) {
                toggleNavigation(false);
                placeManager.revealPlace(new PlaceRequest(NameTokens.HomepagePresenter));
            }
        });
        logo.setStyleName("logo");
        panel.add(logo);

        if (ProductConfig.Profile.PRODUCT.equals(productConfig.getProfile())) {
            logo.addErrorHandler(new ErrorHandler() {
                @Override
                public void onError(ErrorEvent event) {
                    panel.remove(logo);
                    Label productName = new Label(productConfig.getProductName());
                    productName.setStyleName("header-product-name");
                    panel.insert(productName, 0);
                }
            });
            logo.setUrl("images/logo/" + logoName(productConfig.getProductName()) + ".png");
            logo.setAltText(productConfig.getProductName());
        } else {
            logo.setUrl("images/logo/community_title.png");
            logo.setAltText("Wildlfy Application Server");
        }

        return panel;
    }

    private String logoName(String productName) {
        CharMatcher digits = CharMatcher.inRange('0', '9');
        CharMatcher alpha = CharMatcher.inRange('a', 'z');
        return digits.or(alpha).retainFrom(productName.toLowerCase());
    }

    private Widget getLinksSection() {
        linksPane = new HTMLPanel(createLinks());
        linksPane.getElement().setId("header-links-section");
        linksPane.getElement().setAttribute("role", "menubar");
        linksPane.getElement().setAttribute("aria-controls", "main-content-area");

        for (final ToplevelTabs.Config tlt : toplevelTabs) {
            final String id = "header-" + tlt.getToken();

            SafeHtmlBuilder html = new SafeHtmlBuilder();
            html.appendHtmlConstant("<div class='header-link-label'>");
            html.appendHtmlConstant("<span role='menuitem'>");
            html.appendHtmlConstant(tlt.getTitle());
            html.appendHtmlConstant("</span>");
            html.appendHtmlConstant("</div>");
            HTML widget = new HTML(html.toSafeHtml());
            widget.setStyleName("fill-layout");

            widget.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    // navigate either child directly or parent if revealed the first time
                    /*boolean hasChild = perspectiveStore.hasChild(tlt.getToken());
                    String token = hasChild ?
                            perspectiveStore.getChild(tlt.getToken()) : tlt.getToken();
                    boolean updateToken = hasChild ? true : tlt.isUpdateToken();*/

                    String token = tlt.getToken();
                    placeManager.revealPlace(
                            new PlaceRequest.Builder().nameToken(token).build(), tlt.isUpdateToken()
                    );
                }
            });
            linksPane.add(widget, id);

        }

        return linksPane;
    }

    private String createLinks() {

        SafeHtmlBuilder headerString = new SafeHtmlBuilder();

        if (!toplevelTabs.isEmpty()) {
            headerString
                    .appendHtmlConstant("<table border=0 class='header-links' cellpadding=0 cellspacing=0 border=0>");
            headerString.appendHtmlConstant("<tr id='header-links-ref'>");

            headerString.appendHtmlConstant("<td><img src=\"images/blank.png\" width=1/></td>");
            for (ToplevelTabs.Config tlt : toplevelTabs) {
                final String id = "header-" + tlt.getToken();
                String styleClass = "header-link";
                String styleAtt = "vertical-align:middle; text-align:center";

                String td = "<td style='" + styleAtt + "' id='" + id + "' class='" + styleClass + "'></td>";

                headerString.appendHtmlConstant(td);
                //headerString.append(title);

                //headerString.appendHtmlConstant("<td ><img src=\"images/blank.png\" width=1 height=32/></td>");

            }

            headerString.appendHtmlConstant("</tr>");
            headerString.appendHtmlConstant("</table>");
            headerString.appendHtmlConstant("<div id='subnavigation' style='float:right;clear:right;'/>");
        }

        return headerString.toSafeHtml().asString();
    }

    @Override
    public void onValueChange(ValueChangeEvent<String> event) {
        String historyToken = event.getValue();
        if(historyToken.equals(currentHighlightedSection))
            return;
        else
            currentHighlightedSection = historyToken;

        if(historyToken.indexOf("/")!=-1)
        {
            highlight(historyToken.substring(0, historyToken.indexOf("/")));
        }
        else
        {
            highlight(historyToken);
        }
    }

    public void highlight(String name)
    {
        toggleSubnavigation(name);

        com.google.gwt.user.client.Element target = linksPane.getElementById("header-links-ref");
        if(target!=null) // TODO: i think this cannot happen, does it?
        {
            NodeList<Node> childNodes = target.getChildNodes();
            for(int i=0; i<childNodes.getLength(); i++)
            {
                Node n = childNodes.getItem(i);
                if(Node.ELEMENT_NODE == n.getNodeType())
                {
                    Element element = (Element) n;
                    if(element.getId().equals("header-"+name))
                    {
                        element.addClassName("header-link-selected");
                        element.setAttribute("aria-selected", "true");
                    }
                    else {
                        element.removeClassName("header-link-selected");
                        element.setAttribute("aria-selected", "false");
                    }
                }
            }
        }
    }

    public void toggleNavigation(boolean enableApplicationView) {

        if(enableApplicationView && activeFinder!=null) {

            // persist places before entering the application view
            places = new ArrayList(placeManager.getCurrentPlaceHierarchy());

            BreadcrumbMgr breadcrumbMgr = getBreadcrumbMgr(activeFinder);

            if (breadcrumbMgr != null) {
                // update the breadcrumb
                SafeHtmlBuilder html = new SafeHtmlBuilder();

                for (int i = 0; i <= breadcrumbMgr.getBreadcrumbCursor(); i++) {
                    BreadcrumbEvent item = breadcrumbMgr.getNavigationStack().get(i);

                    if (i > 0 && i == breadcrumbMgr.getBreadcrumbCursor()) html.appendHtmlConstant("<b>");
                    html.appendEscaped(item.getKey()).appendEscaped(":").appendHtmlConstant("&nbsp;").appendEscaped(item.getValue());
                    if (i > 0 && i == breadcrumbMgr.getBreadcrumbCursor()) html.appendHtmlConstant("</b>");

                    if (i <= breadcrumbMgr.getBreadcrumbCursor() - 1) {
                        html.appendHtmlConstant("&nbsp;&nbsp;").appendHtmlConstant("<i class=\"icon-angle-right\"></i>").appendHtmlConstant("&nbsp;&nbsp;");
                    }
                }

                breadcrumb.setHTML(html.toSafeHtml());

                // swap sub-navigation
                outerLayout.setWidgetVisible(bottom, false);
                outerLayout.setWidgetVisible(alternateSubNav, true);
            } else {
                throw new IllegalStateException("no breadcrumb mgr for " + activeFinder);
            }
        }
        else
        {
            outerLayout.setWidgetVisible(alternateSubNav, false);
            outerLayout.setWidgetVisible(bottom, true);
        }
    }

    private void toggleSubnavigation(String name) {

    }

    @Override
    public void onBreadcrumbEvent(BreadcrumbEvent event) {

        FinderColumn.FinderId finderId = event.getFinderId();

        BreadcrumbMgr breadcrumbMgr = getBreadcrumbMgr(finderId);
        Stack<BreadcrumbEvent> navigationStack = breadcrumbMgr.getNavigationStack();

        int columnIndex = isColumnPresent(navigationStack, event);
        boolean isDuplicateEntry = isDuplicateColumnEntry(navigationStack, event)!=-1;
        if(isDuplicateEntry) {
            // if the entry exists, simple move the cursor the right position
            breadcrumbMgr.setBreadcrumbCursor(columnIndex);
            return;
        };

        if(event.isSelected())
        {

            // column not present: append to stack
            if(-1 == columnIndex)
            {
                navigationStack.push(event);
            }

            // column present: replace at index
            else if (columnIndex<=navigationStack.size()-1)   // TODO: can this condition ever not be true?
            {
                navigationStack.insertElementAt(event, columnIndex);

                // selection events modify the stack (remove the remainder)
                if(!event.isMenuEvent()) {
                    int numElements = navigationStack.size() - columnIndex;
                    for (int i = 0; i < numElements; i++)
                        navigationStack.pop();

                    // move the cursor the end of stack
                    breadcrumbMgr.setBreadcrumbCursor(navigationStack.size()-1);
                }

                // menu events simply move the cursor, but the stack remains the same
                else
                {
                    breadcrumbMgr.setBreadcrumbCursor(columnIndex);
                }
            }


        }

        /*// dump stack
        System.out.println("<< --");
        for (BreadcrumbEvent breadCrumb : navigationStack) {
            System.out.println(breadCrumb);
        }

        System.out.println(" -- >>");*/

    }

    private BreadcrumbMgr getBreadcrumbMgr(FinderColumn.FinderId finderId) {
        if(!breadcrumbs.containsKey(finderId))
            breadcrumbs.put(finderId, new BreadcrumbMgr());

        return breadcrumbs.get(finderId);
    }

    private int isDuplicateColumnEntry(List<BreadcrumbEvent> navigationStack, BreadcrumbEvent event) {
        return findMatchingIndex(navigationStack, event, (previous, next) -> {
            return previous.getKey().equals(next.getKey())
                    && previous.getValue().equals(next.getValue());
        });
    }

    private int isColumnPresent(List<BreadcrumbEvent> navigationStack, BreadcrumbEvent event) {
        return findMatchingIndex(navigationStack, event, (previous, next) -> {
            return previous.getKey().equals(next.getKey());
        });
    }

    public void activateFinder(FinderColumn.FinderId finderId) {
        this.activeFinder = finderId;
    }

    @FunctionalInterface
    public interface Condition {
        boolean matches(BreadcrumbEvent previous, BreadcrumbEvent next);
    }

    private int findMatchingIndex(List<BreadcrumbEvent> navigationStack, BreadcrumbEvent event, Condition condition) {
        int index = -1;

        for (int i = 0; i < navigationStack.size(); i++) {
            BreadcrumbEvent previousEvent = navigationStack.get(i);
            boolean equals = condition.matches(previousEvent, event);

            if (equals) {
                index = i;
                break;
            }
        }

        return index;
    }

    public SearchTool getSearchTool() {
        return searchTool;
    }
}