/*
 * Copyright 2022, TeamDev. All rights reserved.
 *
 * 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
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.server;

import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.spine.reflect.GenericTypeIndex;

import java.util.HashSet;
import java.util.Set;

import static io.spine.util.Exceptions.newIllegalStateException;

/**
 * Abstract for builder of service classes.
 *
 * @param <T>
 *         the type of service to be produced
 * @param <B>
 *         the self-type of the builder for return type covariance
 */
public abstract class AbstractServiceBuilder<T, B extends AbstractServiceBuilder<T, B>> {

    private final Set<BoundedContext> contexts = new HashSet<>();

    /**
     * Returns this instance with the overloaded type.
     */
    abstract B self();

    /**
     * Adds the passed bounded context to be served by the service.
     */
    @CanIgnoreReturnValue
    public B add(BoundedContext context) {
        contexts.add(context);
        return self();
    }

    /**
     * Tells if this builder has no bounded context.
     */
    boolean isEmpty() {
        return contexts.isEmpty();
    }

    /**
     * Obtains bounded contexts already added before this call.
     */
    ImmutableSet<BoundedContext> contexts() {
        return ImmutableSet.copyOf(contexts);
    }

    /**
     * Verifies if this builder has at least one bounded context added.
     *
     * @throws IllegalStateException otherwise
     */
    void checkNotEmpty() throws IllegalStateException {
        if (isEmpty()) {
            throw newIllegalStateException(
                    "`%s` must serve at least one Bounded Context.",
                    serviceClass().getCanonicalName());
        }
    }

    /**
     * Obtains the class of the service this builder creates.
     */
    Class<T> serviceClass() {
        GenericTypeIndex<AbstractServiceBuilder<T, B>> typeIndex = () -> 0;
        @SuppressWarnings("unchecked")
        var cls = (Class<T>) typeIndex.argumentIn(
                (Class<? extends AbstractServiceBuilder<T, B>>) getClass());
        return cls;
    }

    /**
     * Excludes the passed bounded context from being served by the service.
     */
    @CanIgnoreReturnValue
    public B remove(BoundedContext context) {
        contexts.remove(context);
        return self();
    }

    /**
     * Tells if the builder already contains the passed bounded context.
     */
    public boolean contains(BoundedContext context) {
        return contexts.contains(context);
    }

    /**
     * Creates a new instance of the service.
     */
    abstract T build();
}

@@ -3036,230 +3036,109 @@
     
-GenericTypeIndex%3CAbstractServiceBuilder%3CT, B%3E%3E typeIndex = () -%3E 0;%0A        @SuppressWarnings(%22unchecked%22)%0A        var cls = (Class%3CT%3E) typeIndex.argumentIn(%0A                (Class%3C? extends AbstractServiceBuilder%3CT, B%3E%3E) 
+@SuppressWarnings(%22unchecked%22)%0A        var cls = (Class%3CT%3E) GenericParameter.SERVICE_TYPE.argumentIn(
 getC
@@ -3680,11 +3680,243 @@
 build();
+%0A%0A    @SuppressWarnings(%22rawtypes%22)%0A    private enum GenericParameter implements GenericTypeIndex%3CAbstractServiceBuilder%3E %7B%0A%0A        SERVICE_TYPE;%0A%0A        @Override%0A        public int index() %7B%0A            return 0;%0A        %7D%0A    %7D
 %0A%7D%0A

Fix obtaining generic type arg.