/*
 * Copyright (c) 2010, 2019, 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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.
 */

package jdk.nashorn.internal.runtime.regexp;

import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import jdk.nashorn.internal.runtime.ParserException;
import jdk.nashorn.internal.runtime.regexp.joni.Matcher;
import jdk.nashorn.internal.runtime.regexp.joni.Option;
import jdk.nashorn.internal.runtime.regexp.joni.Regex;
import jdk.nashorn.internal.runtime.regexp.joni.Region;
import jdk.nashorn.internal.runtime.regexp.joni.Syntax;
import jdk.nashorn.internal.runtime.regexp.joni.exception.JOniException;

/**
 * Regular expression implementation based on the Joni engine from the JRuby project.
 */
public class JoniRegExp extends RegExp {

    /** Compiled Joni Regex */
    private Regex regex;

    /**
     * Construct a Regular expression from the given {@code pattern} and {@code flags} strings.
     *
     * @param pattern RegExp pattern string
     * @param flags RegExp flag string
     * @throws ParserException if flags is invalid or pattern string has syntax error.
     */
    public JoniRegExp(final String pattern, final String flags) throws ParserException {
        super(pattern, flags);

        int option = Option.SINGLELINE;

        if (this.isIgnoreCase()) {
            option |= Option.IGNORECASE;
        }
        if (this.isMultiline()) {
            option &= ~Option.SINGLELINE;
            option |= Option.NEGATE_SINGLELINE;
        }

        try {
            RegExpScanner parsed;

            try {
                parsed = RegExpScanner.scan(pattern);
            } catch (final PatternSyntaxException e) {
                // refine the exception with a better syntax error, if this
                // passes, just rethrow what we have
                Pattern.compile(pattern, 0);
                throw e;
            }

            if (parsed != null) {
                final char[] javaPattern = parsed.getJavaPattern().toCharArray();
                this.regex = new Regex(javaPattern, 0, javaPattern.length, option, Syntax.JAVASCRIPT);
                this.groupsInNegativeLookahead = parsed.getGroupsInNegativeLookahead();
            }
        } catch (final PatternSyntaxException | JOniException e2) {
            throwParserException("syntax", e2.getMessage());
        } catch (StackOverflowError e3) {
            throw new RuntimeException(e3);
        }
    }

    @Override
    public RegExpMatcher match(final String input) {
        if (regex == null) {
            return null;
        }

        return new JoniMatcher(input);
    }

    /**
     * RegExp Factory class for Joni regexp engine.
     */
    public static class Factory extends RegExpFactory {

        @Override
        public RegExp compile(final String pattern, final String flags) throws ParserException {
            return new JoniRegExp(pattern, flags);
        }

    }

    class JoniMatcher implements RegExpMatcher {
        final String input;
        final Matcher joniMatcher;

        JoniMatcher(final String input) {
            this.input = input;
            this.joniMatcher = regex.matcher(input.toCharArray());
        }

        @Override
        public boolean search(final int start) {
            return joniMatcher.search(start, input.length(), Option.NONE) > -1;
        }

        @Override
        public String getInput() {
            return input;
        }

        @Override
        public int start() {
            return joniMatcher.getBegin();
        }

        @Override
        public int start(final int group) {
            return group == 0 ? start() : joniMatcher.getRegion().beg[group];
        }

        @Override
        public int end() {
            return joniMatcher.getEnd();
        }

        @Override
        public int end(final int group) {
            return group == 0 ? end() : joniMatcher.getRegion().end[group];
        }

        @Override
        public String group() {
            return input.substring(joniMatcher.getBegin(), joniMatcher.getEnd());
        }

        @Override
        public String group(final int group) {
            if (group == 0) {
                return group();
            }
            final Region region = joniMatcher.getRegion();
            return input.substring(region.beg[group], region.end[group]);
        }

        @Override
        public int groupCount() {
            final Region region = joniMatcher.getRegion();
            return region == null ? 0 : region.numRegs - 1;
        }
    }
}
