/*
 *  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.isis.viewer.wicket.ui.components.collection.selector;

import java.io.Serializable;
import java.util.List;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;

import org.apache.wicket.Component;
import org.apache.wicket.model.IModel;

import org.apache.isis.applib.annotation.Render;
import org.apache.isis.applib.layout.component.CollectionLayoutData;
import org.apache.isis.applib.services.bookmark.Bookmark;
import org.apache.isis.core.metamodel.facets.collections.collection.defaultview.DefaultViewFacet;
import org.apache.isis.core.metamodel.facets.members.render.RenderFacet;
import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
import org.apache.isis.viewer.wicket.model.hints.UiHintContainer;
import org.apache.isis.viewer.wicket.model.models.EntityCollectionModel;
import org.apache.isis.viewer.wicket.model.models.EntityModel;
import org.apache.isis.viewer.wicket.model.util.ComponentHintKey;
import org.apache.isis.viewer.wicket.ui.ComponentFactory;
import org.apache.isis.viewer.wicket.ui.ComponentType;
import org.apache.isis.viewer.wicket.ui.app.registry.ComponentFactoryRegistry;
import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.CollectionContentsAsAjaxTablePanelFactory;
import org.apache.isis.viewer.wicket.ui.components.collectioncontents.multiple.CollectionContentsMultipleViewsPanelFactory;
import org.apache.isis.viewer.wicket.ui.components.collectioncontents.unresolved.CollectionContentsHiddenPanelFactory;

public class CollectionSelectorHelper implements Serializable {

    private static final long serialVersionUID = 1L;

    static final String UIHINT_EVENT_VIEW_KEY = EntityCollectionModel.HINT_KEY_SELECTED_ITEM;

    private final EntityCollectionModel model;

    private final List<ComponentFactory> componentFactories;
    private final ComponentHintKey componentHintKey;

    public CollectionSelectorHelper(
            final EntityCollectionModel model,
            final ComponentFactoryRegistry componentFactoryRegistry) {
        this(model, componentFactoryRegistry, ComponentHintKey.<String>noop());
    }

    public CollectionSelectorHelper(
            final EntityCollectionModel model,
            final ComponentFactoryRegistry componentFactoryRegistry,
            final ComponentHintKey componentHintKey) {
        this.model = model;
        this.componentFactories = locateComponentFactories(componentFactoryRegistry);
        this.componentHintKey = componentHintKey != null ? componentHintKey : ComponentHintKey.<String>noop();
    }

    private List<ComponentFactory> locateComponentFactories(ComponentFactoryRegistry componentFactoryRegistry) {
        final List<ComponentFactory> componentFactories = componentFactoryRegistry.findComponentFactories(ComponentType.COLLECTION_CONTENTS, model);
        List<ComponentFactory> otherFactories = Lists.newArrayList(Collections2.filter(componentFactories, new Predicate<ComponentFactory>() {
            @Override
            public boolean apply(final ComponentFactory input) {
                return input.getClass() != CollectionContentsMultipleViewsPanelFactory.class;
            }
        }));
        return ordered(otherFactories);
    }

    public List<ComponentFactory> getComponentFactories() {
        return componentFactories;
    }

    public String honourViewHintElseDefault(final Component component) {
        // honour hints ...
        final UiHintContainer hintContainer = getUiHintContainer(component);
        if (hintContainer != null) {
            String viewStr = hintContainer.getHint(component, UIHINT_EVENT_VIEW_KEY);
            if (viewStr != null) {
                return viewStr;
            }
        }

        // ... else default
        String initialFactory = determineInitialFactory();
        if (hintContainer != null) {
            hintContainer.setHint(component, UIHINT_EVENT_VIEW_KEY, initialFactory);
            // don't broadcast (no AjaxRequestTarget, still configuring initial setup)
        }
        return initialFactory;
    }

    //region > helpers

    /**
     * return the index of {@link CollectionContentsHiddenPanelFactory unresolved panel} if present and not eager loading;
     * else the index of {@link org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.CollectionContentsAsAjaxTablePanelFactory ajax table} if present,
     * otherwise first factory.
     */
    private String determineInitialFactory() {

        // try to load from session, if can
        final Bookmark bookmark = bookmarkHintIfAny();
        final String sessionAttribute = componentHintKey.get(bookmark);
        if(sessionAttribute != null) {
            return sessionAttribute;
        }

        // else grid layout hint
        final CollectionLayoutData layoutData = this.model.getLayoutData();
        if(layoutData != null) {
            final String defaultView = layoutData.getDefaultView();
            if(defaultView != null) {
                return defaultView;
            }
        }

        // else @CollectionLayout#defaultView attribute
        if (hasDefaultViewFacet(model)) {
            DefaultViewFacet defaultViewFacet = model.getCollectionMemento().getCollection(model.getSpecificationLoader()).getFacet(DefaultViewFacet.class);
            for (ComponentFactory componentFactory : componentFactories) {
                final String componentName = componentFactory.getName();
                final String viewName = defaultViewFacet.value();
                if (componentName.equalsIgnoreCase(viewName)) {
                    return componentName;
                }
            }
        }

        // else honour @CollectionLayout#renderEagerly
        return hasRenderEagerlyFacet(model) || model.isStandalone()
                ? CollectionContentsAsAjaxTablePanelFactory.NAME
                : CollectionContentsHiddenPanelFactory.NAME;

    }

    private Bookmark bookmarkHintIfAny() {
        final EntityModel entityModel = this.model.getEntityModel();
        return entityModel != null
                ? entityModel.getObjectAdapterMemento().asHintingBookmark()
                : null;
    }

    private static List<ComponentFactory> ordered(List<ComponentFactory> componentFactories) {
        return orderAjaxTableToEnd(componentFactories);
    }

    static List<ComponentFactory> orderAjaxTableToEnd(List<ComponentFactory> componentFactories) {
        int ajaxTableIdx = findAjaxTable(componentFactories);
        if (ajaxTableIdx >= 0) {
            List<ComponentFactory> orderedFactories = Lists.newArrayList(componentFactories);
            ComponentFactory ajaxTableFactory = orderedFactories.remove(ajaxTableIdx);
            orderedFactories.add(ajaxTableFactory);
            return orderedFactories;
        } else {
            return componentFactories;
        }
    }

    private static int findAjaxTable(List<ComponentFactory> componentFactories) {
        for (int i = 0; i < componentFactories.size(); i++) {
            if (componentFactories.get(i) instanceof CollectionContentsAsAjaxTablePanelFactory) {
                return i;
            }
        }
        return -1;
    }

    private static UiHintContainer getUiHintContainer(final Component component) {
        return UiHintContainer.Util.hintContainerOf(component, EntityCollectionModel.class);
    }

    private static boolean hasRenderEagerlyFacet(IModel<?> model) {
        final EntityCollectionModel entityCollectionModel = toEntityCollectionModel(model);
        if (entityCollectionModel == null) {
            return false;
        }

        final OneToManyAssociation collection =
                entityCollectionModel.getCollectionMemento().getCollection(entityCollectionModel.getSpecificationLoader());
        RenderFacet renderFacet = collection.getFacet(RenderFacet.class);
        return renderFacet != null && renderFacet.value() == Render.Type.EAGERLY;
    }

    private static boolean hasDefaultViewFacet(IModel<?> model) {
        final EntityCollectionModel entityCollectionModel = toEntityCollectionModel(model);
        if (entityCollectionModel == null) {
            return false;
        }

        final OneToManyAssociation collection =
                entityCollectionModel.getCollectionMemento().getCollection(entityCollectionModel.getSpecificationLoader());
        DefaultViewFacet defaultViewFacet = collection.getFacet(DefaultViewFacet.class);
        return defaultViewFacet != null;
    }

    private static EntityCollectionModel toEntityCollectionModel(IModel<?> model) {
        if (!(model instanceof EntityCollectionModel)) {
            return null;
        }

        final EntityCollectionModel entityCollectionModel = (EntityCollectionModel) model;
        if (!entityCollectionModel.isParented()) {
            return null;
        }

        return entityCollectionModel;
    }

    public ComponentFactory find(final String selected) {
        ComponentFactory componentFactory = doFind(selected);
        if (componentFactory != null) {
            return componentFactory;
        }

        final EntityCollectionModel entityCollectionModel = model;
            final String fallback;
        fallback = entityCollectionModel.isParented()
                ? CollectionContentsHiddenPanelFactory.NAME
                : CollectionContentsAsAjaxTablePanelFactory.NAME;
        componentFactory = doFind(fallback);
        if(componentFactory == null) {
            throw new IllegalStateException(String.format(
                    "Could not locate '%s' (as the fallback collection panel)",
                    fallback));
        }
        return componentFactory;
    }

    private ComponentFactory doFind(final String selected) {
        for (ComponentFactory componentFactory : componentFactories) {
            if(selected.equals(componentFactory.getName())) {
                return componentFactory;
            }
        }
        return null;
    }

    public int lookup(final String view) {
        int i=0;
        for (ComponentFactory componentFactory : componentFactories) {
            if(view.equals(componentFactory.getName())) {
                return i;
            }
            i++;
        }
        return 0;
    }

    //endregion

}
