// Copyright 2017 The Bazel Authors. 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
//
// 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.google.devtools.build.android;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Tests for {@link DensitySpecificResourceFilter}. */
@RunWith(JUnit4.class)
public class DensitySpecificResourceFilterTest {

  private Path tmp;

  @Test public void testNoDensityResources() throws Exception {
    checkTransformedResources(
        ImmutableList.of(
            "test/res/layout/derp.xml",
            "test/res/layout-ldrtl/derp.png",
            "test/res/layout-ldrtl-xhdpi/derp.png",
            "test/res/drawable-en/test.png"),
        ImmutableList.of(
            "test/res/layout/derp.xml",
            "test/res/layout-ldrtl/derp.png",
            "test/res/layout-ldrtl-xhdpi/derp.png",
            "test/res/drawable-en/test.png"),
        ImmutableList.of("xxhdpi"));
  }

  @Test public void testDontFilterXml() throws Exception {
    checkTransformedResources(
        ImmutableList.of(
            "test/res/drawable-xxhdpi/derp.xml",
            "test/res/drawable-ldpi/derp.xml",
            "test/res/drawable-en/test.png"),
        ImmutableList.of(
            "test/res/drawable-xxhdpi/derp.xml",
            "test/res/drawable-ldpi/derp.xml",
            "test/res/drawable-en/test.png"),
        ImmutableList.of("xxhdpi"));
  }

  @Test public void testNoDpiNoFilter() throws Exception {
    checkTransformedResources(
        ImmutableList.of(
            "test/res/drawable-nodpi/test.png",
            "test/res/drawable-xxhdpi/test.png",
            "test/res/drawable-ldpi/test.png"),
        ImmutableList.of(
            "test/res/drawable-nodpi/test.png",
            "test/res/drawable-xxhdpi/test.png"),
        ImmutableList.of("xxhdpi"));
  }

  @Test public void testChoosesExactDensity() throws Exception {
    checkTransformedResources(
        ImmutableList.of(
            "test/res/drawable-nodpi/test.png",
            "test/res/drawable/test.png",
            "test/res/drawable-mdpi/derp.png",
            "test/res/drawable-xxhdpi/test.png",
            "test/res/drawable-mdpi/test.png",
            "test/res/drawable-xxxhdpi/test.png"),
        ImmutableList.of(
            "test/res/drawable-nodpi/test.png",
            "test/res/drawable/test.png",
            "test/res/drawable-mdpi/derp.png",
            "test/res/drawable-xxhdpi/test.png"),
        ImmutableList.of("xxhdpi"));
  }

  @Test public void testMultipleDensities() throws Exception {
    checkTransformedResources(
        ImmutableList.of(
            "test/res/drawable-mdpi/test.png",
            "test/res/drawable-xxhdpi/test.png",
            "test/res/drawable-xxxhdpi/test.png"),
        ImmutableList.of(
            "test/res/drawable-xxhdpi/test.png",
            "test/res/drawable-xxxhdpi/test.png"),
        ImmutableList.of("xxhdpi", "xxxhdpi"));
  }

  @Test public void testNonStandardDensity() throws Exception {
    checkTransformedResources(
        ImmutableList.of(
            "test/res/drawable-mdpi/test.png",
            "test/res/drawable-hdpi/test.png",
            "test/res/drawable-280dpi/test.png",
            "test/res/drawable-xhdpi/test.png",
            "test/res/drawable-340dpi/test.png",
            "test/res/drawable-xxhdpi/test.png"),
        ImmutableList.of(
            "test/res/drawable-280dpi/test.png",
            "test/res/drawable-340dpi/test.png"),
        ImmutableList.of("280dpi", "340dpi"));
  }

  @Test public void testUnknownDensityFails() {
    assertThrows(
        MergingException.class,
        () ->
            checkTransformedResources(
                ImmutableList.<String>of(),
                ImmutableList.<String>of(),
                ImmutableList.of("xxhdpi", "322dpi")));
  }

  @Test public void testPrefersHigherQualityWhenAffinityExact() throws Exception {
    checkTransformedResources(
        ImmutableList.of(
            "test/res/drawable-xxxhdpi/test.png",
            "test/res/drawable-mdpi/test.png"),
        ImmutableList.of(
            "test/res/drawable-xxxhdpi/test.png"),
        ImmutableList.of("xhdpi"));
  }

  @Test public void testPrefersExactlyDoubleDensity() throws Exception {
    checkTransformedResources(
        ImmutableList.of(
            "test/res/drawable-hdpi/test.png",
            "test/res/drawable-mdpi/test.png"),
        ImmutableList.of(
            "test/res/drawable-hdpi/test.png"),
        ImmutableList.of("ldpi"));
  }


  @Test public void testGroupsByQualifiers() throws Exception {
    checkTransformedResources(
        ImmutableList.of(
            "test/res/drawable-en-ldrtl-hdpi/test.png",
            "test/res/drawable-mdpi/test.png"),
        ImmutableList.of(
            "test/res/drawable-en-ldrtl-hdpi/test.png",
            "test/res/drawable-mdpi/test.png"),
        ImmutableList.of("ldpi"));
  }

  @Test public void testQualifiers() throws Exception {
    // This list checks that we correctly grab a -xhdpi-v19 version of the drawable when it is not
    // present at our requested density.
    checkTransformedResources(
        ImmutableList.of(
            "res/drawable-mdpi-v11/btn_edit_pressed.png",
            "res/drawable-mdpi/btn_edit_pressed.png",
            "res/drawable-xhdpi-v19/btn_edit_pressed.png",
            "res/drawable-tvdpi-v11/btn_edit_pressed.png",
            "res/drawable-hdpi-v11/btn_edit_pressed.png",
            "res/drawable-xhdpi-v11/btn_edit_pressed.png",
            "res/drawable-xxhdpi-v19/btn_edit_pressed.png",
            "res/drawable-hdpi/btn_edit_pressed.png",
            "res/drawable-tvdpi/btn_edit_pressed.png",
            "res/drawable-xxhdpi/btn_edit_pressed.png",
            "res/drawable-xhdpi/btn_edit_pressed.png",
            "res/drawable-tvdpi-v19/btn_edit_pressed.png",
            "res/drawable-hdpi-v19/btn_edit_pressed.png",
            "res/drawable-xxhdpi-v11/btn_edit_pressed.png"),
        ImmutableList.of(
            "res/drawable-mdpi-v11/btn_edit_pressed.png",
            "res/drawable-mdpi/btn_edit_pressed.png",
            "res/drawable-xhdpi-v19/btn_edit_pressed.png"),
        ImmutableList.of("mdpi"));
  }

  @Test public void fullIntegration() throws Exception {
    createFiles(
        "res/drawable-mdpi-v11/btn_edit_pressed.png",
        "res/drawable-mdpi/btn_edit_pressed.png",
        "res/drawable-xhdpi-v19/btn_edit_pressed.png",
        "res/drawable-tvdpi-v11/btn_edit_pressed.png",
        "res/drawable-hdpi-v11/btn_edit_pressed.png",
        "res/drawable-xhdpi-v11/btn_edit_pressed.png",
        "res/drawable-xxhdpi-v19/btn_edit_pressed.png",
        "res/drawable-hdpi/btn_edit_pressed.png",
        "res/drawable-tvdpi/btn_edit_pressed.png",
        "res/drawable-xxhdpi/btn_edit_pressed.png",
        "res/drawable-xhdpi/btn_edit_pressed.png",
        "res/drawable-tvdpi-v19/btn_edit_pressed.png",
        "res/drawable-hdpi-v19/btn_edit_pressed.png",
        "res/drawable-xxhdpi-v11/btn_edit_pressed.png");

    Path out = Files.createTempDirectory(this.toString());
    Path working = Files.createTempDirectory(this.toString());
    ImmutableList<String> densities = ImmutableList.of("xxhdpi");

    DensitySpecificResourceFilter filter =
        new DensitySpecificResourceFilter(densities, out, working);

    final Path filteredResourceDir = filter.filter(tmp);
    final List<Path> filteredResources = new ArrayList<>();
    try {
      Files.walkFileTree(
          filteredResourceDir,
          EnumSet.of(FileVisitOption.FOLLOW_LINKS),
          Integer.MAX_VALUE,
          new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path path, BasicFileAttributes attrs)
                throws IOException {
              filteredResources.add(filteredResourceDir.relativize(path));
              return FileVisitResult.CONTINUE;
            }
          });
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    FileSystem fs = FileSystems.getDefault();
    assertThat(filteredResources)
        .containsExactly(
            fs.getPath("res/drawable-xxhdpi/btn_edit_pressed.png"),
            fs.getPath("res/drawable-xxhdpi-v11/btn_edit_pressed.png"),
            fs.getPath("res/drawable-xxhdpi-v19/btn_edit_pressed.png"));
  }

  @Before
  public void setUpEnvironment() throws Exception {
    tmp = Files.createTempDirectory(this.toString());
  }

  private ImmutableList <Path> createFiles(String... pathStrings) throws IOException {
    ImmutableList.Builder<Path> paths = ImmutableList.builder();
    for (String pathString : pathStrings) {
      final Path path = tmp.resolve(pathString);
      Files.createDirectories(path.getParent());
      Files.createFile(path);
      paths.add(path);
    }
    return paths.build();
  }

  private void checkTransformedResources(List<String> resourcePaths,
      List<String> expectedResourcePaths, List<String> densities) throws MergingException {
    List<Path> resources = new ArrayList<>();
    FileSystem fs = FileSystems.getDefault();
    for (String resourcePath : resourcePaths) {
      resources.add(fs.getPath(resourcePath));
    }

    DensitySpecificResourceFilter transformer =
        new DensitySpecificResourceFilter(densities, null, null);
    Set<Path> resourcesToRemove = new HashSet<>(transformer.getResourceToRemove(resources));
    List<String> actualResourcePaths = new ArrayList<>();

    for (Path p : resources) {
      if (!resourcesToRemove.contains(p)) {
        actualResourcePaths.add(p.toString());
      }
    }

    List<String> expected = Lists.transform(expectedResourcePaths, n -> fs.getPath(n).toString());
    assertThat(actualResourcePaths).containsExactlyElementsIn(expected);
  }
}
