/**
 *  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.specloader.specimpl;

import java.util.List;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;

import org.apache.isis.applib.Identifier;
import org.apache.isis.applib.annotation.Where;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.consent.Consent;
import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.isis.core.metamodel.consent.InteractionResultSet;
import org.apache.isis.core.metamodel.facetapi.FacetHolder;
import org.apache.isis.core.metamodel.facetapi.FacetHolderImpl;
import org.apache.isis.core.metamodel.facetapi.FacetUtil;
import org.apache.isis.core.metamodel.facetapi.FeatureType;
import org.apache.isis.core.metamodel.facets.FacetedMethodParameter;
import org.apache.isis.core.metamodel.facets.TypedHolder;
import org.apache.isis.core.metamodel.facets.all.named.NamedFacetInferred;
import org.apache.isis.core.metamodel.interactions.InteractionUtils;
import org.apache.isis.core.metamodel.interactions.UsabilityContext;
import org.apache.isis.core.metamodel.interactions.ValidityContext;
import org.apache.isis.core.metamodel.interactions.VisibilityContext;
import org.apache.isis.core.metamodel.services.ServicesInjector;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;

public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInMember2 {

    /**
     * The type of the mixin (providing the action), eg annotated with {@link org.apache.isis.applib.annotation.Mixin}.
     */
    private final Class<?> mixinType;

    /**
     * The {@link ObjectActionDefault} for the action being mixed in (ie on the {@link #mixinType}.
     */
    final ObjectActionDefault mixinAction;

    /**
     * The domain object type being mixed in to (being supplemented).
     */
    private final ObjectSpecification mixedInType;

    /**
     * Hold facets rather than delegate to the mixin action
     */
    private final FacetHolder facetHolder = new FacetHolderImpl();

    /**
     * Lazily initialized by {@link #getParameters()} (so don't use directly!)
     */
    private List<ObjectActionParameter> parameters;

    private final Identifier identifier;

    public ObjectActionMixedIn(
            final Class<?> mixinType,
            final String mixinMethodName,
            final ObjectActionDefault mixinAction,
            final ObjectSpecification mixedInType,
            final ServicesInjector objectMemberDependencies) {
        super(mixinAction.getFacetedMethod(), objectMemberDependencies);

        this.mixinType = mixinType;
        this.mixinAction = mixinAction;
        this.mixedInType = mixedInType;

        // copy over facets from mixin action to self
        FacetUtil.copyFacets(mixinAction.getFacetedMethod(), facetHolder);

        // adjust name if necessary
        final String name = getName();

        if(Strings.isNullOrEmpty(name) || name.equalsIgnoreCase(mixinMethodName)) {
            String memberName = determineNameFrom(mixinAction);
            FacetUtil.addFacet(new NamedFacetInferred(memberName, facetHolder));
        }

        // calculate the identifier
        final Identifier mixinIdentifier = mixinAction.getFacetedMethod().getIdentifier();
        List<String> memberParameterNames = mixinIdentifier.getMemberParameterNames();
        identifier = Identifier.actionIdentifier(getOnType().getCorrespondingClass().getName(), getId(), memberParameterNames);
    }

    @Override
    public String getId() {
        return determineIdFrom(this.mixinAction);
    }

    @Override
    public String getOriginalId() {
        return super.getId();
    }

    public boolean hasMixinAction(final ObjectAction mixinAction) {
        return this.mixinAction == mixinAction;
    }

    @Override
    public ObjectSpecification getOnType() {
        return mixedInType;
    }

    public int getParameterCount() {
        return mixinAction.getParameterCount();
    }

    @Override
    protected synchronized List<ObjectActionParameter> determineParameters() {
        if (parameters != null) {
            // because possible race condition (caller isn't synchronized)
            return parameters;
        }
        final List<ObjectActionParameter> mixinActionParameters = mixinAction.getParameters();
        final List<FacetedMethodParameter> paramPeers = getFacetedMethod().getParameters();

        final List<ObjectActionParameter> mixedInParameters = Lists.newArrayList();

        for(int paramNum = 0; paramNum < mixinActionParameters.size(); paramNum++) {

            final ObjectActionParameterAbstract mixinParameter =
                    (ObjectActionParameterAbstract) mixinActionParameters.get(paramNum);

            final TypedHolder paramPeer = paramPeers.get(paramNum);
            final ObjectSpecification specification = ObjectMemberAbstract
                    .getSpecification(getSpecificationLoader(), paramPeer.getType());

            final ObjectActionParameterMixedIn mixedInParameter =
                    mixinParameter.getPeer().getFeatureType() == FeatureType.ACTION_PARAMETER_SCALAR
                            ? new OneToOneActionParameterMixedIn(mixinParameter, this)
                            : new OneToManyActionParameterMixedIn(mixinParameter, this);
            mixedInParameters.add(mixedInParameter);
        }
        return mixedInParameters;
    }

    @Override
    public Consent isVisible(
            final ObjectAdapter mixedInAdapter,
            final InteractionInitiatedBy interactionInitiatedBy,
            final Where where) {

        final ObjectAdapter mixinAdapter = mixinAdapterFor(mixinType, mixedInAdapter);
        final VisibilityContext<?> ic =
                mixinAction.createVisibleInteractionContext(mixinAdapter, interactionInitiatedBy, where);
        ic.setMixedIn(mixedInAdapter);
        return InteractionUtils.isVisibleResult(this, ic).createConsent();
    }

    @Override
    public Consent isUsable(
            final ObjectAdapter mixedInAdapter,
            final InteractionInitiatedBy interactionInitiatedBy, final Where where) {

        final ObjectAdapter mixinAdapter = mixinAdapterFor(mixinType, mixedInAdapter);
        final UsabilityContext<?> ic =
                mixinAction.createUsableInteractionContext(mixinAdapter, interactionInitiatedBy, where);
        ic.setMixedIn(mixedInAdapter);
        return InteractionUtils.isUsableResult(this, ic).createConsent();
    }

    @Override
    public ObjectAdapter[] getDefaults(final ObjectAdapter mixedInAdapter) {
        final ObjectAdapter mixinAdapter = mixinAdapterFor(mixedInAdapter);
        return mixinAction.getDefaults(mixinAdapter);
    }

    @Override
    public ObjectAdapter[][] getChoices(
            final ObjectAdapter mixedInAdapter,
            final InteractionInitiatedBy interactionInitiatedBy) {
        final ObjectAdapter mixinAdapter = mixinAdapterFor(mixedInAdapter);
        return mixinAction.getChoices(mixinAdapter, interactionInitiatedBy);
    }

    protected ObjectAdapter mixinAdapterFor(final ObjectAdapter mixedInAdapter) {
        return mixinAdapterFor(mixinType, mixedInAdapter);
    }

    @Override
    protected void validateArgumentSet(
            final ObjectAdapter mixedInAdapter,
            final ObjectAdapter[] proposedArguments,
            final InteractionInitiatedBy interactionInitiatedBy,
            final InteractionResultSet resultSet) {

        final ObjectAdapter targetObject = mixinAdapterFor(mixinType, mixedInAdapter);

        final ValidityContext<?> ic =
                mixinAction.createActionInvocationInteractionContext(targetObject, proposedArguments, interactionInitiatedBy);
        ic.setMixedIn(mixedInAdapter);

        InteractionUtils.isValidResultSet(this, ic, resultSet);
    }



    @Override
    public ObjectAdapter execute(
            final ObjectAdapter target,         // will be the mixedInAdapter
            final ObjectAdapter mixedInAdapter, // will be passed in as null
            final ObjectAdapter[] arguments,
            final InteractionInitiatedBy interactionInitiatedBy) {


        final ObjectAdapter targetAdapter = mixinAdapterFor(mixinType, target);
        final ObjectAdapter actualMixedInAdapter = target;

        setupCommand(actualMixedInAdapter, arguments);

        return mixinAction.executeInternal(
                targetAdapter, actualMixedInAdapter, arguments,
                interactionInitiatedBy);
    }

    //region > facetHolder
    @Override
    protected FacetHolder getFacetHolder() {
        return facetHolder;
    }
    //endregion

    /* (non-Javadoc)
     * @see org.apache.isis.core.metamodel.specloader.specimpl.ObjectMemberAbstract#getIdentifier()
     */
    @Override
    public Identifier getIdentifier() {
        return identifier;
    }

    @Override
    public ObjectSpecification getMixinType() {
        return getSpecificationLoader().loadSpecification(mixinType);

    }


}
