/*
 * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2025, Red Hat, Inc. and/or its affiliates.
 * 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 jdk.test.lib.Platform;
import jdk.test.lib.StringArrayUtils;
import jdk.test.whitebox.WhiteBox;

public class SystemMapTestBase {

    // e.g.
    // 0x00000007ff800000-0x00000007ff91a000      1155072 rw-p      1155072            0 4K   com              JAVAHEAP                              /shared/projects/openjdk/jdk-jdk/output-fastdebug/images/jdk/lib/server/classes.jsa
    private static final String range = "0x\\p{XDigit}+-0x\\p{XDigit}+";
    private static final String space = " +";
    private static final String someSize = "\\d+";
    private static final String someNumber = "(0x\\p{XDigit}+|\\d+)";
    private static final String pagesize = "(4K|8K|16K|64K|2M|16M|64M)";

    interface MapPatterns {
        String[] shouldMatchUnconditionally();
        String[] shouldMatchIfNMTIsEnabled();
    };

    private final MapPatterns patternProvider;

    private static final boolean isWindows = Platform.isWindows();
    private static final boolean isMacOS = Platform.isOSX();

    protected String[] shouldMatchUnconditionally() {
        return patternProvider.shouldMatchUnconditionally();
    }

    protected String[] shouldMatchIfNMTIsEnabled() {
        return patternProvider.shouldMatchIfNMTIsEnabled();
    }

    protected SystemMapTestBase() {
        if (Platform.isWindows()) {
            patternProvider = new WindowsPatterns();
        } else if (Platform.isOSX()) {
            patternProvider = new MacOSPatterns();
        } else {
            patternProvider = new LinuxPatterns();
        }
    }

    private static class LinuxPatterns implements MapPatterns {

        private static final String prot = "[rwsxp-]+";

        static final String regexBase = range + space +
                                        someSize + space +
                                        prot + space +
                                        someSize + space +
                                        someSize + space +
                                        pagesize + space;

        static final String regexBase_committed = regexBase + "com.*";
        static final String regexBase_shared_and_committed = regexBase + "shrd,com.*";

        // java heap is either committed, non-shared, or - in case of ZGC - committed and shared.
        static final String regexBase_java_heap = regexBase + "(shrd,)?com.*";

        static final String shouldMatchUnconditionally_linux[] = {
            // java launcher
            regexBase_committed + "/bin/java",
            // heap segment, should be part of all user space apps on all architectures OpenJDK supports.
            regexBase_committed + "\\[heap\\]",
            // we should see the hs-perf data file, and it should appear as shared as well as committed
            regexBase_shared_and_committed + "hsperfdata_.*"
        };

        static final String shouldMatch_linux_libjvm[] = {
            // libjvm
            regexBase_committed + "/lib/.*/libjvm.so"
        };

        static final String shouldMatchIfNMTIsEnabled_linux[] = {
            regexBase_java_heap + "JAVAHEAP.*",
            // metaspace
            regexBase_committed + "META.*",
            // parts of metaspace should be uncommitted
            regexBase + "-" + space + "META.*",
            // code cache
            regexBase_committed + "CODE.*",
            // Main thread stack
            regexBase_committed + "STACK.*main.*"
        };

        public String[] shouldMatchUnconditionally() {
            if (WhiteBox.getWhiteBox().isStatic()) {
                // On static JDK, libjvm is statically linked with the 'java'
                // launcher. There is no separate mapping for libjvm.
                return shouldMatchUnconditionally_linux;
            } else {
                return StringArrayUtils.concat(shouldMatchUnconditionally_linux,
                                               shouldMatch_linux_libjvm);
            }
        }

        public String[] shouldMatchIfNMTIsEnabled() {
            return shouldMatchIfNMTIsEnabled_linux;
        }
    };

    private static class WindowsPatterns implements MapPatterns {

        static final String winprot = "[\\-rwxcin]*";
        static final String wintype = "[rc]-(img|map|pvt)";

        static final String winbase = range + space + someSize + space + winprot + space;

        static final String winimage     = winbase + "c-img" + space + someNumber + space;
        static final String wincommitted = winbase + "(c-pvt|c-map)" + space + someNumber + space;
        static final String winreserved  = winbase + "r-pvt" + space + someNumber + space;

        static final String shouldMatchUnconditionally_windows[] = {
            // java launcher
            winimage + ".*[\\/\\\\]bin[\\/\\\\]java[.]exe",
        };

        static final String shouldMatch_windows_libjvm[] = {
            // libjvm
            winimage + ".*[\\/\\\\]bin[\\/\\\\].*[\\/\\\\]jvm.dll"
        };

        static final String shouldMatchIfNMTIsEnabled_windows[] = {
            wincommitted + "JAVAHEAP.*",
            // metaspace
            wincommitted + "META.*",
            // parts of metaspace should be uncommitted
            winreserved + "META.*",
            // code cache
            wincommitted + "CODE.*",
            // Main thread stack
            wincommitted + "STACK-\\d+-main.*"
        };

        public String[] shouldMatchUnconditionally() {
            if (WhiteBox.getWhiteBox().isStatic()) {
                // On static JDK, libjvm is statically linked with the 'java'
                // launcher. There is no separate mapping for libjvm.
                return shouldMatchUnconditionally_windows;
            } else {
                return StringArrayUtils.concat(shouldMatchUnconditionally_windows,
                                               shouldMatch_windows_libjvm);
            }
        }

        public String[] shouldMatchIfNMTIsEnabled() {
            return shouldMatchIfNMTIsEnabled_windows;
        }
    };

    private static class MacOSPatterns implements MapPatterns {

        // macOS:
        static final String macprot =  "[\\-rwx]*/[\\-rwx]*";

        static final String macow = "cow";
        static final String macprivate = "pvt";
        static final String macprivate_or_shared = "(pvt|tsh|cow|p/a)";
        static final String macprivatealiased = "p/a";

        static final String macOSbase = range + space + someSize + space + macprot + space;

        static final String shouldMatchUnconditionally_macOS[] = {
            // java launcher
            macOSbase + macow + space + someNumber + space + "/.*/bin/java",
            // we should see the hs-perf data file, and it should appear as shared as well as committed
            macOSbase + macprivate + space + someNumber + space + ".*/.*/hsperfdata_.*"
        };

        static final String shouldMatch_macOS_libjvm[] = {
            // libjvm
            macOSbase + macow + space + someNumber + space + "/.*/lib/server/libjvm.dylib",
        };

        static final String shouldMatchIfNMTIsEnabled_macOS[] = {
            // heap is private with G1GC, shared with ZGC
            macOSbase + macprivate_or_shared + space + someNumber + space + "JAVAHEAP.*",
            // metaspace
            macOSbase + macprivate + space + someNumber + space + "META.*",
            // code cache
            macOSbase + macprivate + space + someNumber + space + "CODE.*",
            // Main thread stack
            macOSbase + macprivatealiased + space + someNumber + space + "STACK-.*-main.*"
        };

        public String[] shouldMatchUnconditionally() {
            if (WhiteBox.getWhiteBox().isStatic()) {
                // On static JDK, libjvm is statically linked with the 'java'
                // launcher. There is no separate mapping for libjvm.
                return shouldMatchUnconditionally_macOS;
            } else {
                return StringArrayUtils.concat(shouldMatchUnconditionally_macOS,
                                               shouldMatch_macOS_libjvm);
            }
        }

        public String[] shouldMatchIfNMTIsEnabled() {
            return shouldMatchIfNMTIsEnabled_macOS;
        }
    };
}
