/*
 *  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.viewer.wicket.model.models.interaction.prop;

import java.util.Optional;
import java.util.stream.Stream;

import org.apache.wicket.model.ChainingModel;

import org.apache.causeway.applib.annotation.Where;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.internal.assertions._Assert;
import org.apache.causeway.commons.internal.base._Lazy;
import org.apache.causeway.commons.internal.exceptions._Exceptions;
import org.apache.causeway.core.metamodel.interactions.managed.ManagedProperty;
import org.apache.causeway.core.metamodel.interactions.managed.PropertyInteraction;
import org.apache.causeway.core.metamodel.interactions.managed.PropertyNegotiationModel;
import org.apache.causeway.core.metamodel.object.ManagedObjects;
import org.apache.causeway.viewer.wicket.model.models.interaction.BookmarkedObjectWkt;
import org.apache.causeway.viewer.wicket.model.models.interaction.HasBookmarkedOwnerAbstract;

/**
 * The parent (container) model of multiple <i>property models</i> which implement
 * {@link ChainingModel}.
 * <pre>
 * IModel[PropertyInteraction] ... setNumber(ComplexNumber complexNumber)
 * |
 * +-- PropertyUiModel ... bound to complexNumber (PropertyNegotiationModel)
 * </pre>
 *
 * @implSpec the state of pending parameters PropertyNegotiationModel is held transient,
 * that means it does not survive a serialization/de-serialization cycle; instead
 * is recreated with property defaults
 *
 * @see ChainingModel
 */
public class PropertyInteractionWkt
extends HasBookmarkedOwnerAbstract<PropertyInteraction> {

    private static final long serialVersionUID = 1L;

    private final String memberId;
    private final Where where;
    private Can<UiPropertyWkt> childModels;

    public PropertyInteractionWkt(
            final BookmarkedObjectWkt bookmarkedObject,
            final String memberId,
            final Where where) {

        super(bookmarkedObject);
        this.memberId = memberId;
        this.where = where;
    }

    @Override
    protected PropertyInteraction load() {
        // restore the transient lazy field - don't evaluate yet
        propertyNegotiationModel =
                _Lazy.threadSafe(this::startPropertyNegotiation);
        return loadPropertyInteraction();
    }

    @Override
    public void detach() {
        super.detach();
        propertyNegotiationModel.clear();
    }

    public final PropertyInteraction propertyInteraction() {
        return getObject();
    }

    // -- LAZY BINDING

    public Stream<UiPropertyWkt> streamPropertyUiModels() {
        if(childModels==null) {
            childModels = Can.ofSingleton(new UiPropertyWkt(this));
        }
        return childModels.stream();
    }

    // -- PROPERTY NEGOTIATION WITH MEMOIZATION (TRANSIENT)

    private transient _Lazy<Optional<PropertyNegotiationModel>> propertyNegotiationModel;

    public final PropertyNegotiationModel propertyNegotiationModel() {
        guardAgainstNotAttached();
        return propertyNegotiationModel.get()
                .orElseThrow(()->_Exceptions.noSuchElement(memberId));
    }

    public void resetPropertyToDefault() {
        propertyNegotiationModel.clear();
    }

    private Optional<PropertyNegotiationModel> startPropertyNegotiation() {
        var propIa = propertyInteraction();
        var prop = propIa.getManagedProperty().orElseThrow();
        ManagedObjects.refreshViewmodel(prop.getOwner(), /* bookmark provider*/ null);
        return propIa.startPropertyNegotiation();
    }

    private PropertyInteraction loadPropertyInteraction() {
        return PropertyInteraction.wrap(
            ManagedProperty.lookupProperty(getBookmarkedOwner(), memberId, where)
            .orElseThrow(()->_Exceptions.noSuchElement("property '%s' in %s",
                    memberId,
                    getBookmarkedOwner().objSpec())));
    }

    /**
     * safe guard against access to the model while it is not attached
     */
    private void guardAgainstNotAttached() {
        if(!this.isAttached()) {
            // start over
            getObject(); // re-attach
        }
        _Assert.assertTrue(this.isAttached(), ()->"model is not attached");
    }

}
