/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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
 *
 * 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 com.android.build.gradle.internal.tasks;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.builder.model.PackagingOptions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Files;


import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;

/**
 * Tests for {@link MergeJavaResourcesTaskTest}
 */
public class MergeJavaResourcesTaskTest {

    @Mock
    PackagingOptions packagingOptions;

    @Mock
    MergeJavaResourcesTask task;

    MergeJavaResourcesTask.FileFilter fileFilter;

    private static File sPackagedJarExpansionFolder, sMergedFolder;
    private static File sExpandedJar1, sExpandedJar2, sExpandedJar3;

    /**
     * Create temporary folders to simulate expanded folders with java resources embedded
     */
    @BeforeClass
    public static void prepareFolders() throws IOException {
        sPackagedJarExpansionFolder = createTmpFolder(null /* parent */);

        sExpandedJar1 = createTmpFolder(sPackagedJarExpansionFolder);
        sExpandedJar2 = createTmpFolder(sPackagedJarExpansionFolder);
        sExpandedJar3 = createTmpFolder(sPackagedJarExpansionFolder);

        sMergedFolder = createTmpFolder(null /* parent */);
    }

    /**
     * After each test, all folders are cleaned.
     */
    @After
    public void cleanUp() {
        cleanContents(sExpandedJar1);
        cleanContents(sExpandedJar2);
        cleanContents(sExpandedJar3);
        cleanContents(sMergedFolder);
    }

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        when(task.getExpandedFolders()).thenReturn(ImmutableList.of(sPackagedJarExpansionFolder));
        assertTrue(listFiles(sMergedFolder).length == 0);
    }

    @Test
    public void testSimpleCopy() throws IOException {

        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        assertFalse(new File(sMergedFolder, "foo/text.properties").exists());
        File changedFile = createFile(sExpandedJar1, "foo/text.properties");
        fileFilter.handleChanged(sMergedFolder, changedFile);
        assertTrue(new File(sMergedFolder, "foo/text.properties").exists());
    }

    @Test
    public void testSimpleExclusion() throws IOException {

        when(packagingOptions.getExcludes()).thenReturn(
                ImmutableSet.of("foo/text.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar1, "foo/text.properties"));
        assertTrue(listFiles(sMergedFolder).length == 0);
    }

    @Test
    public void testExclusionFromMultipleFiles() throws IOException {
        when(packagingOptions.getExcludes()).thenReturn(
                ImmutableSet.of("foo/text.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar1, "foo/text.properties"));
        fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar2, "foo/text.properties"));
        assertTrue(listFiles(sMergedFolder).length == 0);
    }

    @Test
    public void testMultipleExclusions() throws IOException {
        when(packagingOptions.getExcludes()).thenReturn(
                ImmutableSet.of("foo/text.properties", "bar/other.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar1, "foo/text.properties"));
        fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar2, "bar/other.properties"));
        assertTrue(listFiles(sMergedFolder).length == 0);
    }

    @Test
    public void textNonExclusion() throws IOException {
        when(packagingOptions.getExcludes()).thenReturn(
                ImmutableSet.of("foo/text.properties", "bar/other.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar1, "foo/text.properties"));
        fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar2, "bar/other.properties"));
        // this one should be copied over.
        fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar2, "bar/foo.properties"));
        assertTrue(listFiles(sMergedFolder).length == 1);
        assertTrue(new File(sMergedFolder, "bar/foo.properties").exists());
    }

    @Test
    public void testSingleMerge() throws IOException {
        when(packagingOptions.getMerges()).thenReturn(ImmutableSet.of("foo/text.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        createFile(sExpandedJar1, "foo/text.properties", "one");
        File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");

        // one has changed...
        fileFilter.handleChanged(sMergedFolder, secondFile);

        File mergedFile = new File(sMergedFolder, "foo/text.properties");
        assertTrue(mergedFile.exists());
        assertContentInAnyOrder(
                Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
                , ImmutableList.of("one", "two"));
    }

    @Test
    public void testMultipleMerges() throws IOException {
        when(packagingOptions.getMerges()).thenReturn(ImmutableSet.of("foo/text.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        createFile(sExpandedJar1, "foo/text.properties", "one");
        File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
        createFile(sExpandedJar3, "foo/text.properties", "three");

        // one has changed...
        fileFilter.handleChanged(sMergedFolder, secondFile);

        File mergedFile = new File(sMergedFolder, "foo/text.properties");
        assertTrue(mergedFile.exists());
        assertContentInAnyOrder(
                Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
                , ImmutableList.of("one", "two", "three"));
    }

    @Test
    public void testMergeAddon() throws IOException {
        when(packagingOptions.getMerges()).thenReturn(ImmutableSet.of("foo/text.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        createFile(sExpandedJar1, "foo/text.properties", "one");
        File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");

        // simulate one has changed to create initial version
        fileFilter.handleChanged(sMergedFolder, secondFile);

        File mergedFile = new File(sMergedFolder, "foo/text.properties");
        assertTrue(mergedFile.exists());
        assertContentInAnyOrder(
                Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
                , ImmutableList.of("one", "two"));

        // add a new one.
        File thirdFile = createFile(sExpandedJar3, "foo/text.properties", "three");
        fileFilter.handleChanged(sMergedFolder, thirdFile);

        assertTrue(mergedFile.exists());
        assertContentInAnyOrder(
                Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
                , ImmutableList.of("one", "two", "three"));
    }

    @Test
    public void testMergeUpdate() throws IOException {
        when(packagingOptions.getMerges()).thenReturn(ImmutableSet.of("foo/text.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        createFile(sExpandedJar1, "foo/text.properties", "one");
        File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
        createFile(sExpandedJar3, "foo/text.properties", "three");

        // simulate one has changed to create initial version
        fileFilter.handleChanged(sMergedFolder, secondFile);

        File mergedFile = new File(sMergedFolder, "foo/text.properties");
        assertTrue(mergedFile.exists());
        assertContentInAnyOrder(
                Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
                , ImmutableList.of("one", "two", "three"));

        // change one...
        assertTrue(secondFile.delete());
        secondFile = createFile(sExpandedJar2, "foo/text.properties", "deux");

        fileFilter.handleChanged(sMergedFolder, secondFile);

        assertTrue(mergedFile.exists());
        assertContentInAnyOrder(
                Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
                , ImmutableList.of("one", "deux", "three"));
    }

    @Test
    public void testMergeRemoval() throws IOException {
        when(packagingOptions.getMerges()).thenReturn(ImmutableSet.of("foo/text.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        createFile(sExpandedJar1, "foo/text.properties", "one");
        File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
        createFile(sExpandedJar3, "foo/text.properties", "three");

        // simulate one has changed to create initial version
        fileFilter.handleChanged(sMergedFolder, secondFile);

        File mergedFile = new File(sMergedFolder, "foo/text.properties");
        assertTrue(mergedFile.exists());
        assertContentInAnyOrder(
                Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
                , ImmutableList.of("one", "two", "three"));

        // remove one...
        assertTrue(secondFile.delete());

        fileFilter.handleRemoved(sMergedFolder, secondFile);

        assertTrue(mergedFile.exists());
        assertContentInAnyOrder(
                Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
                , ImmutableList.of("one", "three"));
    }

    @Test
    public void testPickFirst() throws IOException {
        when(packagingOptions.getPickFirsts()).thenReturn(ImmutableSet.of("foo/text.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        // simulate the three elements were added.
        fileFilter.handleChanged(sMergedFolder,
                createFile(sExpandedJar1, "foo/text.properties", "one"));
        fileFilter.handleChanged(sMergedFolder,
                createFile(sExpandedJar2, "foo/text.properties", "two"));
        fileFilter.handleChanged(sMergedFolder,
                createFile(sExpandedJar3, "foo/text.properties", "three"));

        File mergedFile = new File(sMergedFolder, "foo/text.properties");
        assertTrue(mergedFile.exists());
        String mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
        assertTrue(mergedContent.equals("one")
                || mergedContent.equals("two")
                || mergedContent.equals("three"));
    }

    @Test
    public void testPickFirstUpdate() throws IOException {
        when(packagingOptions.getPickFirsts()).thenReturn(ImmutableSet.of("foo/text.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        File firstFile = createFile(sExpandedJar1, "foo/text.properties", "one");
        File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
        File thirdFile = createFile(sExpandedJar3, "foo/text.properties", "three");

        // simulate the three elements were added.
        fileFilter.handleChanged(sMergedFolder, firstFile);
        fileFilter.handleChanged(sMergedFolder, secondFile);
        fileFilter.handleChanged(sMergedFolder, thirdFile);

        File mergedFile = new File(sMergedFolder, "foo/text.properties");
        assertTrue(mergedFile.exists());
        String mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
        if (mergedContent.equals("one")) {
            assertTrue(firstFile.delete());
            createFile(sExpandedJar1, "foo/text.properties", "un");
            fileFilter.handleChanged(sMergedFolder, firstFile);
            assertEquals("un", Files.asCharSource(mergedFile, Charset.defaultCharset()).read());
        }
        if (mergedContent.equals("two")) {
            assertTrue(thirdFile.delete());
            createFile(sExpandedJar2, "foo/text.properties", "deux");
            fileFilter.handleChanged(sMergedFolder, secondFile);
            assertEquals("deux", Files.asCharSource(mergedFile, Charset.defaultCharset()).read());
        }
        if (mergedContent.equals("three")) {
            assertTrue(thirdFile.delete());
            createFile(sExpandedJar3, "foo/text.properties", "trois");
            fileFilter.handleChanged(sMergedFolder, thirdFile);
            assertEquals("trois", Files.asCharSource(mergedFile, Charset.defaultCharset()).read());
        }
    }

    @Test
    public void testPickFirstRemoval() throws IOException {
        when(packagingOptions.getPickFirsts()).thenReturn(ImmutableSet.of("foo/text.properties"));
        fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);

        File firstFile = createFile(sExpandedJar1, "foo/text.properties", "one");
        File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
        File thirdFile = createFile(sExpandedJar3, "foo/text.properties", "three");

        // simulate the three elements were added.
        fileFilter.handleChanged(sMergedFolder, firstFile);
        fileFilter.handleChanged(sMergedFolder, secondFile);
        fileFilter.handleChanged(sMergedFolder, thirdFile);

        File mergedFile = new File(sMergedFolder, "foo/text.properties");
        assertTrue(mergedFile.exists());
        String mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
        if (mergedContent.equals("one")) {
            assertTrue(firstFile.delete());
            fileFilter.handleRemoved(sMergedFolder, firstFile);
            mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
            assertTrue(mergedContent.equals("two") || mergedContent.equals("three"));
        }
        if (mergedContent.equals("two")) {
            assertTrue(thirdFile.delete());
            fileFilter.handleRemoved(sMergedFolder, secondFile);
            mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
            assertTrue(mergedContent.equals("one") || mergedContent.equals("three"));
        }
        if (mergedContent.equals("three")) {
            assertTrue(thirdFile.delete());
            fileFilter.handleRemoved(sMergedFolder, thirdFile);
            mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
            assertTrue(mergedContent.equals("one") || mergedContent.equals("two"));
        }
    }

    private static void assertContentInAnyOrder(String content, Iterable<String> subStrings) {
        int length = 0;
        for (String subString : subStrings) {
            length += subString.length();
            assertTrue(content.contains(subString));
        }
        assertEquals(length, content.length());
    }

    private static File createTmpFolder(@Nullable File parent) throws IOException {
        File folder = File.createTempFile("tmp", "dir");
        assertTrue(folder.delete());
        if (parent != null) {
            folder = new File(parent, folder.getName());
        }
        assertTrue(folder.mkdirs());
        return folder;
    }

    @NonNull private static File[] listFiles(@NonNull File folder) {
        File[] files = folder.listFiles();
        if (files == null) {
            return new File[0];
        }
        return files;
    }

    private static void cleanContents(File folder) {
        for (File f : listFiles(folder)) {
            if (f.isDirectory()) {
                cleanContents(f);
            }
            assertTrue(f.delete());
        }
    }

    @NonNull private static File createFile(
            @NonNull File parent,
            @NonNull String archivePath) throws IOException {
        return createFile(parent, archivePath, null /* content */);
    }

    @NonNull private static File createFile(
            @NonNull File parent,
            @NonNull String archivePath,
            @Nullable String content) throws IOException {

        File newFile = new File(parent, archivePath);
        if (!newFile.getParentFile().exists()) {
            assertTrue(newFile.getParentFile().mkdirs());
        }
        String fileContent = content == null ? "test!" : content;
        Files.append(fileContent, newFile, Charset.defaultCharset());
        return newFile;
    }
}
