/*
 * Copyright (C) 2016 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.
 */
#pragma once

#include <stdint.h>
#include <time.h>

namespace cvd {
namespace time {

static const int64_t kNanosecondsPerSecond = 1000000000;

class TimeDifference {
 public:
  TimeDifference(time_t seconds, long nanoseconds, int64_t scale) :
      scale_(scale), truncated_(false) {
    ts_.tv_sec = seconds;
    ts_.tv_nsec = nanoseconds;
    if (scale_ == kNanosecondsPerSecond) {
      truncated_ = true;
      truncated_ns_ = 0;
    }
  }

  TimeDifference(const TimeDifference& in, int64_t scale) :
      scale_(scale), truncated_(false) {
    ts_ = in.GetTS();
    if (scale_ == kNanosecondsPerSecond) {
      truncated_ = true;
      truncated_ns_ = 0;
    } else if ((in.scale_ % scale_) == 0) {
      truncated_ = true;
      truncated_ns_ = ts_.tv_nsec;
    }
  }

  TimeDifference(const struct timespec& in, int64_t scale) :
      ts_(in), scale_(scale), truncated_(false) { }

  TimeDifference operator*(const uint32_t factor) {
    TimeDifference rval = *this;
    rval.ts_.tv_sec = ts_.tv_sec * factor;
    // Create temporary variable to hold the multiplied
    // nanoseconds so that no overflow is possible.
    // Nanoseconds must be in [0, 10^9) and so all are less
    // then 2^30. Even multiplied by the largest uint32
    // this will fit in a 64-bit int without overflow.
    int64_t tv_nsec = static_cast<int64_t>(ts_.tv_nsec) * factor;
    rval.ts_.tv_sec += (tv_nsec / kNanosecondsPerSecond);
    rval.ts_.tv_nsec = tv_nsec % kNanosecondsPerSecond;
    return rval;
  }

  TimeDifference operator+(const TimeDifference& other) const {
    struct timespec ret = ts_;
    ret.tv_nsec = (ts_.tv_nsec + other.ts_.tv_nsec) % 1000000000;
    ret.tv_sec = (ts_.tv_sec + other.ts_.tv_sec) +
                  (ts_.tv_nsec + other.ts_.tv_nsec) / 1000000000;
    return TimeDifference(ret, scale_ < other.scale_ ? scale_: other.scale_);
  }

  TimeDifference operator-(const TimeDifference& other) const {
    struct timespec ret = ts_;
    // Keeps nanoseconds positive and allow negative numbers only on
    // seconds.
    ret.tv_nsec = (1000000000 + ts_.tv_nsec - other.ts_.tv_nsec) % 1000000000;
    ret.tv_sec = (ts_.tv_sec - other.ts_.tv_sec) -
                  (ts_.tv_nsec < other.ts_.tv_nsec ? 1 : 0);
    return TimeDifference(ret, scale_ < other.scale_ ? scale_: other.scale_);
  }

  bool operator<(const TimeDifference& other) const {
    return ts_.tv_sec < other.ts_.tv_sec ||
           (ts_.tv_sec == other.ts_.tv_sec && ts_.tv_nsec < other.ts_.tv_nsec);
  }

  int64_t count() const {
    return ts_.tv_sec * (kNanosecondsPerSecond / scale_) + ts_.tv_nsec / scale_;
  }

  time_t seconds() const {
    return ts_.tv_sec;
  }

  long subseconds_in_ns() const {
    if (!truncated_) {
      truncated_ns_ = (ts_.tv_nsec / scale_) * scale_;
      truncated_ = true;
    }
    return truncated_ns_;
  }

  struct timespec GetTS() const {
    // We can't assume C++11, so avoid extended initializer lists.
    struct timespec rval = { ts_.tv_sec, subseconds_in_ns()};
    return rval;
  }

 protected:
  struct timespec ts_;
  int64_t scale_;
  mutable bool truncated_;
  mutable long truncated_ns_;
};

class MonotonicTimePoint {
 public:
  static MonotonicTimePoint Now() {
    struct timespec ts;
#ifdef CLOCK_MONOTONIC_RAW
    // WARNING:
    // While we do have CLOCK_MONOTONIC_RAW, we can't depend on it until:
    // - ALL places relying on MonotonicTimePoint are fixed,
    // - pthread supports pthread_timewait_monotonic.
    //
    // This is currently observable as a LEGITIMATE problem while running
    // pthread_test. DO NOT revert this to CLOCK_MONOTONIC_RAW until test
    // passes.
    clock_gettime(CLOCK_MONOTONIC, &ts);
#else
    clock_gettime(CLOCK_MONOTONIC, &ts);
#endif
    return MonotonicTimePoint(ts);
  }

  MonotonicTimePoint() {
    ts_.tv_sec = 0;
    ts_.tv_nsec = 0;
  }

  explicit MonotonicTimePoint(const struct timespec& ts) {
    ts_ = ts;
  }

  TimeDifference SinceEpoch() const {
    return TimeDifference(ts_, 1);
  }

  TimeDifference operator-(const MonotonicTimePoint& other) const {
    struct timespec rval;
    rval.tv_sec = ts_.tv_sec - other.ts_.tv_sec;
    rval.tv_nsec = ts_.tv_nsec - other.ts_.tv_nsec;
    if (rval.tv_nsec < 0) {
      --rval.tv_sec;
      rval.tv_nsec += kNanosecondsPerSecond;
    }
    return TimeDifference(rval, 1);
  }

  MonotonicTimePoint operator+(const TimeDifference& other) const {
    MonotonicTimePoint rval = *this;
    rval.ts_.tv_sec += other.seconds();
    rval.ts_.tv_nsec += other.subseconds_in_ns();
    if (rval.ts_.tv_nsec >= kNanosecondsPerSecond) {
      ++rval.ts_.tv_sec;
      rval.ts_.tv_nsec -= kNanosecondsPerSecond;
    }
    return rval;
  }

  bool operator==(const MonotonicTimePoint& other) const {
    return (ts_.tv_sec == other.ts_.tv_sec) &&
        (ts_.tv_nsec == other.ts_.tv_nsec);
  }

  bool operator!=(const MonotonicTimePoint& other) const {
    return !(*this == other);
  }

  bool operator<(const MonotonicTimePoint& other) const {
    return ((ts_.tv_sec - other.ts_.tv_sec) < 0) ||
        ((ts_.tv_sec == other.ts_.tv_sec) &&
         (ts_.tv_nsec < other.ts_.tv_nsec));
  }

  bool operator>(const MonotonicTimePoint& other) const {
    return other < *this;
  }

  bool operator<=(const MonotonicTimePoint& other) const {
    return !(*this > other);
  }

  bool operator>=(const MonotonicTimePoint& other) const {
    return !(*this < other);
  }

  MonotonicTimePoint& operator+=(const TimeDifference& other) {
    ts_.tv_sec += other.seconds();
    ts_.tv_nsec += other.subseconds_in_ns();
    if (ts_.tv_nsec >= kNanosecondsPerSecond) {
      ++ts_.tv_sec;
      ts_.tv_nsec -= kNanosecondsPerSecond;
    }
    return *this;
  }

  MonotonicTimePoint& operator-=(const TimeDifference& other) {
    ts_.tv_sec -= other.seconds();
    ts_.tv_nsec -= other.subseconds_in_ns();
    if (ts_.tv_nsec < 0) {
      --ts_.tv_sec;
      ts_.tv_nsec += kNanosecondsPerSecond;
    }
    return *this;
  }

  void ToTimespec(struct timespec* dest) const {
    *dest = ts_;
  }

 protected:
  struct timespec ts_;
};

class MonotonicTimePointFactory {
 public:
  static MonotonicTimePointFactory* GetInstance();

  virtual ~MonotonicTimePointFactory() { }

  virtual void FetchCurrentTime(MonotonicTimePoint* dest) const {
    *dest = MonotonicTimePoint::Now();
  }
};

class Seconds : public TimeDifference {
 public:
  explicit Seconds(const TimeDifference& difference) :
      TimeDifference(difference, kNanosecondsPerSecond) { }

  Seconds(int64_t seconds) :
      TimeDifference(seconds, 0, kNanosecondsPerSecond) { }
};

class Milliseconds : public TimeDifference {
 public:
  explicit Milliseconds(const TimeDifference& difference) :
      TimeDifference(difference, kScale) { }

  Milliseconds(int64_t ms) : TimeDifference(
      ms / 1000, (ms % 1000) * kScale, kScale) { }

 protected:
  static const int kScale = kNanosecondsPerSecond / 1000;
};

class Microseconds : public TimeDifference {
 public:
  explicit Microseconds(const TimeDifference& difference) :
      TimeDifference(difference, kScale) { }

  Microseconds(int64_t micros) : TimeDifference(
      micros / 1000000, (micros % 1000000) * kScale, kScale) { }

 protected:
  static const int kScale = kNanosecondsPerSecond / 1000000;
};

class Nanoseconds : public TimeDifference {
 public:
  explicit Nanoseconds(const TimeDifference& difference) :
      TimeDifference(difference, 1) { }
  Nanoseconds(int64_t ns) : TimeDifference(ns / kNanosecondsPerSecond,
                                           ns % kNanosecondsPerSecond, 1) { }
};

}  // namespace time
}  // namespace cvd

/**
 * Legacy support for microseconds. Use MonotonicTimePoint in new code.
 */
static const int64_t kSecsToUsecs = static_cast<int64_t>(1000) * 1000;

static inline int64_t get_monotonic_usecs() {
  return cvd::time::Microseconds(
      cvd::time::MonotonicTimePoint::Now().SinceEpoch()).count();
}