/*
 * Copyright (c) 2010-2017. Axon Framework
 * Licensed 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.axonframework.commandhandling.model.inspection;

import org.axonframework.commandhandling.CommandMessage;
import org.axonframework.commandhandling.model.ForwardingMode;
import org.axonframework.common.property.Property;
import org.axonframework.eventhandling.EventMessage;
import org.axonframework.messaging.annotation.MessageHandlingMember;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.axonframework.common.property.PropertyAccessStrategy.getProperty;

/**
 * Implementation of a {@link ChildEntity} that uses annotations on a target entity to resolve event and command
 * handlers.
 *
 * @param <P> the parent entity type.
 * @param <C> the child entity type.
 */
public class AnnotatedChildEntity<P, C> implements ChildEntity<P> {

    private static final String EMPTY_STRING = "";

    private final EntityModel<C> entityModel;
    private final ForwardingMode eventForwardingMode;
    private final String eventRoutingKey;
    private final Map<String, MessageHandlingMember<? super P>> commandHandlers;
    private final BiFunction<EventMessage<?>, P, Stream<C>> eventTargetResolver;

    /**
     * Initiates a new AnnotatedChildEntity instance that uses the provided {@code entityModel} to delegate command
     * and event handling to an annotated child entity.
     *
     * @param entityModel           a {@link EntityModel} describing
     *                              the entity.
     * @param forwardCommands       flag indicating whether commands should be forwarded to the entity
     * @param eventForwardingMode   a {@link ForwardingMode} enum describing the
     *                              used forwarding mode for events.
     *                              only entity specific events should be forwarded
     * @param eventRoutingKey       a {@link String} specifying the routing key for all events within this
     *                              Entity.
     * @param commandTargetResolver resolver for command handler methods on the target
     * @param eventTargetResolver   resolver for event handler methods on the target
     */
    @SuppressWarnings("unchecked")
    public AnnotatedChildEntity(EntityModel<C> entityModel,
                                boolean forwardCommands,
                                ForwardingMode eventForwardingMode,
                                String eventRoutingKey,
                                BiFunction<CommandMessage<?>, P, C> commandTargetResolver,
                                BiFunction<EventMessage<?>, P, Stream<C>> eventTargetResolver) {
        this.entityModel = entityModel;
        this.eventForwardingMode = eventForwardingMode;
        this.eventRoutingKey = eventRoutingKey;
        this.eventTargetResolver = eventTargetResolver;
        this.commandHandlers = new HashMap<>();
        if (forwardCommands) {
            entityModel.commandHandlers().forEach(
                    (commandType, childHandler) -> commandHandlers.put(
                            commandType,
                            new ChildForwardingCommandMessageHandlingMember<>(childHandler, commandTargetResolver)
                    )
            );
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public void publish(EventMessage<?> msg, P declaringInstance) {
        if (eventForwardingMode == ForwardingMode.NONE) {
            return;
        }

        eventTargetResolver.apply(msg, declaringInstance)
                           .collect(Collectors.toList())
                           .forEach(target -> publishToTarget(msg, target));
    }

    @SuppressWarnings("unchecked")
    @Override
    public Map<String, MessageHandlingMember<? super P>> commandHandlers() {
        return commandHandlers;
    }

    @SuppressWarnings("unchecked")
    private void publishToTarget(EventMessage<?> msg, C target) {
        if (eventForwardingMode == ForwardingMode.ALL) {
            entityModel.publish(msg, target);
        } else if (eventForwardingMode == ForwardingMode.ROUTING_KEY) {
            Property eventRoutingProperty = getProperty(msg.getPayloadType(), eventRoutingKey());

            Object eventRoutingValue = eventRoutingProperty.getValue(msg.getPayload());
            Object entityIdentifier = entityModel.getIdentifier(target);

            if (Objects.equals(eventRoutingValue, entityIdentifier)) {
                entityModel.publish(msg, target);
            }
        }
    }

    private String eventRoutingKey() {
        return Objects.equals(eventRoutingKey, EMPTY_STRING) ? entityModel.routingKey() : eventRoutingKey;
    }
}

@@ -4035,57 +4035,57 @@
     
-if (
 event
-ForwardingMode == ForwardingMode.NONE) %7B
+TargetResolver.apply(msg, declaringInstance)
 %0A   
@@ -4097,24 +4097,16 @@
     
-return;%0A
         
 %7D%0A%0A 
@@ -4105,67 +4105,58 @@
     
-%7D%0A%0A
        
- eventTargetResolver.apply(msg, declaringInstance
+.filter(target -%3E filterTarget(msg, target)
 )%0A  
@@ -4256,31 +4256,35 @@
 rget -%3E 
-publishToTarget
+entityModel.publish
 (msg, ta
@@ -4341,242 +4341,165 @@
     
-@Override%0A    public Map%3CString, MessageHandlingMember%3C? super P%3E%3E commandHandlers() %7B%0A        return commandHandlers;
+private boolean filterTarget(EventMessage msg, C target) %7B%0A        if (eventForwardingMode == ForwardingMode.NONE) %7B
 %0A    
-%7D%0A%0A
     
-@SuppressWarnings(%22unchecked%22)%0A    private void publishToTarget(EventMessage%3C?%3E msg, C target) %7B%0A       
+    return false;%0A        %7D else
  if 
@@ -4560,40 +4560,19 @@
     
-entityModel.publish(msg, target)
+return true
 ;%0A  
@@ -4914,20 +4914,23 @@
         
-if (
+return 
 Objects.
@@ -4972,19 +4972,17 @@
 ntifier)
-) %7B
+;
 %0A       
@@ -4978,32 +4978,34 @@
 r);%0A        
+%7D%0A
         
 entityModel.
@@ -4996,65 +4996,21 @@
     
-entityModel.publish(msg, target);%0A            %7D%0A        %7D
+return false;
 %0A   
@@ -5164,11 +5164,176 @@
 y;%0A    %7D
+%0A%0A    @SuppressWarnings(%22unchecked%22)%0A    @Override%0A    public Map%3CString, MessageHandlingMember%3C? super P%3E%3E commandHandlers() %7B%0A        return commandHandlers;%0A    %7D
 %0A%7D%0A

Move forwarding mode to filter function