//
//  ========================================================================
//  Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.start;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;

/**
 * Common PathMatcher implementations.
 */
public class PathMatchers
{
    private static class NonHiddenMatcher implements PathMatcher
    {
        @Override
        public boolean matches(Path path)
        {
            try
            {
                return !Files.isHidden(path);
            }
            catch (IOException e)
            {
                StartLog.debug(e);
                return false;
            }
        }
    }

    private static final char[] GLOB_CHARS = "*?".toCharArray();
    private static final char[] SYNTAXED_GLOB_CHARS = "{}[]|:".toCharArray();
    private static final Path EMPTY_PATH = new File(".").toPath();

    /**
     * Convert a pattern to a Path object.
     *
     * @param pattern the raw pattern (can contain "glob:" or "regex:" syntax indicator)
     * @return the Path version of the pattern provided.
     */
    private static Path asPath(final String pattern)
    {
        String test = pattern;
        if (test.startsWith("glob:"))
        {
            test = test.substring("glob:".length());
        }
        else if (test.startsWith("regex:"))
        {
            test = test.substring("regex:".length());
        }
        return new File(test).toPath();
    }

    public static PathMatcher getMatcher(final String rawpattern)
    {
        FileSystem fs = FileSystems.getDefault();

        String pattern = rawpattern;

        // Strip trailing slash (if present)
        int lastchar = pattern.charAt(pattern.length() - 1);
        if (lastchar == '/' || lastchar == '\\')
        {
            pattern = pattern.substring(0, pattern.length() - 1);
        }

        // If using FileSystem.getPathMatcher() with "glob:" or "regex:"
        // use FileSystem default pattern behavior
        if (pattern.startsWith("glob:") || pattern.startsWith("regex:"))
        {
            StartLog.debug("Using Standard " + fs.getClass().getName() + " pattern: " + pattern);
            return fs.getPathMatcher(pattern);
        }

        // If the pattern starts with a root path then its assumed to
        // be a full system path
        if (isAbsolute(pattern))
        {
            String pat = "glob:" + pattern;
            StartLog.debug("Using absolute path pattern: " + pat);
            return fs.getPathMatcher(pat);
        }

        // Doesn't start with filesystem root, then assume the pattern
        // is a relative file path pattern.
        String pat = "glob:**/" + pattern;
        StartLog.debug("Using relative path pattern: " + pat);
        return fs.getPathMatcher(pat);
    }

    public static PathMatcher getNonHidden()
    {
        return new NonHiddenMatcher();
    }

    /**
     * Provide the non-glob / non-regex prefix on the pattern as a Path reference.
     *
     * @param pattern the pattern to test
     * @return the Path representing the search root for the pattern provided.
     */
    public static Path getSearchRoot(final String pattern)
    {
        StringBuilder root = new StringBuilder();

        int start = 0;
        boolean syntaxed = false;
        if (pattern.startsWith("glob:"))
        {
            start = "glob:".length();
            syntaxed = true;
        }
        else if (pattern.startsWith("regex:"))
        {
            start = "regex:".length();
            syntaxed = true;
        }
        int len = pattern.length();
        int lastSep = 0;
        for (int i = start; i < len; i++)
        {
            int cp = pattern.codePointAt(i);
            if (cp < 127)
            {
                char c = (char)cp;

                // unix path case
                if (c == '/')
                {
                    root.append(c);
                    lastSep = root.length();
                }
                else if (c == '\\')
                {
                    root.append("\\");
                    lastSep = root.length();

                    // possible escaped sequence.
                    // only really interested in windows escape sequences "\\"
                    int count = countChars(pattern, i + 1, '\\');
                    if (count > 0)
                    {
                        // skip extra slashes
                        i += count;
                    }
                }
                else
                {
                    if (isGlob(c, syntaxed))
                    {
                        break;
                    }
                    root.append(c);
                }
            }
            else
            {
                root.appendCodePoint(cp);
            }
        }

        String rootPath = root.substring(0, lastSep);
        if (rootPath.length() <= 0)
        {
            return EMPTY_PATH;
        }

        return asPath(rootPath);
    }

    private static int countChars(String pattern, int offset, char c)
    {
        int count = 0;
        int len = pattern.length();
        for (int i = offset; i < len; i++)
        {
            if (pattern.charAt(i) == c)
            {
                count++;
            }
            else
            {
                break;
            }
        }
        return count;
    }

    /**
     * Tests if provided pattern is an absolute reference (or not)
     *
     * @param pattern the pattern to test
     * @return true if pattern is an absolute reference.
     */
    public static boolean isAbsolute(final String pattern)
    {
        Path searchRoot = getSearchRoot(pattern);
        if (searchRoot == EMPTY_PATH)
        {
            return false;
        }
        return searchRoot.isAbsolute();
    }

    /**
     * Determine if part is a glob pattern.
     *
     * @param c the char to check
     * @param syntaxed true if overall pattern is syntaxed with <code>"glob:"</code> or <code>"regex:"</code>
     * @return true if part has glob characters
     */
    private static boolean isGlob(char c, boolean syntaxed)
    {
        for (char g : GLOB_CHARS)
        {
            if (c == g)
            {
                return true;
            }
        }
        if (syntaxed)
        {
            for (char g : SYNTAXED_GLOB_CHARS)
            {
                if (c == g)
                {
                    return true;
                }
            }
        }
        return false;
    }
}
