// Copyright 2021 gRPC authors.
//
// 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 GRPC_CORE_LIB_GPRPP_TIME_H
#define GRPC_CORE_LIB_GPRPP_TIME_H

#include <grpc/support/port_platform.h>

#include <stdint.h>

#include <cstdint>
#include <limits>
#include <ostream>
#include <string>

#include <grpc/support/time.h>

#include "src/core/lib/gpr/time_precise.h"
#include "src/core/lib/gpr/useful.h"

namespace grpc_core {

namespace time_detail {

inline int64_t MillisAdd(int64_t a, int64_t b) {
  if (a == std::numeric_limits<int64_t>::max() ||
      b == std::numeric_limits<int64_t>::max()) {
    return std::numeric_limits<int64_t>::max();
  }
  if (a == std::numeric_limits<int64_t>::min() ||
      b == std::numeric_limits<int64_t>::min()) {
    return std::numeric_limits<int64_t>::min();
  }
  return SaturatingAdd(a, b);
}

constexpr inline int64_t MillisMul(int64_t millis, int64_t mul) {
  return millis >= std::numeric_limits<int64_t>::max() / mul
             ? std::numeric_limits<int64_t>::max()
         : millis <= std::numeric_limits<int64_t>::min() / mul
             ? std::numeric_limits<int64_t>::min()
             : millis * mul;
}

}  // namespace time_detail

class Duration;

// Timestamp represents a discrete point in time.
class Timestamp {
 public:
  constexpr Timestamp() = default;
  // Constructs a Timestamp from a gpr_timespec.
  static Timestamp FromTimespecRoundDown(gpr_timespec t);
  static Timestamp FromTimespecRoundUp(gpr_timespec t);

  // Construct a Timestamp from a gpr_cycle_counter.
  static Timestamp FromCycleCounterRoundUp(gpr_cycle_counter c);
  static Timestamp FromCycleCounterRoundDown(gpr_cycle_counter c);

  static constexpr Timestamp FromMillisecondsAfterProcessEpoch(int64_t millis) {
    return Timestamp(millis);
  }

  static constexpr Timestamp ProcessEpoch() { return Timestamp(0); }

  static constexpr Timestamp InfFuture() {
    return Timestamp(std::numeric_limits<int64_t>::max());
  }

  static constexpr Timestamp InfPast() {
    return Timestamp(std::numeric_limits<int64_t>::min());
  }

  constexpr bool operator==(Timestamp other) const {
    return millis_ == other.millis_;
  }
  constexpr bool operator!=(Timestamp other) const {
    return millis_ != other.millis_;
  }
  constexpr bool operator<(Timestamp other) const {
    return millis_ < other.millis_;
  }
  constexpr bool operator<=(Timestamp other) const {
    return millis_ <= other.millis_;
  }
  constexpr bool operator>(Timestamp other) const {
    return millis_ > other.millis_;
  }
  constexpr bool operator>=(Timestamp other) const {
    return millis_ >= other.millis_;
  }
  Timestamp& operator+=(Duration duration);

  bool is_process_epoch() const { return millis_ == 0; }

  uint64_t milliseconds_after_process_epoch() const { return millis_; }

  gpr_timespec as_timespec(gpr_clock_type type) const;

  std::string ToString() const;

 private:
  explicit constexpr Timestamp(int64_t millis) : millis_(millis) {}

  int64_t millis_ = 0;
};

// Duration represents a span of time.
class Duration {
 public:
  constexpr Duration() noexcept : millis_(0) {}

  static Duration FromTimespec(gpr_timespec t);
  static Duration FromSecondsAndNanoseconds(int64_t seconds, int32_t nanos);
  static Duration FromSecondsAsDouble(double seconds);

  static constexpr Duration Zero() { return Duration(0); }

  // Smallest representatable positive duration.
  static constexpr Duration Epsilon() { return Duration(1); }

  static constexpr Duration NegativeInfinity() {
    return Duration(std::numeric_limits<int64_t>::min());
  }

  static constexpr Duration Infinity() {
    return Duration(std::numeric_limits<int64_t>::max());
  }

  static constexpr Duration Hours(int64_t hours) {
    return Minutes(time_detail::MillisMul(hours, 60));
  }

  static constexpr Duration Minutes(int64_t minutes) {
    return Seconds(time_detail::MillisMul(minutes, 60));
  }

  static constexpr Duration Seconds(int64_t seconds) {
    return Milliseconds(time_detail::MillisMul(seconds, GPR_MS_PER_SEC));
  }

  static constexpr Duration Milliseconds(int64_t millis) {
    return Duration(millis);
  }

  static constexpr Duration MicrosecondsRoundDown(int64_t micros) {
    return Duration(micros / GPR_US_PER_MS);
  }

  static constexpr Duration NanosecondsRoundDown(int64_t nanos) {
    return Duration(nanos / GPR_NS_PER_MS);
  }

  static constexpr Duration MicrosecondsRoundUp(int64_t micros) {
    return Duration(micros / GPR_US_PER_MS + (micros % GPR_US_PER_MS != 0));
  }

  static constexpr Duration NanosecondsRoundUp(int64_t nanos) {
    return Duration(nanos / GPR_NS_PER_MS + (nanos % GPR_NS_PER_MS != 0));
  }

  constexpr bool operator==(Duration other) const {
    return millis_ == other.millis_;
  }
  constexpr bool operator!=(Duration other) const {
    return millis_ != other.millis_;
  }
  constexpr bool operator<(Duration other) const {
    return millis_ < other.millis_;
  }
  constexpr bool operator<=(Duration other) const {
    return millis_ <= other.millis_;
  }
  constexpr bool operator>(Duration other) const {
    return millis_ > other.millis_;
  }
  constexpr bool operator>=(Duration other) const {
    return millis_ >= other.millis_;
  }
  Duration& operator/=(int64_t divisor) {
    if (millis_ == std::numeric_limits<int64_t>::max()) {
      *this = divisor < 0 ? NegativeInfinity() : Infinity();
    } else if (millis_ == std::numeric_limits<int64_t>::min()) {
      *this = divisor < 0 ? Infinity() : NegativeInfinity();
    } else {
      millis_ /= divisor;
    }
    return *this;
  }
  Duration& operator+=(Duration other) {
    millis_ += other.millis_;
    return *this;
  }

  constexpr int64_t millis() const { return millis_; }
  double seconds() const { return static_cast<double>(millis_) / 1000.0; }

  gpr_timespec as_timespec() const;

  std::string ToString() const;

 private:
  explicit constexpr Duration(int64_t millis) : millis_(millis) {}

  int64_t millis_;
};

inline Duration operator+(Duration lhs, Duration rhs) {
  return Duration::Milliseconds(
      time_detail::MillisAdd(lhs.millis(), rhs.millis()));
}

inline Duration operator-(Duration lhs, Duration rhs) {
  return Duration::Milliseconds(
      time_detail::MillisAdd(lhs.millis(), -rhs.millis()));
}

inline Timestamp operator+(Timestamp lhs, Duration rhs) {
  return Timestamp::FromMillisecondsAfterProcessEpoch(time_detail::MillisAdd(
      lhs.milliseconds_after_process_epoch(), rhs.millis()));
}

inline Timestamp operator-(Timestamp lhs, Duration rhs) {
  return Timestamp::FromMillisecondsAfterProcessEpoch(time_detail::MillisAdd(
      lhs.milliseconds_after_process_epoch(), -rhs.millis()));
}

inline Timestamp operator+(Duration lhs, Timestamp rhs) { return rhs + lhs; }

inline Duration operator-(Timestamp lhs, Timestamp rhs) {
  return Duration::Milliseconds(
      time_detail::MillisAdd(lhs.milliseconds_after_process_epoch(),
                             -rhs.milliseconds_after_process_epoch()));
}

inline Duration operator*(Duration lhs, double rhs) {
  if (lhs == Duration::Infinity()) {
    return rhs < 0 ? Duration::NegativeInfinity() : Duration::Infinity();
  }
  if (lhs == Duration::NegativeInfinity()) {
    return rhs < 0 ? Duration::Infinity() : Duration::NegativeInfinity();
  }
  return Duration::FromSecondsAsDouble(lhs.millis() * rhs / 1000.0);
}

inline Duration operator*(double lhs, Duration rhs) { return rhs * lhs; }

inline Duration operator/(Duration lhs, int64_t rhs) {
  lhs /= rhs;
  return lhs;
}

inline Duration Duration::FromSecondsAndNanoseconds(int64_t seconds,
                                                    int32_t nanos) {
  return Seconds(seconds) + NanosecondsRoundDown(nanos);
}

inline Duration Duration::FromSecondsAsDouble(double seconds) {
  double millis = seconds * 1000.0;
  if (millis >= static_cast<double>(std::numeric_limits<int64_t>::max())) {
    return Infinity();
  }
  if (millis <= static_cast<double>(std::numeric_limits<int64_t>::min())) {
    return NegativeInfinity();
  }
  return Milliseconds(static_cast<int64_t>(millis));
}

inline Timestamp& Timestamp::operator+=(Duration duration) {
  return *this = (*this + duration);
}

void TestOnlySetProcessEpoch(gpr_timespec epoch);

std::ostream& operator<<(std::ostream& out, Timestamp timestamp);
std::ostream& operator<<(std::ostream& out, Duration duration);

}  // namespace grpc_core

#endif  // GRPC_CORE_LIB_GPRPP_TIME_H
