/*
 * Copyright (C) 2012 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.tools.lint.checks;

import static com.android.SdkConstants.FN_PUBLIC_TXT;
import static com.android.SdkConstants.FN_RESOURCE_TEXT;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.builder.model.AndroidArtifact;
import com.android.builder.model.AndroidLibrary;
import com.android.builder.model.AndroidProject;
import com.android.builder.model.Dependencies;
import com.android.builder.model.MavenCoordinates;
import com.android.builder.model.Variant;
import com.android.ide.common.repository.GradleCoordinate;
import com.android.testutils.TestUtils;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Project;
import com.google.common.base.Charsets;
import com.google.common.io.Files;

import org.mockito.stubbing.OngoingStubbing;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

@SuppressWarnings("javadoc")
public class PrivateResourceDetectorTest extends AbstractCheckTest {
    @Override
    protected Detector getDetector() {
        return new PrivateResourceDetector();
    }

    @Override
    protected boolean allowCompilationErrors() {
        // Some of these unit tests are still relying on source code that references
        // unresolved symbols etc.
        return true;
    }

    public void testPrivateInXml() throws Exception {
        assertEquals(""
                + "res/layout/private.xml:11: Warning: The resource @string/my_private_string is marked as private in com.android.tools:test-library [PrivateResource]\n"
                + "            android:text=\"@string/my_private_string\" />\n"
                + "                          ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
                + "0 errors, 1 warnings\n",
                lintProject(xml("res/layout/private.xml", ""
                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                        + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
                        + "              android:id=\"@+id/newlinear\"\n"
                        + "              android:orientation=\"vertical\"\n"
                        + "              android:layout_width=\"match_parent\"\n"
                        + "              android:layout_height=\"match_parent\">\n"
                        + "\n"
                        + "    <TextView\n"
                        + "            android:layout_width=\"wrap_content\"\n"
                        + "            android:layout_height=\"wrap_content\"\n"
                        + "            android:text=\"@string/my_private_string\" />\n"
                        + "\n"
                        + "    <TextView\n"
                        + "            android:layout_width=\"wrap_content\"\n"
                        + "            android:layout_height=\"wrap_content\"\n"
                        + "            android:text=\"@string/my_public_string\" />\n"
                        + "</LinearLayout>\n")));
    }

    @SuppressWarnings("ClassNameDiffersFromFileName")
    public void testPrivateInJava() throws Exception {
        assertEquals(""
                + "src/test/pkg/Private.java:3: Warning: The resource @string/my_private_string is marked as private in com.android.tools:test-library [PrivateResource]\n"
                + "        int x = R.string.my_private_string; // ERROR\n"
                + "                ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
                + "0 errors, 1 warnings\n",
                lintProject(java("src/test/pkg/Private.java", ""
                        + "public class Private {\n"
                        + "    void test() {\n"
                        + "        int x = R.string.my_private_string; // ERROR\n"
                        + "        int y = R.string.my_public_string; // OK\n"
                        + "        int y = android.R.string.my_private_string; // OK (not in project namespace)\n"
                        + "    }\n"
                        + "}\n")));
    }

    public void testOverride() throws Exception {
        assertEquals(""
                + "res/layout/my_private_layout.xml: Warning: Overriding @layout/my_private_layout which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
                + "res/values/strings.xml:5: Warning: Overriding @string/my_private_string which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
                + "    <string name=\"my_private_string\">String 1</string>\n"
                + "                  ~~~~~~~~~~~~~~~~~\n"
                + "res/values/strings.xml:9: Warning: Overriding @string/my_private_string which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
                + "    <item type=\"string\" name=\"my_private_string\">String 1</item>\n"
                + "                              ~~~~~~~~~~~~~~~~~\n"
                + "res/values/strings.xml:12: Warning: Overriding @string/my_private_string which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
                + "    <string tools:override=\"false\" name=\"my_private_string\">String 2</string>\n"
                + "                                         ~~~~~~~~~~~~~~~~~\n"
                + "0 errors, 4 warnings\n",
                lintProject(xml("res/values/strings.xml", ""
                        + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                        + "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n"
                        + "\n"
                        + "    <string name=\"app_name\">LibraryProject</string>\n"
                        + "    <string name=\"my_private_string\">String 1</string>\n"
                        + "    <string name=\"my_public_string\">String 2</string>\n"
                        + "    <string name=\"string3\"> @my_private_string </string>\n"
                        + "    <string name=\"string4\"> @my_public_string </string>\n"
                        + "    <item type=\"string\" name=\"my_private_string\">String 1</item>\n"
                        + "    <dimen name=\"my_private_string\">String 1</dimen>\n" // unrelated
                        + "    <string tools:ignore=\"PrivateResource\" name=\"my_private_string\">String 2</string>\n"
                        + "    <string tools:override=\"false\" name=\"my_private_string\">String 2</string>\n"
                        + "    <string tools:override=\"true\" name=\"my_private_string\">String 2</string>\n"
                        + "\n"
                        + "</resources>\n"),
                        xml("res/layout/my_private_layout.xml", "<LinearLayout/>"),
                        xml("res/layout/my_public_layout.xml", "<LinearLayout/>")));
    }

    @SuppressWarnings("ClassNameDiffersFromFileName")
    public void testIds() throws Exception {
        // Regression test for https://code.google.com/p/android/issues/detail?id=183851
        assertEquals("No warnings.",
                lintProject(
                        xml("res/layout/private.xml", ""
                                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                                + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
                                + "              android:id=\"@+id/title\"\n"
                                + "              android:orientation=\"vertical\"\n"
                                + "              android:layout_width=\"match_parent\"\n"
                                + "              android:layout_height=\"match_parent\"/>\n"),
                        java("src/test/pkg/Private.java", ""
                                + "public class Private {\n"
                                + "    void test() {\n"
                                + "        int x = R.id.title; // ERROR\n"
                                + "    }\n"
                                + "}\n")));
    }

    @Override
    protected TestLintClient createClient() {
        return new TestLintClient() {
            @NonNull
            @Override
            protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
                return new Project(this, dir, referenceDir) {
                    @Override
                    public boolean isGradleProject() {
                        return true;
                    }

                    @Nullable
                    @Override
                    public AndroidProject getGradleProjectModel() {
                        // First version which supported private resources; this does not
                        // need to track later versions we release
                        return createMockProject("1.3.0-alpha2", 3);
                    }

                    @Nullable
                    @Override
                    public Variant getCurrentVariant() {
                        try {
                            AndroidLibrary library = createMockLibrary(
                                    "com.android.tools:test-library:1.0.0",
                                    ""
                                            + "int string my_private_string 0x7f040000\n"
                                            + "int string my_public_string 0x7f040001\n"
                                            + "int layout my_private_layout 0x7f040002\n"
                                            + "int id title 0x7f040003\n",
                                    ""
                                            + ""
                                            + "string my_public_string\n",
                                    Collections.<AndroidLibrary>emptyList()
                            );
                            AndroidArtifact artifact = createMockArtifact(
                                    Collections.singletonList(library));

                            Variant variant = mock(Variant.class);
                            when(variant.getMainArtifact()).thenReturn(artifact);
                            return variant;
                        } catch (Exception e) {
                            fail(e.toString());
                            return null;
                        }
                    }
                };
            }
        };
    }

    public static AndroidProject createMockProject(String modelVersion, int apiVersion) {
        AndroidProject project = mock(AndroidProject.class);
        when(project.getApiVersion()).thenReturn(apiVersion);
        when(project.getModelVersion()).thenReturn(modelVersion);

        return project;
    }

    public static AndroidArtifact createMockArtifact(List<AndroidLibrary> libraries) {
        Dependencies dependencies = mock(Dependencies.class);
        when(dependencies.getLibraries()).thenReturn(libraries);

        AndroidArtifact artifact = mock(AndroidArtifact.class);
        when(artifact.getDependencies()).thenReturn(dependencies);
        when(artifact.getCompileDependencies()).thenReturn(dependencies);

        return artifact;
    }

    public static AndroidLibrary createMockLibrary(String name,
            String allResources, String publicResources,
            List<AndroidLibrary> dependencies)
            throws IOException {
        final File tempDir = TestUtils.createTempDirDeletedOnExit();

        Files.write(allResources, new File(tempDir, FN_RESOURCE_TEXT), Charsets.UTF_8);
        File publicTxtFile = new File(tempDir, FN_PUBLIC_TXT);
        if (publicResources != null) {
            Files.write(publicResources, publicTxtFile, Charsets.UTF_8);
        }
        AndroidLibrary library = mock(AndroidLibrary.class);
        when(library.getPublicResources()).thenReturn(publicTxtFile);
        GradleCoordinate c = GradleCoordinate.parseCoordinateString(name);
        assertNotNull(c);
        MavenCoordinates coordinates = mock(MavenCoordinates.class);
        when(coordinates.getGroupId()).thenReturn(c.getGroupId());
        when(coordinates.getArtifactId()).thenReturn(c.getArtifactId());
        when(coordinates.getVersion()).thenReturn(c.getRevision());
        when(library.getResolvedCoordinates()).thenReturn(coordinates);
        when(library.getBundle()).thenReturn(new File("intermediates" + File.separator +
                "exploded-aar" + File.separator + name));

        // Work around wildcard capture
        //when(library.getLibraryDependencies()).thenReturn(dependencies);
        List libraryDependencies = library.getLibraryDependencies();
        OngoingStubbing<List> setter = when(libraryDependencies);
        setter.thenReturn(dependencies);
        return library;
    }
}
