/*
 * Copyright 2018, TeamDev. All rights reserved.
 *
 * 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.type;

import com.google.protobuf.Any;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import io.spine.annotation.Internal;
import io.spine.code.proto.Type;
import io.spine.code.proto.TypeSet;

import java.util.Optional;
import java.util.Set;

import static java.util.stream.Collectors.toSet;

/**
 * All Protobuf types known to the application.
 *
 * <p>This includes types generated by Protobuf compiler and collected into
 * the {@linkplain io.spine.code.proto.FileDescriptors#KNOWN_TYPES resource file}.
 *
 * @author Mikhail Mikhaylov
 * @author Alexander Litus
 * @author Alexander Yevsyukov
 * @author Dmytro Dashenkov
 */
@Internal
public class KnownTypes {

    private final TypeSet types;

    /**
     * Builds the instance by loading known types and composing lookup map for type URLs.
     */
    private KnownTypes(TypeSet types) {
        this.types = types;
    }

    /**
     * Retrieves a Java class name generated for the Protobuf type by its type url
     * to be used to parse {@link Message Message} from {@link Any}.
     *
     * @param typeUrl {@link Any} type url
     * @return Java class name
     * @throws UnknownTypeException if there is no such type known to the application
     */
    public ClassName getClassName(TypeUrl typeUrl) throws UnknownTypeException {
        if (!instance().contains(typeUrl)) {
            throw new UnknownTypeException(typeUrl.getTypeName());
        }
        final ClassName result = instance().get(typeUrl);
        return result;
    }

    /**
     * Retrieves Protobuf type URLs known to the application.
     */
    public Set<TypeUrl> getAllUrls() {
        return types.types()
                    .stream()
                    .map(Type::url)
                    .collect(toSet());
    }

    /**
     * Assembles the known types into a {@link JsonFormat.TypeRegistry}.
     *
     * <p>The resulting registry contains all the known Protobuf message types.
     */
    public JsonFormat.TypeRegistry typeRegistry() {
        return types.toJsonPrinterRegistry();
    }

    /**
     * Retrieves all the types that belong to the given package or its subpackages.
     *
     * @param packageName proto package name
     * @return set of {@link TypeUrl TypeUrl}s of types that belong to the given package
     */
    public Set<TypeUrl> getAllFromPackage(String packageName) {
        final Set<TypeUrl> result = getAllUrls().stream()
                                                .filter(url -> url.toName()
                                                                  .belongsTo(packageName))
                                                .collect(toSet());
        return result;
    }

    /**
     * Shows if the given {@link TypeUrl} is known the system.
     *
     * @param typeUrl the {@link TypeUrl} to look up
     * @return {@code true} if the given type is known, {@code false} otherwise
     */
    public boolean contains(TypeUrl typeUrl) {
        TypeName name = typeUrl.toName();
        boolean result = types.contains(name);
        return result;
    }

    /**
     * Finds a {@link Type} by its name.
     *
     * @see TypeSet#find(TypeName)
     */
    Optional<Type<?, ?>> find(TypeName typeName) {
        Optional<Type<?, ?>> type = types.find(typeName);
        return type;
    }

    private Type get(TypeName name) throws UnknownTypeException {
        Type result = types.find(name)
                           .orElseThrow(() -> new UnknownTypeException(name.value()));
        return result;
    }

    private ClassName get(TypeUrl typeUrl) {
        Type type = get(typeUrl.toName());
        ClassName result = type.javaClassName();
        return result;
    }

    /**
     * Retrieves the singleton instance of {@code KnownTypes}.
     */
    public static KnownTypes instance() {
        return Singleton.INSTANCE.value;
    }

    @SuppressWarnings("NonSerializableFieldInSerializableClass")
    private enum Singleton {
        INSTANCE;

        private final KnownTypes value = new KnownTypes(Loader.load());
    }
}

@@ -2020,27 +2020,27 @@
 by its type 
-url
+URL
 %0A     * to b

Fix grammar in Javadoc.