/*
 * 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.Duration;
import com.google.protobuf.Empty;
import com.google.protobuf.Message;
import com.google.protobuf.StringValue;
import com.google.protobuf.Timestamp;
import io.spine.base.Error;
import io.spine.code.proto.Type;
import io.spine.option.EntityOption;
import io.spine.option.IfMissingOption;
import io.spine.test.types.Task;
import io.spine.test.types.TaskId;
import io.spine.test.types.TaskName;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertThrows;

/**
 * Tests {@link io.spine.type.KnownTypes}.
 *
 * @author Alexander Litus
 * @author Alexander Yevsyukov
 */
@DisplayName("KnownTypes should")
class KnownTypesTest {

    private final KnownTypes knownTypes = KnownTypes.instance();

    @Test
    @DisplayName("obtain type URLs of known proto types")
    void typeUrls() {
        Set<TypeUrl> typeUrls = knownTypes.getAllUrls();

        assertFalse(typeUrls.isEmpty());
    }

    @Test
    @DisplayName("contain types defined by Spine framework")
    void containsSpineTypes() {
        assertContainsClass(EntityOption.class);
        assertContainsClass(Error.class);
        assertContainsClass(IfMissingOption.class);
    }

    @Test
    @DisplayName("contain types from Google Protobuf")
    void containsProtobufTypes() {
        assertContainsClass(Any.class);
        assertContainsClass(Timestamp.class);
        assertContainsClass(Duration.class);
        assertContainsClass(Empty.class);
    }

    private void assertContainsClass(Class<? extends Message> msgClass) {
        TypeUrl typeUrl = TypeUrl.of(msgClass);
        ClassName className = knownTypes.getClassName(typeUrl);

        assertEquals(ClassName.of(msgClass), className);
    }

    @Test
    @DisplayName("contain nested proto types")
    void containNestedProtoTypes() {
        TypeUrl typeUrl = TypeUrl.from(EntityOption.Kind.getDescriptor());
        ClassName className = knownTypes.getClassName(typeUrl);

        assertEquals(ClassName.of(EntityOption.Kind.class), className);
    }

    @Test
    @DisplayName("find type URL by type name")
    void findTypeUrlByName() {
        TypeUrl typeUrlExpected = TypeUrl.from(StringValue.getDescriptor());

        Optional<TypeUrl> typeUrlActual = knownTypes.find(typeUrlExpected.toName())
                                                    .map(Type::url);
        assertTrue(typeUrlActual.isPresent());
        assertEquals(typeUrlExpected, typeUrlActual.get());
    }

    @Test
    @DisplayName("obtain all types under a given package")
    void typesFromPackage() {
        TypeUrl taskId = TypeUrl.from(TaskId.getDescriptor());
        TypeUrl taskName = TypeUrl.from(TaskName.getDescriptor());
        TypeUrl task = TypeUrl.from(Task.getDescriptor());

        String packageName = "spine.test.types";

        Set<TypeUrl> packageTypes = knownTypes.getAllFromPackage(packageName);

        assertThat(packageTypes).hasSize(3);
        assertTrue(packageTypes.containsAll(Arrays.asList(taskId, taskName, task)));
    }

    @Test
    @DisplayName("return empty set of types for unknown package")
    void emptyTypeSetForUnknownPackage() {
        String packageName = "com.foo.invalid.package";
        Set<?> emptyTypesCollection = knownTypes.getAllFromPackage(packageName);
        assertNotNull(emptyTypesCollection);
        assertTrue(emptyTypesCollection.isEmpty());
    }

    @Test
    @DisplayName("do not return types by package prefix")
    void noTypesByPrefix() {
        String prefix = "spine.test.ty"; // "spine.test.types" is a valid package

        Collection<TypeUrl> packageTypes = knownTypes.getAllFromPackage(prefix);
        assertTrue(packageTypes.isEmpty());
    }

    @Test
    @DisplayName("throw UnknownTypeException for requesting info on an unknown type")
    void throwOnUnkownType() {
        TypeUrl unexpectedUrl = TypeUrl.parse("prefix/unexpected.type");
        assertThrows(
                UnknownTypeException.class,
                () -> knownTypes.getClassName(unexpectedUrl)
        );
    }
}

@@ -1030,16 +1030,64 @@
 .type;%0A%0A
+import com.google.common.truth.IterableSubject;%0A
 import c
@@ -1620,33 +1620,8 @@
 t;%0A%0A
-import java.util.Arrays;%0A
 impo
@@ -1691,24 +1691,81 @@
 .util.Set;%0A%0A
+import static com.google.common.collect.ImmutableSet.of;%0A
 import stati
@@ -4428,32 +4428,62 @@
 eName);%0A%0A       
+ IterableSubject assertTypes =
  assertThat(pack
@@ -4491,16 +4491,37 @@
 geTypes)
+;%0A        assertTypes
 .hasSize
@@ -4536,36 +4536,24 @@
      assertT
-rue(packageT
 ypes.contain
@@ -4560,22 +4560,13 @@
 sAll
-(Arrays.asList
+In(of
 (tas
@@ -4586,17 +4586,16 @@
 , task))
-)
 ;%0A    %7D%0A

Extend usage of Truth.