/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <vector>

#include <folly/Expected.h>
#include <folly/Optional.h>
#include <folly/dynamic.h>
#include <folly/json_pointer.h>

namespace folly {

/*
 * json_patch
 *
 * As described in RFC 6902 "JSON Patch".
 *
 * Implements parsing. Application over data structures must be
 * implemented separately.
 */
class json_patch {
 public:
  enum class parse_error_code : uint8_t {
    undefined,
    invalid_shape,
    missing_op,
    unknown_op,
    malformed_op,
    missing_path_attr,
    malformed_path_attr,
    missing_from_attr,
    malformed_from_attr,
    missing_value_attr,
    overlapping_pointers,
  };

  /*
   * If parsing JSON patch object fails we return err code along with
   * pointer to part of JSON document that we could not parse
   */
  struct parse_error {
    // one of the above error codes
    parse_error_code error_code{parse_error_code::undefined};
    // pointer to object that caused the error
    dynamic const* obj{};
  };

  enum class patch_operation_code : uint8_t {
    invalid = 0,
    test,
    remove,
    add,
    replace,
    move,
    copy,
  };

  /*
   * Single JSON patch operation. Argument may vary based on op type
   */
  struct patch_operation {
    patch_operation_code op_code{patch_operation_code::invalid};
    json_pointer path;
    Optional<json_pointer> from;
    Optional<dynamic> value;
    friend bool operator==(
        patch_operation const& lhs, patch_operation const& rhs) {
      return lhs.op_code == rhs.op_code && lhs.path == rhs.path &&
          lhs.from == rhs.from && lhs.value == rhs.value;
    }
    friend bool operator!=(
        patch_operation const& lhs, patch_operation const& rhs) {
      return !(lhs == rhs);
    }
  };

  json_patch() = default;
  ~json_patch() = default;

  static Expected<json_patch, parse_error> try_parse(
      dynamic const& obj) noexcept;

  std::vector<patch_operation> const& ops() const;

  enum class patch_application_error_code : uint8_t {
    other,
    // "from" pointer did not resolve
    from_not_found,
    // "path" pointer did not resolve
    path_not_found,
    // "test" condition failed
    test_failed,
  };

  struct patch_application_error {
    patch_application_error_code error_code{};
    // index of the patch element (in array) that caused error
    size_t index{};
  };

  /*
   * Mutate supplied object in accordance with patch operations. Leaves
   * object in partially modified state if one of the operations fails.
   */
  Expected<Unit, patch_application_error> apply(dynamic& obj) const;

 private:
  std::vector<patch_operation> ops_;
};

} // namespace folly
