/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * 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.
 */

#ifndef SRC_PROTOVM_ERROR_HANDLING_H_
#define SRC_PROTOVM_ERROR_HANDLING_H_

#include <cstdarg>
#include <cstdint>
#include <memory>
#include <new>
#include <string>
#include <type_traits>
#include <vector>

#include "perfetto/base/compiler.h"
#include "perfetto/base/logging.h"

#if defined(__GNUC__) || defined(__clang__)
#if defined(__clang__)
#pragma clang diagnostic push
// Fix 'error: #pragma system_header ignored in main file' for amalgamated
// build.
#pragma clang diagnostic ignored "-Wpragma-system-header-outside-header"
#endif

// Ignore GCC warning about a missing argument for a variadic macro parameter.
#pragma GCC system_header

#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif

namespace perfetto {
namespace protovm {

using Stacktrace = std::vector<std::string>;

void LogStacktraceMessage(Stacktrace& stacktrace,
                          const char* file_name,
                          int file_line,
                          const char* fmt,
                          ...) PERFETTO_PRINTF_FORMAT(4, 5);

// overload for calls without message args
void LogStacktraceMessage(Stacktrace& stacktrace,
                          const char* file_name,
                          int file_line);

#define PROTOVM_ABORT(fmt, ...)                                       \
  do {                                                                \
    auto status_abort = StatusOr<void>::Abort();                      \
    LogStacktraceMessage(status_abort.stacktrace(),                   \
                         ::perfetto::base::LoggingBasename(__FILE__), \
                         __LINE__, fmt, ##__VA_ARGS__);               \
    return status_abort;                                              \
  } while (0)

#define PROTOVM_RETURN(s, ...)                                          \
  do {                                                                  \
    if (s.IsAbort()) {                                                  \
      LogStacktraceMessage(s.stacktrace(),                              \
                           ::perfetto::base::LoggingBasename(__FILE__), \
                           __LINE__, ##__VA_ARGS__);                    \
    }                                                                   \
    return s;                                                           \
  } while (0)

#define PROTOVM_RETURN_IF_NOT_OK(s, ...)                                \
  do {                                                                  \
    if (s.IsAbort()) {                                                  \
      LogStacktraceMessage(s.stacktrace(),                              \
                           ::perfetto::base::LoggingBasename(__FILE__), \
                           __LINE__, ##__VA_ARGS__);                    \
      return s;                                                         \
    }                                                                   \
    if (!s.IsOk()) {                                                    \
      return s;                                                         \
    }                                                                   \
  } while (0)

enum class Status : uint8_t {
  kOk,
  kError,

  // It causes an abort of the whole VM's program. Typically indicates a
  // fundamental issue with the program and requires developer's intervention.
  kAbort,
};

namespace internal {

template <class T, class Arg, class... Args>
static constexpr bool is_copy_or_move_ctor_arg_v =
    sizeof...(Args) == 0 && std::is_same_v<T, std::decay_t<Arg>>;

template <class T>
inline constexpr std::size_t sizeof_v = sizeof(T);

template <>
inline constexpr std::size_t sizeof_v<void> = 0;

template <class T>
inline constexpr std::size_t alignof_v = alignof(T);

template <>
inline constexpr std::size_t alignof_v<void> = 0;

}  // namespace internal

template <class T>
class StatusOr {
 public:
  // Factory methods to create empty instances (without value)
  static StatusOr Ok() { return StatusOr{Status::kOk}; }
  static StatusOr Error() { return StatusOr{Status::kError}; }
  static StatusOr Abort() { return StatusOr{Status::kAbort}; }

  StatusOr(const StatusOr&) = delete;

  StatusOr(StatusOr&& other) : status_{other.status_} {
    if (IsOk()) {
      if constexpr (!std::is_same_v<T, void>) {
        new (storage_) T(std::move(other.value()));
      }
    } else if (IsAbort()) {
      new (storage_) std::unique_ptr<Stacktrace>(
          std::move(other.GetStacktraceUniquePtr()));
    }
  }

  // Intentionally implicit to allow idiomatic usage (return plain value to be
  // implicitly converted to StatusOr)
  template <
      class... Args,
      // sfinae to avoid clashing with copy/move constructors
      std::enable_if_t<!internal::is_copy_or_move_ctor_arg_v<StatusOr, Args...>,
                       int> = 0>
  StatusOr(Args&&... args) : status_{Status::kOk} {
    if constexpr (!std::is_same_v<T, void>) {
      new (storage_) T(std::forward<Args>(args)...);
    }
  }

  // Intentionally implicit to allow idiomatic usage (e.g. return StatusOr<A> to
  // be implicitely converted to StatusOr<B>).
  // When status is ok, the conversion is only allowed if B = void, otherwise it
  // would require a conversion from A to non-void B.
  template <class U, std::enable_if_t<!std::is_same_v<U, T>, int> = 0>
  StatusOr(StatusOr<U>&& other) : status_{other.status_} {
    if (IsOk()) {
      PERFETTO_DCHECK((std::is_same_v<T, void>));
    } else if (IsAbort()) {
      new (storage_) std::unique_ptr<Stacktrace>(
          std::move(other.GetStacktraceUniquePtr()));
    }
  }

  ~StatusOr() {
    if (IsOk()) {
      if constexpr (!std::is_same_v<T, void>) {
        value().~T();
      }
    }
    if (IsAbort()) {
      GetStacktraceUniquePtr().~unique_ptr();
    }
  }

  bool IsOk() const { return status_ == Status::kOk; }
  bool IsError() const { return status_ == Status::kError; }
  bool IsAbort() const { return status_ == Status::kAbort; }

  template <class U = T>
  std::enable_if_t<!std::is_same_v<U, void>, U&> value() {
    PERFETTO_DCHECK(IsOk());
    return *std::launder(reinterpret_cast<T*>(storage_));
  }

  template <class U = T>
  std::enable_if_t<!std::is_same_v<U, void>, const U&> value() const {
    return const_cast<StatusOr*>(this)->value();
  }

  template <class U = T>
  std::enable_if_t<!std::is_same_v<U, void>, U&> operator*() {
    return value();
  }

  template <class U = T>
  std::enable_if_t<!std::is_same_v<U, void>, const U&> operator*() const {
    return value();
  }

  template <class U = T>
  std::enable_if_t<!std::is_same_v<U, void>, U*> operator->() {
    return &value();
  }

  template <class U = T>
  std::enable_if_t<!std::is_same_v<U, void>, const U*> operator->() const {
    return &value();
  }

  Stacktrace& stacktrace() {
    PERFETTO_DCHECK(IsAbort());
    return **std::launder(
        reinterpret_cast<std::unique_ptr<Stacktrace>*>(storage_));
  }

 private:
  template <class U>
  friend class StatusOr;

  // Intentionally private to encourage the use of factory methods, instead of
  // plain status values. E.g. StatusOr::Abort() instead of Status::kAbort.
  // Plain status value don't include stacktrace information in case of an
  // abort.
  explicit StatusOr(Status status) : status_{status} {
    PERFETTO_DCHECK((std::is_same_v<T, void>) || !IsOk());
    if (IsAbort()) {
      new (storage_) std::unique_ptr<Stacktrace>{new Stacktrace{}};
    }
  }

  std::unique_ptr<Stacktrace>& GetStacktraceUniquePtr() {
    PERFETTO_DCHECK(IsAbort());
    return *std::launder(
        reinterpret_cast<std::unique_ptr<Stacktrace>*>(storage_));
  }

  Status status_;

  static constexpr size_t kStorageAlign =
      std::max(internal::alignof_v<T>, alignof(std::unique_ptr<Stacktrace>));
  static constexpr size_t kStorageSize =
      std::max(internal::sizeof_v<T>, sizeof(std::unique_ptr<Stacktrace>));
  alignas(kStorageAlign) unsigned char storage_[kStorageSize];
};

}  // namespace protovm
}  // namespace perfetto

#endif  // SRC_PROTOVM_ERROR_HANDLING_H_
