/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// Function template for the following functions:
//   * RegExpGlobalReplaceOpt
//   * RegExpGlobalReplaceOptFunc
//   * RegExpGlobalReplaceOptSubst
//   * RegExpGlobalReplaceOptElemBase
// Define the following macro and include this file to declare function:
//   * FUNC_NAME     -- function name (required)
//       e.g.
//         #define FUNC_NAME RegExpGlobalReplaceOpt
// Define the following macro (without value) to switch the code:
//   * SUBSTITUTION     -- replaceValue is a string with "$"
//   * FUNCTIONAL       -- replaceValue is a function
//   * ELEMBASE         -- replaceValue is a function that returns an element
//                         of an object
//   * none of above    -- replaceValue is a string without "$"

// ES 2017 draft 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
// steps 8.b-16.
// Optimized path for @@replace with the following conditions:
//   * global flag is true
function FUNC_NAME(rx, S, lengthS, replaceValue, flags
#ifdef SUBSTITUTION
                   , firstDollarIndex
#endif
#ifdef ELEMBASE
                   , elemBase
#endif
                  )
{
    // Step 8.a.
    var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG);

    // Step 8.b.
    var lastIndex = 0;
    rx.lastIndex = 0;

#if defined(FUNCTIONAL) || defined(ELEMBASE)
    // Save the original source and flags, so we can check if the replacer
    // function recompiled the regexp.
    var originalSource = UnsafeGetStringFromReservedSlot(rx, REGEXP_SOURCE_SLOT);
    var originalFlags = flags;
#endif

    // Step 12 (reordered).
    var accumulatedResult = "";

    // Step 13 (reordered).
    var nextSourcePosition = 0;

    // Step 11.
    while (true) {
        // Step 11.a.
        var result = RegExpMatcher(rx, S, lastIndex);

        // Step 11.b.
        if (result === null)
            break;

        // Steps 14.a-b (skipped).
        assert(result.length >= 1, "RegExpMatcher doesn't return an empty array");

        // Step 14.c.
        var matched = result[0];

        // Step 14.d.
        var matchLength = matched.length | 0;

        // Steps 14.e-f.
        var position = result.index | 0;
        lastIndex = position + matchLength;

        // Steps g-l.
        var replacement;
#if defined(FUNCTIONAL)
        replacement = RegExpGetFunctionalReplacement(result, S, position, replaceValue);
#elif defined(SUBSTITUTION)
        // Step l.i
        var namedCaptures = result.groups;
        if (namedCaptures !== undefined) {
            namedCaptures = ToObject(namedCaptures);
        }
        // Step l.ii
        replacement = RegExpGetSubstitution(result, S, position, replaceValue,
                                            firstDollarIndex, namedCaptures);
#elif defined(ELEMBASE)
        if (IsObject(elemBase)) {
            var prop = GetStringDataProperty(elemBase, matched);
            if (prop !== undefined) {
                assert(typeof prop === "string",
                       "GetStringDataProperty should return either string or undefined");
                replacement = prop;
            } else {
                elemBase = undefined;
            }
        }

        if (!IsObject(elemBase))
            replacement = RegExpGetFunctionalReplacement(result, S, position, replaceValue);
#else
        replacement = replaceValue;
#endif

        // Step 14.m.ii.
        accumulatedResult += Substring(S, nextSourcePosition,
                                       position - nextSourcePosition) + replacement;

        // Step 14.m.iii.
        nextSourcePosition = lastIndex;

        // Step 11.c.iii.2.
        if (matchLength === 0) {
            lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1;
            if (lastIndex > lengthS)
                break;
            lastIndex |= 0;
        }

#if defined(FUNCTIONAL) || defined(ELEMBASE)
        // Ensure the current source and flags match the original regexp, the
        // replaceValue function may have called RegExp#compile.
        if (UnsafeGetStringFromReservedSlot(rx, REGEXP_SOURCE_SLOT) !== originalSource ||
            UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT) !== originalFlags)
        {
            rx = regexp_construct_raw_flags(originalSource, originalFlags);
        }
#endif
    }

    // Step 15.
    if (nextSourcePosition >= lengthS)
        return accumulatedResult;

    // Step 16.
    return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
}
