// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "util/internal/field_mask_utility.h"

#include "stubs/strutil.h"
#include "stubs/status_macros.h"

namespace google {
namespace protobuf {
namespace util {
namespace converter {

namespace {
inline util::Status CallPathSink(PathSinkCallback path_sink,
                                   StringPiece arg) {
  return path_sink->Run(arg);
}

// Appends a FieldMask path segment to a prefix.
string AppendPathSegmentToPrefix(StringPiece prefix, StringPiece segment) {
  if (prefix.empty()) {
    return segment.ToString();
  }
  if (segment.empty()) {
    return prefix.ToString();
  }
  // If the segment is a map key, appends it to the prefix without the ".".
  if (segment.starts_with("[\"")) {
    return StrCat(prefix, segment);
  }
  return StrCat(prefix, ".", segment);
}

}  // namespace

string ConvertFieldMaskPath(const StringPiece path,
                            ConverterCallback converter) {
  string result;
  result.reserve(path.size() << 1);

  bool is_quoted = false;
  bool is_escaping = false;
  int current_segment_start = 0;

  // Loops until 1 passed the end of the input to make handling the last
  // segment easier.
  for (size_t i = 0; i <= path.size(); ++i) {
    // Outputs quoted string as-is.
    if (is_quoted) {
      if (i == path.size()) {
        break;
      }
      result.push_back(path[i]);
      if (is_escaping) {
        is_escaping = false;
      } else if (path[i] == '\\') {
        is_escaping = true;
      } else if (path[i] == '\"') {
        current_segment_start = i + 1;
        is_quoted = false;
      }
      continue;
    }
    if (i == path.size() || path[i] == '.' || path[i] == '(' ||
        path[i] == ')' || path[i] == '\"') {
      result += converter(
          path.substr(current_segment_start, i - current_segment_start));
      if (i < path.size()) {
        result.push_back(path[i]);
      }
      current_segment_start = i + 1;
    }
    if (i < path.size() && path[i] == '\"') {
      is_quoted = true;
    }
  }
  return result;
}

util::Status DecodeCompactFieldMaskPaths(StringPiece paths,
                                           PathSinkCallback path_sink) {
  std::stack<string> prefix;
  int length = paths.length();
  int previous_position = 0;
  bool in_map_key = false;
  bool is_escaping = false;
  // Loops until 1 passed the end of the input to make the handle of the last
  // segment easier.
  for (int i = 0; i <= length; ++i) {
    if (i != length) {
      // Skips everything in a map key until we hit the end of it, which is
      // marked by an un-escaped '"' immediately followed by a ']'.
      if (in_map_key) {
        if (is_escaping) {
          is_escaping = false;
          continue;
        }
        if (paths[i] == '\\') {
          is_escaping = true;
          continue;
        }
        if (paths[i] != '\"') {
          continue;
        }
        // Un-escaped '"' must be followed with a ']'.
        if (i >= length - 1 || paths[i + 1] != ']') {
          return util::Status(
              util::error::INVALID_ARGUMENT,
              StrCat("Invalid FieldMask '", paths,
                     "'. Map keys should be represented as [\"some_key\"]."));
        }
        // The end of the map key ("\"]") has been found.
        in_map_key = false;
        // Skips ']'.
        i++;
        // Checks whether the key ends at the end of a path segment.
        if (i < length - 1 && paths[i + 1] != '.' && paths[i + 1] != ',' &&
            paths[i + 1] != ')' && paths[i + 1] != '(') {
          return util::Status(
              util::error::INVALID_ARGUMENT,
              StrCat("Invalid FieldMask '", paths,
                     "'. Map keys should be at the end of a path segment."));
        }
        is_escaping = false;
        continue;
      }

      // We are not in a map key, look for the start of one.
      if (paths[i] == '[') {
        if (i >= length - 1 || paths[i + 1] != '\"') {
          return util::Status(
              util::error::INVALID_ARGUMENT,
              StrCat("Invalid FieldMask '", paths,
                     "'. Map keys should be represented as [\"some_key\"]."));
        }
        // "[\"" starts a map key.
        in_map_key = true;
        i++;  // Skips the '\"'.
        continue;
      }
      // If the current character is not a special character (',', '(' or ')'),
      // continue to the next.
      if (paths[i] != ',' && paths[i] != ')' && paths[i] != '(') {
        continue;
      }
    }
    // Gets the current segment - sub-string between previous position (after
    // '(', ')', ',', or the beginning of the input) and the current position.
    StringPiece segment =
        paths.substr(previous_position, i - previous_position);
    string current_prefix = prefix.empty() ? "" : prefix.top();

    if (i < length && paths[i] == '(') {
      // Builds a prefix and save it into the stack.
      prefix.push(AppendPathSegmentToPrefix(current_prefix, segment));
    } else if (!segment.empty()) {
      // When the current charactor is ')', ',' or the current position has
      // passed the end of the input, builds and outputs a new paths by
      // concatenating the last prefix with the current segment.
      RETURN_IF_ERROR(CallPathSink(
          path_sink, AppendPathSegmentToPrefix(current_prefix, segment)));
    }

    // Removes the last prefix after seeing a ')'.
    if (i < length && paths[i] == ')') {
      if (prefix.empty()) {
        return util::Status(
            util::error::INVALID_ARGUMENT,
            StrCat("Invalid FieldMask '", paths,
                   "'. Cannot find matching '(' for all ')'."));
      }
      prefix.pop();
    }
    previous_position = i + 1;
  }
  if (in_map_key) {
    return util::Status(util::error::INVALID_ARGUMENT,
                          StrCat("Invalid FieldMask '", paths,
                                 "'. Cannot find matching ']' for all '['."));
  }
  if (!prefix.empty()) {
    return util::Status(util::error::INVALID_ARGUMENT,
                          StrCat("Invalid FieldMask '", paths,
                                 "'. Cannot find matching ')' for all '('."));
  }
  return util::Status();
}

}  // namespace converter
}  // namespace util
}  // namespace protobuf
}  // namespace google
