/*
 * 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 <cstdint>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include <folly/CPortability.h>
#include <folly/Portability.h>
#include <folly/detail/base64_detail/Base64Api.h>
#include <folly/detail/base64_detail/Base64Common.h>
#include <folly/lang/Exception.h>
#include <folly/memory/UninitializedMemoryHacks.h>

namespace folly {

//
// base64 encoding/decoding
//
// There are a few variations of base64 encoding.
//
// We have 2: base64 and base64URL.
//
// base64 uses '+' '/' for encoding 62 and 63 and uses '=' padding symbol.
// (padding symbols are required on decoding)
//
// base64URL uses '-' '_' for encoding 62 and 63 and has no padding.
// Decoding with base64URL will accept both base64 and base64URL encoded data +
// padding is always optional.
//
// SIMD implementation is based on 0x80 blog.
// See details explained in folly/detail/base64_detail/README.md
//

//
// High level API.
// Encoding never fails, except for allocation.
// Decoding will throw base64_decode_error if it fails.
//
// NOTE: the expection does not contain detailed information
//       about the error because keeping track of that is overhead.
//       We can potentially improve error reporting by doing a second
//       pass if we decide that it's benefitial.

struct base64_decode_error;

inline auto base64Encode(std::string_view s) -> std::string;
inline auto base64Decode(std::string_view s) -> std::string;
inline auto base64URLEncode(std::string_view s) -> std::string;
inline auto base64URLDecode(std::string_view s) -> std::string;

// Low level API.
//
// This API does not throw and is constexpr enabled.
//
// Encode returns a pointer past the last the byte written
// Decode returns a struct with `is_success` flag and the pointer `o`
// past the last char written.
//
// NOTE: decode will not stop writing when encountering a failure
//       and can always write up to size.
//
// NOTE: since on C++17 we cannot always adequately determine if
//       the function is running in compile time or not,
//       we provide explicit runime versions too.

constexpr std::size_t base64EncodedSize(std::size_t inSize) noexcept;
constexpr std::size_t base64URLEncodedSize(std::size_t inSize) noexcept;

inline constexpr char* base64Encode(
    const char* f, const char* l, char* o) noexcept;
inline constexpr char* base64URLEncode(
    const char* f, const char* l, char* o) noexcept;

inline char* base64EncodeRuntime(
    const char* f, const char* l, char* o) noexcept;
inline char* base64URLEncodeRuntime(
    const char* f, const char* l, char* o) noexcept;

constexpr std::size_t base64DecodedSize(const char* f, const char* l) noexcept;
constexpr std::size_t base64DecodedSize(std::string_view s) noexcept;

constexpr std::size_t base64URLDecodedSize(
    const char* f, const char* l) noexcept;
constexpr std::size_t base64URLDecodedSize(std::string_view s) noexcept;

struct base64_decode_result {
  bool is_success;
  char* o;
};

inline constexpr base64_decode_result base64Decode(
    const char* f, const char* l, char* o) noexcept;
inline constexpr base64_decode_result base64Decode(
    std::string_view s, char* o) noexcept;

inline constexpr base64_decode_result base64URLDecode(
    const char* f, const char* l, char* o) noexcept;
inline constexpr base64_decode_result base64URLDecode(
    std::string_view s, char* o) noexcept;

inline base64_decode_result base64DecodeRuntime(
    const char* f, const char* l, char* o) noexcept;
inline base64_decode_result base64DecodeRuntime(
    std::string_view s, char* o) noexcept;

inline base64_decode_result base64URLDecodeRuntime(
    const char* f, const char* l, char* o) noexcept;
inline base64_decode_result base64URLDecodeRuntime(
    std::string_view s, char* o) noexcept;

// -----------------------------------------------------------------
// implementation

struct base64_decode_error : std::runtime_error {
  using std::runtime_error::runtime_error;
};

constexpr std::size_t base64EncodedSize(std::size_t inSize) noexcept {
  return detail::base64_detail::base64EncodedSize(inSize);
}

constexpr std::size_t base64URLEncodedSize(std::size_t inSize) noexcept {
  return detail::base64_detail::base64URLEncodedSize(inSize);
}

inline constexpr char* base64Encode(
    const char* f, const char* l, char* o) noexcept {
  return detail::base64_detail::base64Encode(f, l, o);
}

inline constexpr char* base64URLEncode(
    const char* f, const char* l, char* o) noexcept {
  return detail::base64_detail::base64URLEncode(f, l, o);
}

inline char* base64EncodeRuntime(
    const char* f, const char* l, char* o) noexcept {
  return detail::base64_detail::base64EncodeRuntime(f, l, o);
}

inline char* base64URLEncodeRuntime(
    const char* f, const char* l, char* o) noexcept {
  return detail::base64_detail::base64URLEncodeRuntime(f, l, o);
}

inline std::string base64Encode(std::string_view s) {
  std::string res;
  std::size_t resSize = folly::base64EncodedSize(s.size());
  folly::resizeWithoutInitialization(res, resSize);
  folly::base64EncodeRuntime(s.data(), s.data() + s.size(), res.data());
  return res;
}

inline std::string base64URLEncode(std::string_view s) {
  std::string res;
  std::size_t resSize = folly::base64URLEncodedSize(s.size());
  folly::resizeWithoutInitialization(res, resSize);
  folly::base64URLEncodeRuntime(s.data(), s.data() + s.size(), res.data());
  return res;
}

constexpr std::size_t base64DecodedSize(const char* f, const char* l) noexcept {
  return detail::base64_detail::base64DecodedSize(f, l);
}

constexpr std::size_t base64DecodedSize(std::string_view s) noexcept {
  return folly::base64DecodedSize(s.data(), s.data() + s.size());
}

constexpr std::size_t base64URLDecodedSize(
    const char* f, const char* l) noexcept {
  return detail::base64_detail::base64URLDecodedSize(f, l);
}

constexpr std::size_t base64URLDecodedSize(std::string_view s) noexcept {
  return folly::base64URLDecodedSize(s.data(), s.data() + s.size());
}

inline constexpr base64_decode_result base64Decode(
    const char* f, const char* l, char* o) noexcept {
  auto detailResult = detail::base64_detail::base64Decode(f, l, o);
  return {detailResult.isSuccess, detailResult.o};
}

inline constexpr base64_decode_result base64Decode(
    std::string_view s, char* o) noexcept {
  return folly::base64Decode(s.data(), s.data() + s.size(), o);
}

inline constexpr base64_decode_result base64URLDecode(
    const char* f, const char* l, char* o) noexcept {
  auto detailResult = detail::base64_detail::base64URLDecode(f, l, o);
  return {detailResult.isSuccess, detailResult.o};
}

inline constexpr base64_decode_result base64URLDecode(
    std::string_view s, char* o) noexcept {
  return folly::base64URLDecode(s.data(), s.data() + s.size(), o);
}

inline base64_decode_result base64DecodeRuntime(
    const char* f, const char* l, char* o) noexcept {
  auto detailResult = detail::base64_detail::base64DecodeRuntime(f, l, o);
  return {detailResult.isSuccess, detailResult.o};
}

inline base64_decode_result base64DecodeRuntime(
    std::string_view s, char* o) noexcept {
  return folly::base64DecodeRuntime(s.data(), s.data() + s.size(), o);
}

inline base64_decode_result base64URLDecodeRuntime(
    const char* f, const char* l, char* o) noexcept {
  auto detailResult = detail::base64_detail::base64URLDecodeRuntime(f, l, o);
  return {detailResult.isSuccess, detailResult.o};
}

inline base64_decode_result base64URLDecodeRuntime(
    std::string_view s, char* o) noexcept {
  return folly::base64URLDecodeRuntime(s.data(), s.data() + s.size(), o);
}

// NOTE: for resizeWithoutInitialization we don't need to declare the macros,
//       since we are using char which is already included by default.
inline std::string base64Decode(std::string_view s) {
  std::string res;
  std::size_t resSize = folly::base64DecodedSize(s);
  folly::resizeWithoutInitialization(res, resSize);

  if (!folly::base64DecodeRuntime(s, res.data()).is_success) {
    folly::throw_exception<base64_decode_error>("Base64 Decoding failed");
  }
  return res;
}

inline std::string base64URLDecode(std::string_view s) {
  std::string res;
  std::size_t resSize = folly::base64URLDecodedSize(s);
  folly::resizeWithoutInitialization(res, resSize);

  if (!folly::base64URLDecodeRuntime(s, res.data()).is_success) {
    folly::throw_exception<base64_decode_error>("Base64URL Decoding failed");
  }
  return res;
}

} // namespace folly
