/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef TIME_UNITS_H
#define TIME_UNITS_H

#include <limits>
#include <type_traits>

#include "Intervals.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "nsPrintfCString.h"

namespace mozilla::media {
class TimeIntervals;
}  // namespace mozilla::media
// CopyChooser specialization for nsTArray
template <>
struct nsTArray_RelocationStrategy<mozilla::media::TimeIntervals> {
  using Type =
      nsTArray_RelocateUsingMoveConstructor<mozilla::media::TimeIntervals>;
};

namespace mozilla {

// Number of milliseconds per second. 1e3.
static const int64_t MSECS_PER_S = 1000;

// Number of microseconds per second. 1e6.
static const int64_t USECS_PER_S = 1000000;

// Number of nanoseconds per second. 1e9.
static const int64_t NSECS_PER_S = 1000000000;

namespace media {

#ifndef PROCESS_DECODE_LOG
#  define PROCESS_DECODE_LOG(sample)                                   \
    MOZ_LOG(sPDMLog, mozilla::LogLevel::Verbose,                       \
            ("ProcessDecode: mDuration=%" PRIu64 "µs ; mTime=%" PRIu64 \
             "µs ; mTimecode=%" PRIu64 "µs",                           \
             (sample)->mDuration.ToMicroseconds(),                     \
             (sample)->mTime.ToMicroseconds(),                         \
             (sample)->mTimecode.ToMicroseconds()))
#endif  // PROCESS_DECODE_LOG

// TimeUnit is a class that represents a time value, that can be negative or
// positive.
//
// Internally, it works by storing a numerator (the tick numbers), that uses
// checked arithmetics, and a denominator (the base), that is a regular integer
// on which arithmetics is never performed, and is only set at construction, or
// replaced.
//
// Dividing the tick count by the base always yields a value in seconds,
// but it's very useful to have a base that is dependant on the context: it can
// be the sample-rate of an audio stream, the time base of an mp4, that's often
// 90000 because it's divisible by 24, 25 and 30.
//
// Keeping time like this allows performing calculations on time values with
// maximum precision, without having to have to care about rounding errors or
// precision loss.
//
// If not specified, the base is 1e6, representing microseconds, for historical
// reasons. Users can gradually move to more precise representations when
// needed.
//
// INT64_MAX has the special meaning of being +∞, and INT64_MIN means -∞. Any
// other value corresponds to a valid time.
//
// If an overflow or other problem occurs, the underlying CheckedInt<int64_t> is
// invalid and a crash is triggered.
class TimeUnit final {
 public:
  constexpr TimeUnit(CheckedInt64 aTicks, int64_t aBase)
      : mTicks(aTicks), mBase(aBase) {
    MOZ_RELEASE_ASSERT(mBase > 0);
  }

  explicit constexpr TimeUnit(CheckedInt64 aTicks)
      : mTicks(aTicks), mBase(USECS_PER_S) {}

  // Return the maximum number of ticks that a TimeUnit can contain.
  static constexpr int64_t MaxTicks() {
    return std::numeric_limits<int64_t>::max() - 1;
  }

  // This is only precise up to a point, which is aValue * aBase <= 2^53 - 1
  static TimeUnit FromSeconds(double aValue, int64_t aBase = USECS_PER_S);
  static constexpr TimeUnit FromMicroseconds(int64_t aValue) {
    return TimeUnit(aValue, USECS_PER_S);
  }
  static TimeUnit FromHns(int64_t aValue, int64_t aBase) {
    // Truncating here would mean a loss of precision.
    return TimeUnit::FromNanoseconds(aValue * 100).ToBase<RoundPolicy>(aBase);
  }
  static constexpr TimeUnit FromNanoseconds(int64_t aValue) {
    return TimeUnit(aValue, NSECS_PER_S);
  }
  static TimeUnit FromInfinity();
  static TimeUnit FromNegativeInfinity();
  static TimeUnit FromTimeDuration(const TimeDuration& aDuration);
  static constexpr TimeUnit Zero(int64_t aBase = USECS_PER_S) {
    return TimeUnit(0, aBase);
  }
  static constexpr TimeUnit Zero(const TimeUnit& aOther) {
    return TimeUnit(0, aOther.mBase);
  }
  static TimeUnit Invalid();
  int64_t ToMilliseconds() const;
  int64_t ToMicroseconds() const;
  int64_t ToNanoseconds() const;
  int64_t ToTicksAtRate(int64_t aRate) const;
  double ToSeconds() const;
  nsCString ToString() const;
  TimeDuration ToTimeDuration() const;
  bool IsInfinite() const;
  bool IsPositive() const;
  bool IsPositiveOrZero() const;
  bool IsZero() const;
  bool IsNegative() const;

  // Returns true if the fractions are equal when converted to the smallest
  // base.
  bool EqualsAtLowestResolution(const TimeUnit& aOther) const;
  // Strict equality -- the fractions must be exactly equal
  bool operator==(const TimeUnit& aOther) const;
  bool operator!=(const TimeUnit& aOther) const;
  bool operator>=(const TimeUnit& aOther) const;
  bool operator>(const TimeUnit& aOther) const;
  bool operator<=(const TimeUnit& aOther) const;
  bool operator<(const TimeUnit& aOther) const;
  TimeUnit operator%(const TimeUnit& aOther) const;
  TimeUnit operator+(const TimeUnit& aOther) const;
  TimeUnit operator-(const TimeUnit& aOther) const;
  TimeUnit& operator+=(const TimeUnit& aOther);
  TimeUnit& operator-=(const TimeUnit& aOther);
  template <typename T>
  TimeUnit operator*(T aVal) const {
    // See bug 853398 for the reason to block double multiplier.
    // If required, use MultDouble below and with caution.
    static_assert(std::is_integral_v<T>, "Must be an integral type");
    return TimeUnit(mTicks * aVal, mBase);
  }
  TimeUnit MultDouble(double aVal) const;
  friend TimeUnit operator/(const TimeUnit& aUnit, int64_t aVal) {
    MOZ_DIAGNOSTIC_ASSERT(0 <= aVal && aVal <= UINT32_MAX);
    return TimeUnit(aUnit.mTicks / aVal, aUnit.mBase);
  }
  friend TimeUnit operator%(const TimeUnit& aUnit, int64_t aVal) {
    MOZ_DIAGNOSTIC_ASSERT(0 <= aVal && aVal <= UINT32_MAX);
    return TimeUnit(aUnit.mTicks % aVal, aUnit.mBase);
  }

  struct TruncatePolicy {
    template <typename T>
    static T policy(T& aValue) {
      return static_cast<T>(aValue);
    }
  };

  struct RoundPolicy {
    template <typename T>
    static T policy(T& aValue) {
      return std::round(aValue);
    }
  };

  template <class RoundingPolicy = TruncatePolicy>
  TimeUnit ToBase(int64_t aTargetBase) const {
    double dummy = 0.0;
    return ToBase<RoundingPolicy>(aTargetBase, dummy);
  }

  template <class RoundingPolicy = TruncatePolicy>
  TimeUnit ToBase(const TimeUnit& aTimeUnit) const {
    double dummy = 0.0;
    return ToBase<RoundingPolicy>(aTimeUnit, dummy);
  }

  // Allow returning the same value, in a base that matches another TimeUnit.
  template <class RoundingPolicy = TruncatePolicy>
  TimeUnit ToBase(const TimeUnit& aTimeUnit, double& aOutError) const {
    int64_t targetBase = aTimeUnit.mBase;
    return ToBase<RoundingPolicy>(targetBase, aOutError);
  }

  template <class RoundingPolicy = TruncatePolicy>
  TimeUnit ToBase(int64_t aTargetBase, double& aOutError) const {
    aOutError = 0.0;
    CheckedInt<int64_t> ticks = mTicks * aTargetBase;
    if (ticks.isValid()) {
      imaxdiv_t rv = imaxdiv(ticks.value(), mBase);
      if (!rv.rem) {
        return TimeUnit(rv.quot, aTargetBase);
      }
    }
    double approx = static_cast<double>(mTicks.value()) *
                    static_cast<double>(aTargetBase) /
                    static_cast<double>(mBase);
    double integer;
    aOutError = modf(approx, &integer);
    return TimeUnit(AssertedCast<int64_t>(RoundingPolicy::policy(approx)),
                    aTargetBase);
  }

  bool IsValid() const;

  constexpr TimeUnit() = default;

  TimeUnit(const TimeUnit&) = default;

  TimeUnit& operator=(const TimeUnit&) = default;

  bool IsPosInf() const;
  bool IsNegInf() const;

  // Allow serializing a TimeUnit via IPC
  friend IPC::ParamTraits<mozilla::media::TimeUnit>;

#ifndef VISIBLE_TIMEUNIT_INTERNALS
 private:
#endif
  int64_t ToCommonUnit(int64_t aRatio) const;
  // Reduce a TimeUnit to the smallest possible ticks and base. This is useful
  // to comparison with big time values that can otherwise overflow.
  TimeUnit Reduced() const;

  CheckedInt64 mTicks{0};
  // Default base is microseconds.
  int64_t mBase{USECS_PER_S};
};

using NullableTimeUnit = Maybe<TimeUnit>;

using TimeInterval = Interval<TimeUnit>;

// A set of intervals, containing TimeUnit.
class TimeIntervals : public IntervalSet<TimeUnit> {
 public:
  using BaseType = IntervalSet<TimeUnit>;
  using InnerType = TimeUnit;

  // We can't use inherited constructors yet. So we have to duplicate all the
  // constructors found in IntervalSet base class.
  // all this could be later replaced with:
  // using IntervalSet<TimeUnit>::IntervalSet;

  // MOZ_IMPLICIT as we want to enable initialization in the form:
  // TimeIntervals i = ... like we would do with IntervalSet<T> i = ...
  MOZ_IMPLICIT TimeIntervals(const BaseType& aOther) : BaseType(aOther) {}
  MOZ_IMPLICIT TimeIntervals(BaseType&& aOther) : BaseType(std::move(aOther)) {}
  explicit TimeIntervals(const BaseType::ElemType& aOther) : BaseType(aOther) {}
  explicit TimeIntervals(BaseType::ElemType&& aOther)
      : BaseType(std::move(aOther)) {}

  static TimeIntervals Invalid() {
    return TimeIntervals(TimeInterval(TimeUnit::FromNegativeInfinity(),
                                      TimeUnit::FromNegativeInfinity()));
  }
  bool IsInvalid() const {
    return Length() == 1 && Start(0).IsNegInf() && End(0).IsNegInf();
  }

  // Returns the same interval, with a microsecond resolution. This is used to
  // compare TimeUnits internal to demuxers (that use a base from the container)
  // to floating point numbers in seconds from content.
  TimeIntervals ToMicrosecondResolution() const {
    TimeIntervals output;

    for (const auto& interval : mIntervals) {
      TimeInterval reducedPrecision{interval.mStart.ToBase(USECS_PER_S),
                                    interval.mEnd.ToBase(USECS_PER_S),
                                    interval.mFuzz.ToBase(USECS_PER_S)};
      output += reducedPrecision;
    }
    return output;
  }

  nsCString ToString() const {
    nsCString dump;
    for (const auto& interval : mIntervals) {
      dump += nsPrintfCString("[%s],", interval.ToString().get());
    }
    return dump;
  }

  TimeIntervals() = default;
};

using TimeRange = Interval<double>;

// A set of intervals, containing doubles that are seconds.
class TimeRanges : public IntervalSet<double> {
 public:
  using BaseType = IntervalSet<double>;
  using InnerType = double;
  using nld = std::numeric_limits<double>;

  // We can't use inherited constructors yet. So we have to duplicate all the
  // constructors found in IntervalSet base class.
  // all this could be later replaced with:
  // using IntervalSet<TimeUnit>::IntervalSet;

  // MOZ_IMPLICIT as we want to enable initialization in the form:
  // TimeIntervals i = ... like we would do with IntervalSet<T> i = ...
  MOZ_IMPLICIT TimeRanges(const BaseType& aOther) : BaseType(aOther) {}
  MOZ_IMPLICIT TimeRanges(BaseType&& aOther) : BaseType(std::move(aOther)) {}
  explicit TimeRanges(const BaseType::ElemType& aOther) : BaseType(aOther) {}
  explicit TimeRanges(BaseType::ElemType&& aOther)
      : BaseType(std::move(aOther)) {}

  static TimeRanges Invalid() {
    return TimeRanges(TimeRange(-nld::infinity(), nld::infinity()));
  }
  bool IsInvalid() const {
    return Length() == 1 && Start(0) == -nld::infinity() &&
           End(0) == nld::infinity();
  }
  // Convert from TimeUnit-based intervals to second-based TimeRanges.
  explicit TimeRanges(const TimeIntervals& aIntervals) {
    for (const auto& interval : aIntervals) {
      Add(TimeRange(interval.mStart.ToSeconds(), interval.mEnd.ToSeconds()));
    }
  }

  TimeRanges ToMicrosecondResolution() const;

  TimeRanges() = default;
};

}  // namespace media
}  // namespace mozilla

#endif  // TIME_UNITS_H
