/*
 *  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.core.metamodel.facets;

import java.lang.reflect.Method;
import java.util.List;

import com.google.common.collect.Lists;

import org.apache.isis.applib.filter.Filter;
import org.apache.isis.applib.filter.Filters;
import org.apache.isis.applib.services.wrapper.WrapperFactory;
import org.apache.isis.core.commons.lang.ObjectExtensions;
import org.apache.isis.core.metamodel.facetapi.DecoratingFacet;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.feature.ObjectMember;

/**
 * A {@link Facet} implementation that ultimately wraps a {@link Method} or
 * possibly several equivalent methods, for a Java implementation of a
 * {@link ObjectMember}.
 * 
 * <p>
 * Used by <tt>ObjectSpecificationDefault#getMember(Method)</tt> in order to
 * reverse lookup {@link ObjectMember}s from underlying {@link Method}s. So, for
 * example, the facets that represents an action xxx, or an <tt>validateXxx</tt>
 * method, or an <tt>addToXxx</tt> collection, can all be used to lookup the
 * member.
 * 
 * <p>
 * Note that {@link Facet}s relating to the class itself (ie for
 * {@link ObjectSpecification}) should not implement this interface.
 */
public interface ImperativeFacet extends Facet {

    /**
     * The {@link Method}s invoked by this {@link Facet}.
     * 
     * <p>
     * In the vast majority of cases there is only a single {@link Method} (eg
     * wrapping a property's getter). However, some {@link Facet}s, such as
     * those for callbacks, could map to multiple {@link Method}s.
     * Implementations that will return multiple {@link Method}s should
     * implement the {@link ImperativeFacetMulti} sub-interface that provides
     * the ability to {@link ImperativeFacetMulti#addMethod(Method) add}
     * {@link Method}s as part of the interface API. For example:
     * 
     * <pre>
     * if (someFacet instanceof ImperativeFacetMulti) {
     *     ImperativeFacetMulti ifm = (ImperativeFacetMulti)someFacet;
     *     ifm.addMethod(...);
     * }
     * </pre>
     */
    public List<Method> getMethods();

    public static enum Intent {
        CHECK_IF_HIDDEN,
        CHECK_IF_DISABLED,
        CHECK_IF_VALID,
        ACCESSOR,
        EXECUTE,
        MODIFY_PROPERTY,
        /**
         * Modify property using modify/clear rather than simply using set.
         */
        MODIFY_PROPERTY_SUPPORTING, 
        MODIFY_COLLECTION_ADD,
        MODIFY_COLLECTION_REMOVE,
        CHOICES_OR_AUTOCOMPLETE,
        DEFAULTS, 
        INITIALIZATION,
        LIFECYCLE, 
        UI_HINT
    }
    
    /**
     * The intent of this method, so that the {@link WrapperFactory} knows whether to delegate on or to reject.
     * @param method - one of the methods returned from {@link #getMethods()}
     */
    public Intent getIntent(Method method);


    public static Filter<Facet> FILTER = new Filter<Facet>() {
        @Override
        public boolean accept(final Facet facet) {
            return ImperativeFacet.Util.isImperativeFacet(facet);
        }
    };


    // //////////////////////////////////////

    public static class Util {
        private Util(){}

        /**
         * Returns the provided {@link Facet facet} as an {@link ImperativeFacet} if
         * it either is one or if it is a {@link DecoratingFacet} that in turn wraps
         * an {@link ImperativeFacet}.
         * 
         * <p>
         * Otherwise, returns <tt>null</tt>.
         */
        public static ImperativeFacet getImperativeFacet(final Facet facet) {
            if (facet instanceof ImperativeFacet) {
                return (ImperativeFacet) facet;
            }
            if (facet.getUnderlyingFacet() instanceof ImperativeFacet) {
                return (ImperativeFacet) facet.getUnderlyingFacet();
            }
            if (facet instanceof DecoratingFacet) {
                final DecoratingFacet<?> decoratingFacet = ObjectExtensions.asT(facet);
                return getImperativeFacet(decoratingFacet.getDecoratedFacet());
            }
            return null;
        }
        
        public static boolean isImperativeFacet(final Facet facet) {
            return getImperativeFacet(facet) != null;
        }

        public static Intent getIntent(final ObjectMember member, final Method method) {
            final List<Facet> allFacets = member.getFacets(Filters.anyOfType(Facet.class));
            final List<ImperativeFacet> imperativeFacets = Lists.newArrayList();
            for (final Facet facet : allFacets) {
                final ImperativeFacet imperativeFacet = ImperativeFacet.Util.getImperativeFacet(facet);
                if (imperativeFacet == null) {
                    continue;
                }
                final List<Method> methods = imperativeFacet.getMethods();
                if (!methods.contains(method)) {
                    continue;
                }
                imperativeFacets.add(imperativeFacet);
            }
            switch(imperativeFacets.size()) {
                case 0:
                    break;
                case 1:
                    return imperativeFacets.get(0).getIntent(method);
                default:
                    Intent intentToReturn = null;
                    for (ImperativeFacet imperativeFacet : imperativeFacets) {
                        Intent intent = imperativeFacet.getIntent(method);
                        if(intentToReturn == null) {
                            intentToReturn = intent;
                        } else if(intentToReturn != intent) {
                            throw new IllegalArgumentException(member.getIdentifier().toClassAndNameIdentityString() +  ": more than one ImperativeFacet for method " + method.getName() + " , with inconsistent intents: " + imperativeFacets.toString());
                        }
                    }
                    return intentToReturn;
            }
            throw new IllegalArgumentException(member.getIdentifier().toClassAndNameIdentityString() +  ": unable to determine intent of " + method.getName());
        }
    }

}
