/*
 * Copyright 2019, 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.code.proto;

import com.google.common.collect.Iterators;
import com.google.common.collect.UnmodifiableIterator;
import com.google.common.io.Files;
import io.spine.code.proto.DescriptorReference.ResourceReference;
import io.spine.util.Exceptions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junitpioneer.jupiter.TempDirectory;
import org.junitpioneer.jupiter.TempDirectory.TempDir;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

import static com.google.common.collect.Lists.newArrayList;
import static io.spine.code.proto.DescriptorReference.loadFromResources;
import static io.spine.code.proto.given.DescriptorReferenceTestEnv.knownTypesRef;
import static io.spine.code.proto.given.DescriptorReferenceTestEnv.randomRef;
import static io.spine.code.proto.given.DescriptorReferenceTestEnv.smokeTestModelCompilerRef;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertThrows;

@SuppressWarnings("HardcodedLineSeparator") /* Resistance to different separators is
                                               a part of the test. */
@DisplayName("Descriptor reference should")
@ExtendWith(TempDirectory.class)
class DescriptorReferenceTest {

    private static final String WINDOWS_SEPARATOR = "\r\n";
    private static final String UNIX_SEPARATOR = "\n";

    @Test
    @DisplayName("be unaffected by Windows line separator")
    void unaffectedByCrLf(@TempDir Path path) {
        assertResourcesWrittenWithSeparator(path, WINDOWS_SEPARATOR);
    }

    @Test
    @DisplayName("be unaffected by Unix line separator")
    void unaffectedByLf(@TempDir Path path) {
        assertResourcesWrittenWithSeparator(path, UNIX_SEPARATOR);
    }

    @Test
    @DisplayName("ignore previous content of the `desc.ref` file")
    void ignorePreviousDescRef(@TempDir Path path) {
        DescriptorReference firstReference = randomRef();
        firstReference.writeTo(path);
        assertResourcesLoaded(path, firstReference);

        DescriptorReference secondReference = knownTypesRef();
        secondReference.writeTo(path);
        assertResourcesLoaded(path, firstReference, secondReference);

        DescriptorReference thirdReference = smokeTestModelCompilerRef();
        thirdReference.writeTo(path);
        assertResourcesLoaded(path, firstReference, secondReference, thirdReference);
    }

    @Test
    @DisplayName("write a reference with expected content")
    void properContent(@TempDir Path path) throws IOException {
        DescriptorReference knownTypes = knownTypesRef();
        knownTypes.writeTo(path);

        File descRef = path.resolve(DescriptorReference.FILE_NAME)
                           .toFile();
        List<String> linesWritten = Files.readLines(descRef, UTF_8);
        assertEquals(1, linesWritten.size());
        String actual = linesWritten.get(0);
        String expected = knownTypes.asResource()
                                    .toString();
        assertEquals(expected, actual);
    }

    private static void assertResourcesWrittenWithSeparator(@TempDir Path path,
                                                            String separator) {
        DescriptorReference knownTypes = knownTypesRef();
        DescriptorReference smokeTestModelCompiler = smokeTestModelCompilerRef();
        knownTypes.writeToWithSeparator(path, separator);
        smokeTestModelCompiler.writeToWithSeparator(path, separator);

        assertResourcesLoaded(path, knownTypes, smokeTestModelCompiler);
    }

    @Test
    @DisplayName("throw if the referenced path points to a file instead of a directory")
    void throwsOnDirectory(@TempDir Path path) {
        DescriptorReference knownTypes = knownTypesRef();
        File newFile = createFileUnderPath(path);
        assertThrows(IllegalStateException.class, () -> knownTypes.writeTo(newFile.toPath()));
    }

    @Test
    @DisplayName("throw if the referenced path is null")
    void throwsOnNull() {
        DescriptorReference knownTypes = knownTypesRef();
        assertThrows(NullPointerException.class, () -> knownTypes.writeTo(null));
    }

    private static void assertResourcesLoaded(Path path, DescriptorReference... expected) {
        Path descRef = path.resolve(DescriptorReference.FILE_NAME);
        Iterator<ResourceReference> existingDescriptors = loadFromResources(asIterator(descRef));
        List<ResourceReference> result = newArrayList(existingDescriptors);
        assertEquals(expected.length, result.size());
        for (DescriptorReference reference : expected) {
            assertTrue(result.contains(reference.asResource()));
        }
    }

    private static UnmodifiableIterator<URL> asIterator(Path descRef) {
        try {
            return Iterators.singletonIterator(descRef.toUri()
                                                      .toURL());
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("TailRecursion")
    // As long as the specified path does not contain files with names matching a random UUID value,
    // recursive calls will not happen.
    private static File createFileUnderPath(Path path) {
        // Ensures no existing file with such name.
        String fileName = UUID.randomUUID()
                              .toString();
        File result = new File(path.toFile(), fileName);
        if (result.exists()) {
            return createFileUnderPath(path);
        }
        try {
            result.createNewFile();
            return result;
        } catch (IOException e) {
            throw Exceptions.newIllegalStateException(e,
                                                      "Could not create a temporary file in %s.",
                                                      path.toAbsolutePath());
        }
    }
}


@@ -2819,61 +2819,92 @@
 sert
-ResourcesWrittenWithSeparator(path, WINDOWS_SEPARATOR
+DescriptorRefsWrittenCorrectly(path, WINDOWS_SEPARATOR, knownTypesRef(), randomRef()
 );%0A 
@@ -3040,58 +3040,89 @@
 sert
-ResourcesWrittenWithSeparator(path, UNIX_SEPARATOR
+DescriptorRefsWrittenCorrectly(path, UNIX_SEPARATOR, knownTypesRef(), randomRef()
 );%0A 
@@ -4457,37 +4457,38 @@
 sert
-ResourcesWrittenWithSeparator
+DescriptorRefsWrittenCorrectly
 (@Te
@@ -4560,24 +4560,25 @@
             
+ 
 String separ
@@ -4585,69 +4585,108 @@
 ator
-) %7B%0A        DescriptorReference knownTypes = knownTypesRef();
+,%0A                                                             DescriptorReference... descriptors) %7B
 %0A   
@@ -4682,32 +4682,37 @@
 tors) %7B%0A        
+for (
 DescriptorRefere
@@ -4719,189 +4719,107 @@
 nce 
-smokeTestModelCompiler = smokeTestModelCompilerRef();%0A        knownTypes.writeToWithSeparator(path, separator);%0A        smokeTestModelCompiler.writeToWithSeparator(path, separator);
+descriptor : descriptors) %7B%0A            descriptor.writeToWithSeparator(path, separator);%0A        %7D
 %0A%0A  
@@ -4856,42 +4856,19 @@
 th, 
-knownTypes, smokeTestModelCompiler
+descriptors
 );%0A 

Improve API in tests.