/*
 * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import javadoc.tester.JavadocTester;
import toolbox.ToolBox;

/*
 * @test
 * @bug 8318082
 * @library /tools/lib ../../lib
 * @modules jdk.javadoc/jdk.javadoc.internal.tool
 * @build toolbox.ToolBox javadoc.tester.*
 * @run main TestSelfIndexing
 */
public class TestSelfIndexing extends JavadocTester {

    public static void main(String... args) throws Exception {
        new TestSelfIndexing().runTests();
    }

    private final ToolBox tb = new ToolBox();

    /*
     * Pages derived from other pages must not be indexed and may not
     * cross-reference each other except for navigation ergonomics.
     *
     * For example, it's okay for all-index.html to reference deprecated-list.html;
     * but it is not okay, for all-index.html to reference an anchor, such as
     * deprecated-list.html#java.lang.Object.finalize()
     */
    @Test
    public void test(Path base) throws Exception {
        Path src = base.resolve("src");
        int i = 0;
        // try to start a search tag (i) with the same letter, H,
        // as the class, Hello, and (ii) with some other letter, P
        for (var l : List.of("H", "P")) {
            // try all markup constructs that cause indexing
            for (var t : List.of("<h2>%s</h2>", "{@index %s}", "{@systemProperty %s}")) {
                tb.writeJavaFiles(src, """
                        package pkg;

                        /** @deprecated %s */
                        public class Hello { }
                        """.formatted(t.formatted(l)));

                Path out = base.resolve("out-" + i);
                checking(t.formatted(l) + "; results in: " + out);
                setAutomaticCheckNoStacktrace(true); // no exceptions
                javadoc("-d", out.toString(),
                        "--source-path", src.toString(),
                        "pkg");
                // check that index pages do not refer to derived pages
                try (var s = findIndexFiles(out)) {
                    record PathAndString(Path path, String str) { }
                    Optional<PathAndString> r = s.map(p -> {
                                try {
                                    return new PathAndString(p, Files.readString(p));
                                } catch (IOException e) {
                                    throw new UncheckedIOException(e);
                                }
                            })
                            .flatMap(pac -> findLinksToDerivedPages(pac.str)
                                    .map(link -> new PathAndString(pac.path, link)))
                            .findAny();
                    r.ifPresentOrElse(p -> failed(p.toString()), () -> passed(t.formatted(l)));
                }
                i++;
            }
        }
    }

    // ----------- support and infrastructure -----------

    private static Stream<Path> findIndexFiles(Path start) throws IOException {
        return Files.find(start, Integer.MAX_VALUE, (path, attr) -> {
            if (attr.isDirectory())
                return false;
            var fileName = path.getFileName().toString();
            if (!fileName.endsWith(".html") && !fileName.endsWith(".js"))
                return false;
            if (!fileName.contains("-index") && !fileName.contains("index-"))
                return false;
            var underDocFiles = StreamSupport.stream(Spliterators.spliterator(path.iterator(),
                            Integer.MAX_VALUE, Spliterator.ORDERED), false)
                    .anyMatch(p -> p.equals(DOC_FILES));
            return !underDocFiles;
        });
    }

    private static final Path DOC_FILES = Path.of("doc-files");

    // good enough to capture relevant parts of URLs that javadoc uses,
    // from html and js files alike
    private static final Pattern URL = Pattern.compile(
            "(?<path>([a-zA-Z.%0-9-]+/)*+)(?<file>[a-zA-Z.%0-9-]+\\.html)#[a-zA-Z.%0-9-]+");

    static {
        assert findLinksToDerivedPages("module-summary.html#a").findAny().isEmpty();
        assert findLinksToDerivedPages("package-summary.html#a").findAny().isEmpty();
        assert findLinksToDerivedPages("Exception.html#a").findAny().isEmpty();
        assert findLinksToDerivedPages("util/doc-files/coll-index.html#a").findAny().isEmpty();
        assert findLinksToDerivedPages("util/doc-files/index-all.html#a").findAny().isEmpty(); // tricky


        assert findLinksToDerivedPages("index-all.html#a").findAny().isPresent();
        assert findLinksToDerivedPages("index-17.html#a").findAny().isPresent();
    }

    // NOTE: this will not find self-links that are allowed on some index pages.
    // For example, the quick-jump first-character links, such as #I:A,
    // #I:B, etc., on the top and at the bottom of index-all.html
    private static Stream<String> findLinksToDerivedPages(String content) {
        return URL.matcher(content).results()
                .filter(r -> {
                    String f = r.group("file");
                    if (!f.contains("-"))
                        return false;
                    return switch (f) {
                        case "package-summary.html",
                                "module-summary.html",
                                "overview-summary.html",
                                "help-doc.html" -> false;
                        default -> {
                            String p = r.group("path");
                            yield !p.contains("/doc-files/") && !p.startsWith("doc-files/");
                        }
                    };
                })
                .map(r -> r.group(0));
    }
}
