/*
 * 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.ignite.network;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicReferenceArray;
import org.apache.ignite.network.annotations.MessageGroup;

/**
 * Base class for {@link MessagingService} implementations.
 */
public abstract class AbstractMessagingService implements MessagingService {
    /**
     * Class holding a pair of a message group class and corresponding handlers.
     */
    private static class Handler {
        /** Message group. */
        final Class<?> messageGroup;

        /** Handlers, registered for the corresponding message group. */
        final List<NetworkMessageHandler> handlers;

        /**
         * Constructor.
         *
         * @param messageGroup Message group.
         * @param handlers     Message handlers.
         */
        Handler(Class<?> messageGroup, List<NetworkMessageHandler> handlers) {
            this.messageGroup = messageGroup;
            this.handlers = handlers;
        }
    }

    /** Mapping from group type (array index) to a list of registered message handlers. */
    private final AtomicReferenceArray<Handler> handlersByGroupType = new AtomicReferenceArray<>(Short.MAX_VALUE + 1);

    /** {@inheritDoc} */
    @Override
    public void addMessageHandler(Class<?> messageGroup, NetworkMessageHandler handler) {
        handlersByGroupType.getAndUpdate(getMessageGroupType(messageGroup), oldHandler -> {
            if (oldHandler == null) {
                return new Handler(messageGroup, List.of(handler));
            }

            if (oldHandler.messageGroup != messageGroup) {
                throw new IllegalArgumentException(String.format(
                        "Handlers are already registered for a message group with the same group ID "
                                + "but different class. Group ID: %d, given message group: %s, existing message group: %s",
                        getMessageGroupType(messageGroup), messageGroup, oldHandler.messageGroup
                ));
            }

            var handlers = new ArrayList<NetworkMessageHandler>(oldHandler.handlers.size() + 1);

            handlers.addAll(oldHandler.handlers);
            handlers.add(handler);

            return new Handler(messageGroup, handlers);
        });
    }

    /**
     * Extracts the message group ID from a class annotated with {@link MessageGroup}.
     *
     * @param messageGroup Message group.
     * @return Message group ID.
     */
    private static short getMessageGroupType(Class<?> messageGroup) {
        MessageGroup annotation = messageGroup.getAnnotation(MessageGroup.class);

        assert annotation != null : "No MessageGroup annotation present on " + messageGroup;

        short groupType = annotation.groupType();

        assert groupType >= 0 : "Group type must not be negative";

        return groupType;
    }

    /**
     * Returns registered handlers for the given group ID.
     *
     * @param groupType Message group ID.
     * @return Registered message handlers.
     */
    protected final Collection<NetworkMessageHandler> getMessageHandlers(short groupType) {
        assert groupType >= 0 : "Group type must not be negative";

        Handler result = handlersByGroupType.get(groupType);

        return result == null ? List.of() : result.handlers;
    }
}
