/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */

#include "cmStringReplaceHelper.h"

#include <sstream>
#include <utility>

#include "cmMakefile.h"

cmStringReplaceHelper::cmStringReplaceHelper(const std::string& regex,
                                             std::string replace_expr,
                                             cmMakefile* makefile)
  : RegExString(regex)
  , RegularExpression(regex)
  , ReplaceExpression(std::move(replace_expr))
  , Makefile(makefile)
{
  ParseReplaceExpression();
}

bool cmStringReplaceHelper::Replace(const std::string& input,
                                    std::string& output)
{
  output.clear();

  // Scan through the input for all matches.
  std::string::size_type base = 0;
  while (RegularExpression.find(input.c_str() + base)) {
    if (Makefile != nullptr) {
      Makefile->ClearMatches();
      Makefile->StoreMatches(RegularExpression);
    }
    auto l2 = RegularExpression.start();
    auto r = RegularExpression.end();

    // Concatenate the part of the input that was not matched.
    output += input.substr(base, l2);

    // Make sure the match had some text.
    if (r - l2 == 0) {
      std::ostringstream error;
      error << "regex \"" << RegExString << "\" matched an empty string";
      ErrorString = error.str();
      return false;
    }

    // Concatenate the replacement for the match.
    for (const auto& replacement : Replacements) {
      if (replacement.Number < 0) {
        // This is just a plain-text part of the replacement.
        output += replacement.Value;
      } else {
        // Replace with part of the match.
        auto n = replacement.Number;
        auto start = RegularExpression.start(n);
        auto end = RegularExpression.end(n);
        auto len = input.length() - base;
        if ((start != std::string::npos) && (end != std::string::npos) &&
            (start <= len) && (end <= len)) {
          output += input.substr(base + start, end - start);
        } else {
          std::ostringstream error;
          error << "replace expression \"" << ReplaceExpression
                << "\" contains an out-of-range escape for regex \""
                << RegExString << "\"";
          ErrorString = error.str();
          return false;
        }
      }
    }

    // Move past the match.
    base += r;
  }

  // Concatenate the text after the last match.
  output += input.substr(base, input.length() - base);

  return true;
}

void cmStringReplaceHelper::ParseReplaceExpression()
{
  std::string::size_type l = 0;
  while (l < ReplaceExpression.length()) {
    auto r = ReplaceExpression.find('\\', l);
    if (r == std::string::npos) {
      r = ReplaceExpression.length();
      Replacements.emplace_back(ReplaceExpression.substr(l, r - l));
    } else {
      if (r - l > 0) {
        Replacements.emplace_back(ReplaceExpression.substr(l, r - l));
      }
      if (r == (ReplaceExpression.length() - 1)) {
        ValidReplaceExpression = false;
        ErrorString = "replace-expression ends in a backslash";
        return;
      }
      if ((ReplaceExpression[r + 1] >= '0') &&
          (ReplaceExpression[r + 1] <= '9')) {
        Replacements.emplace_back(ReplaceExpression[r + 1] - '0');
      } else if (ReplaceExpression[r + 1] == 'n') {
        Replacements.emplace_back("\n");
      } else if (ReplaceExpression[r + 1] == '\\') {
        Replacements.emplace_back("\\");
      } else {
        ValidReplaceExpression = false;
        std::ostringstream error;
        error << "Unknown escape \"" << ReplaceExpression.substr(r, 2)
              << "\" in replace-expression";
        ErrorString = error.str();
        return;
      }
      r += 2;
    }
    l = r;
  }
}
