/* 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.causeway.core.metamodel.spec.impl;

import org.apache.causeway.core.metamodel.context.MetaModelContext;
import org.apache.causeway.core.metamodel.facetapi.MetaModelRefiner;
import org.apache.causeway.core.metamodel.facets.actions.action.ActionAnnotationFacetFactory;
import org.apache.causeway.core.metamodel.facets.actions.action.ActionAnnotationShouldEnforceConcreteTypeToBeIncludedWithMetamodelValidator;
import org.apache.causeway.core.metamodel.facets.actions.action.ActionOverloadingValidator;
import org.apache.causeway.core.metamodel.facets.actions.contributing.derived.ContributingFacetFromMixinFacetFactory;
import org.apache.causeway.core.metamodel.facets.actions.homepage.annotation.HomePageFacetAnnotationFactory;
import org.apache.causeway.core.metamodel.facets.actions.layout.ActionLayoutFacetFactory;
import org.apache.causeway.core.metamodel.facets.actions.validate.method.ActionValidationFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.collections.accessor.CollectionAccessorFacetViaAccessorFactory;
import org.apache.causeway.core.metamodel.facets.collections.collection.CollectionAnnotationFacetFactory;
import org.apache.causeway.core.metamodel.facets.collections.javautilcollection.CollectionFacetFactory;
import org.apache.causeway.core.metamodel.facets.collections.layout.CollectionLayoutFacetFactory;
import org.apache.causeway.core.metamodel.facets.collections.parented.ParentedFacetSinceCollectionFactory;
import org.apache.causeway.core.metamodel.facets.collections.sortedby.annotation.SortedByFacetAnnotationFactory;
import org.apache.causeway.core.metamodel.facets.fallback.FallbackFacetFactory;
import org.apache.causeway.core.metamodel.facets.jaxb.JaxbFacetFactory;
import org.apache.causeway.core.metamodel.facets.members.cssclass.annotprop.CssClassFacetOnActionFromConfiguredRegexFactory;
import org.apache.causeway.core.metamodel.facets.members.described.method.DescribedAsFacetForMemberViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.members.disabled.method.DisableForContextFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.members.hidden.method.HideForContextFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.members.named.method.NamedFacetForMemberViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.object.ViewModelSemanticCheckingFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.bookmarkpolicy.bookmarkable.BookmarkPolicyFacetFallbackFactory;
import org.apache.causeway.core.metamodel.facets.object.callbacks.CallbackFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.choices.enums.ChoicesFacetFromEnumFactory;
import org.apache.causeway.core.metamodel.facets.object.domainobject.DomainObjectAnnotationFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.domainobjectlayout.DomainObjectLayoutFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.domainservice.annotation.DomainServiceFacetAnnotationFactory;
import org.apache.causeway.core.metamodel.facets.object.domainservicelayout.DomainServiceLayoutFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.grid.GridFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.hidden.HiddenTypeFacetFromAuthorizationFactory;
import org.apache.causeway.core.metamodel.facets.object.ignore.annotation.RemoveAnnotatedMethodsFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.ignore.javalang.IteratorFilteringFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.ignore.javalang.RemoveMethodsFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.logicaltype.LogicalTypeMalformedValidator;
import org.apache.causeway.core.metamodel.facets.object.logicaltype.classname.LogicalTypeFacetFromClassNameFactory;
import org.apache.causeway.core.metamodel.facets.object.navchild.NavigableSubtreeFacetPostProcessor;
import org.apache.causeway.core.metamodel.facets.object.navparent.annotation.NavigableParentAnnotationFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.objectvalidprops.impl.ObjectValidPropertiesFacetImplFactory;
import org.apache.causeway.core.metamodel.facets.object.support.ObjectSupportFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.title.annotation.TitleAnnotationFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.value.annotcfg.ValueFacetForValueAnnotationOrAnyMatchingValueSemanticsFacetFactory;
import org.apache.causeway.core.metamodel.facets.object.viewmodel.ViewModelFacetFactory;
import org.apache.causeway.core.metamodel.facets.param.autocomplete.method.ActionParameterAutoCompleteFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.param.choices.methodnum.ActionParameterChoicesFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.param.defaults.methodnum.ActionParameterDefaultsFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.param.disable.method.ActionParameterDisabledFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.param.hide.method.ActionParameterHiddenFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.param.layout.ParameterLayoutFacetFactory;
import org.apache.causeway.core.metamodel.facets.param.mandatory.dflt.MandatoryFacetOnParametersDefaultFactory;
import org.apache.causeway.core.metamodel.facets.param.name.ParameterNameFacetFactoryUsingReflection;
import org.apache.causeway.core.metamodel.facets.param.parameter.ParameterAnnotationFacetFactory;
import org.apache.causeway.core.metamodel.facets.param.validate.method.ActionParameterValidationFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.properties.accessor.PropertyAccessorFacetViaAccessorFactory;
import org.apache.causeway.core.metamodel.facets.properties.autocomplete.method.PropertyAutoCompleteFacetMethodFactory;
import org.apache.causeway.core.metamodel.facets.properties.choices.method.PropertyChoicesFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.properties.defaults.method.PropertyDefaultFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.properties.disabled.inferred.DisabledFacetOnPropertyInferredFactory;
import org.apache.causeway.core.metamodel.facets.properties.mandatory.dflt.MandatoryFacetOnProperyDefaultFactory;
import org.apache.causeway.core.metamodel.facets.properties.property.PropertyAnnotationFacetFactory;
import org.apache.causeway.core.metamodel.facets.properties.property.mandatory.jaxb.MandatoryFacetFromXmlElementAnnotationFactory;
import org.apache.causeway.core.metamodel.facets.properties.propertylayout.PropertyLayoutFacetFactory;
import org.apache.causeway.core.metamodel.facets.properties.update.PropertySetterFacetFactory;
import org.apache.causeway.core.metamodel.facets.properties.validating.dflt.PropertyValidateFacetDefaultFactory;
import org.apache.causeway.core.metamodel.facets.properties.validating.method.PropertyValidateFacetViaMethodFactory;
import org.apache.causeway.core.metamodel.facets.value.semantics.ValueSemanticsAnnotationFacetFactory;
import org.apache.causeway.core.metamodel.postprocessors.all.CssOnActionFromConfiguredRegexPostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.all.DescribedAsFromTypePostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.all.MixinSanityChecksValidator;
import org.apache.causeway.core.metamodel.postprocessors.all.SanityChecksValidator;
import org.apache.causeway.core.metamodel.postprocessors.all.i18n.SynthesizeObjectNamingPostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.all.i18n.TranslationPostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.allbutparam.authorization.AuthorizationPostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.members.SynthesizeDomainEventsForMixinPostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.members.navigation.NavigationFacetFromHiddenTypePostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.object.ProjectionFacetsPostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.param.ChoicesAndDefaultsPostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.param.TypicalLengthFromTypePostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.properties.DisabledFromImmutablePostProcessor;
import org.apache.causeway.core.metamodel.progmodel.ProgrammingModelAbstract;
import org.apache.causeway.core.metamodel.services.title.TitlesAndTranslationsValidator;

import lombok.Getter;
import lombok.experimental.Accessors;

final class ProgrammingModelDefault
extends ProgrammingModelAbstract {

    ProgrammingModelDefault(final MetaModelContext mmc, final Iterable<MetaModelRefiner> refiners) {
        super(mmc.getServiceInjector());

        // acts on the peer objects (FacetedMethod etc), rather than ObjectMembers etc
        addFacetFactories();

        // only during post processing will the mixed-in members be resolved
        // and made available to the ObjectSpecification
        addPostProcessors();

        addValidators();

        for (var metaModelRefiner : refiners) {
            metaModelRefiner.refineProgrammingModel(this);
        }

        this.mixinNamingStrategy = new _MixedInMemberNamingStrategy();
    }

    @Getter(onMethod_={@Override}) @Accessors(fluent=true)
    private final MixinNamingStrategy mixinNamingStrategy;

    // -- HELPER

    private void addFacetFactories() {
        var mmc = getMetaModelContext();

        // must be first, so any Facets created can be replaced by other
        // FacetFactorys later.
        addFactory(FacetProcessingOrder.A1_FALLBACK_DEFAULTS, new FallbackFacetFactory(mmc));

        addFactory(FacetProcessingOrder.B1_OBJECT_NAMING, new LogicalTypeFacetFromClassNameFactory(mmc));
        addFactory(FacetProcessingOrder.B1_OBJECT_NAMING, new DomainServiceFacetAnnotationFactory(mmc));
        addFactory(FacetProcessingOrder.B1_OBJECT_NAMING, new ValueFacetForValueAnnotationOrAnyMatchingValueSemanticsFacetFactory(mmc));

        addFactory(FacetProcessingOrder.C1_METHOD_REMOVING, new IteratorFilteringFacetFactory(mmc));

        addFactory(FacetProcessingOrder.C1_METHOD_REMOVING, new RemoveMethodsFacetFactory(mmc));

        addFactory(FacetProcessingOrder.C1_METHOD_REMOVING, new RemoveAnnotatedMethodsFacetFactory(mmc));

        // must be before any other FacetFactories that install MandatoryFacet.class facets
        addFactory(FacetProcessingOrder.D1_MANDATORY_SUPPORT, new MandatoryFacetOnProperyDefaultFactory(mmc));
        addFactory(FacetProcessingOrder.D1_MANDATORY_SUPPORT, new MandatoryFacetOnParametersDefaultFactory(mmc));

        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new PropertyValidateFacetDefaultFactory(mmc));

        // enum support
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ChoicesFacetFromEnumFactory(mmc));

        // properties
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new PropertyAccessorFacetViaAccessorFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new PropertySetterFacetFactory(mmc));

        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new PropertyValidateFacetViaMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new PropertyChoicesFacetViaMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new PropertyAutoCompleteFacetMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new PropertyDefaultFacetViaMethodFactory(mmc));

        // collections
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new CollectionAccessorFacetViaAccessorFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new SortedByFacetAnnotationFactory(mmc));

        // actions
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ActionParameterHiddenFacetViaMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ActionParameterDisabledFacetViaMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ActionValidationFacetViaMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ActionParameterValidationFacetViaMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ActionParameterChoicesFacetViaMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ActionParameterAutoCompleteFacetViaMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ActionParameterDefaultsFacetViaMethodFactory(mmc));

        // members in general
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new NamedFacetForMemberViaMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new DescribedAsFacetForMemberViaMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new DisableForContextFacetViaMethodFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new HideForContextFacetViaMethodFactory(mmc));

        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new CallbackFacetFactory(mmc));

        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ObjectValidPropertiesFacetImplFactory(mmc));

        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new BookmarkPolicyFacetFallbackFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new HomePageFacetAnnotationFactory(mmc));

        // must come after CssClassFacetOnMemberFactory
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new CssClassFacetOnActionFromConfiguredRegexFactory(mmc));

        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ViewModelFacetFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new JaxbFacetFactory(mmc));

        // must come after RecreatableObjectFacetFactory
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new DomainObjectAnnotationFacetFactory(mmc));

        // must come after the property/collection accessor+mutator facet factories
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ActionAnnotationFacetFactory(mmc));
        // after the ActionAnnotationFacetFactory so that takes precedent for contributed associations
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new PropertyAnnotationFacetFactory(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new MandatoryFacetFromXmlElementAnnotationFactory(mmc));
        // after the ActionAnnotationFacetFactory so that takes precedent for contributed associations
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new CollectionAnnotationFacetFactory(mmc));

        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ParameterNameFacetFactoryUsingReflection(mmc));
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ParameterAnnotationFacetFactory(mmc));

        // must come after DomainObjectAnnotationFacetFactory & MixinFacetFactory
        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ContributingFacetFromMixinFacetFactory(mmc));

        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new HiddenTypeFacetFromAuthorizationFactory(mmc));

        addFactory(FacetProcessingOrder.E1_MEMBER_MODELLING, new ValueSemanticsAnnotationFacetFactory(mmc));

        addFactory(FacetProcessingOrder.F1_LAYOUT, new GridFacetFactory(mmc));

        // must come before DomainObjectLayoutFacetFactory
        // (so subscribers on titleUi event etc can override) ... no longer true since we have event rank that overrules anyway
        addFactory(FacetProcessingOrder.F1_LAYOUT, new TitleAnnotationFacetFactory(mmc));

        addFactory(FacetProcessingOrder.F1_LAYOUT, new ObjectSupportFacetFactory(mmc));
        addFactory(FacetProcessingOrder.F1_LAYOUT, new NavigableParentAnnotationFacetFactory(mmc));

        addFactory(FacetProcessingOrder.F1_LAYOUT, new DomainServiceLayoutFacetFactory(mmc));
        addFactory(FacetProcessingOrder.F1_LAYOUT, new DomainObjectLayoutFacetFactory(mmc));

        // must come after MultiLine
        addFactory(FacetProcessingOrder.F1_LAYOUT, new PropertyLayoutFacetFactory(mmc));
        addFactory(FacetProcessingOrder.F1_LAYOUT, new ParameterLayoutFacetFactory(mmc));
        addFactory(FacetProcessingOrder.F1_LAYOUT, new ActionLayoutFacetFactory(mmc));
        addFactory(FacetProcessingOrder.F1_LAYOUT, new CollectionLayoutFacetFactory(mmc));

        // written to not trample over TypeOf if already installed
        addFactory(FacetProcessingOrder.Z1_FINALLY, new CollectionFacetFactory(mmc));
        // must come after CollectionFacetFactory
        addFactory(FacetProcessingOrder.Z1_FINALLY, new ParentedFacetSinceCollectionFactory(mmc));

        // should come near the end, after any facets that install PropertySetterFacet have run.
        addFactory(FacetProcessingOrder.Z1_FINALLY, new DisabledFacetOnPropertyInferredFactory(mmc));

        addFactory(FacetProcessingOrder.Z1_FINALLY, new ViewModelSemanticCheckingFacetFactory(mmc));
    }

    private void addPostProcessors() {
        var mmc = getMetaModelContext();

        // must run before Object nouns are used
        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new SynthesizeObjectNamingPostProcessor(mmc));

        // requires member names to have settled
        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new CssOnActionFromConfiguredRegexPostProcessor(mmc));

        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new DescribedAsFromTypePostProcessor(mmc));
        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new TypicalLengthFromTypePostProcessor(mmc));
        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new ChoicesAndDefaultsPostProcessor(mmc));
        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new DisabledFromImmutablePostProcessor(mmc));
        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new SynthesizeDomainEventsForMixinPostProcessor(mmc));
        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new ProjectionFacetsPostProcessor(mmc));

        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new NavigableSubtreeFacetPostProcessor(mmc));
        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new NavigationFacetFromHiddenTypePostProcessor(mmc));

        // must be after all named facets and description facets have been installed
        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new TranslationPostProcessor(mmc));

        addPostProcessor(PostProcessingOrder.A1_BUILTIN, new AuthorizationPostProcessor(mmc));
    }

    private void addValidators() {
        var mmc = getMetaModelContext();

        addValidator(ValidationOrder.A1_BUILTIN, new SanityChecksValidator(mmc));
        addValidator(ValidationOrder.A1_BUILTIN, new ValidatorDomainIncludeAnnotationEnforcesMetamodelContribution(mmc));

        // should this instead be a post processor, alongside TranslationPostProcessor ?
        addValidator(ValidationOrder.A1_BUILTIN, new TitlesAndTranslationsValidator(mmc));
        addValidator(ValidationOrder.A1_BUILTIN, new ActionAnnotationShouldEnforceConcreteTypeToBeIncludedWithMetamodelValidator(mmc));
        addValidator(ValidationOrder.A1_BUILTIN, new ActionOverloadingValidator(mmc));
        addValidator(ValidationOrder.A1_BUILTIN, new LogicalTypeMalformedValidator(mmc));

        addValidator(ValidationOrder.A1_BUILTIN, new MixinSanityChecksValidator(mmc));
    }

}
