/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License Version 2.0 with LLVM Exceptions
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *   https://llvm.org/LICENSE.txt
 *
 * 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 <chrono>
#include <cstdint>

#include <windows.h>

namespace experimental::execution::__win32
{

  class filetime_clock
  {
   public:
    using rep      = std::int64_t;
    using ratio    = std::ratio<1, 10'000'000>;  // 100ns
    using duration = std::chrono::duration<rep, ratio>;

    static constexpr bool is_steady = false;

    class time_point
    {
     public:
      using duration = filetime_clock::duration;

      constexpr time_point() noexcept = default;

      constexpr time_point(time_point const &) noexcept = default;

      auto operator=(time_point const &) noexcept -> time_point & = default;

      [[nodiscard]]
      auto get_ticks() const noexcept -> std::uint64_t
      {
        return ticks_;
      }

      static constexpr auto(max)() noexcept -> time_point
      {
        time_point tp;
        tp.ticks_ = (std::numeric_limits<std::int64_t>::max)();
        return tp;
      }

      static constexpr auto(min)() noexcept -> time_point
      {
        return time_point{};
      }

      static auto from_ticks(std::uint64_t ticks) noexcept -> time_point
      {
        time_point tp;
        tp.ticks_ = ticks;
        return tp;
      }

      template <typename Rep, typename Ratio>
      auto operator+=(std::chrono::duration<Rep, Ratio> const &d) noexcept -> time_point &
      {
        ticks_ += std::chrono::duration_cast<duration>(d).count();
        return *this;
      }

      template <typename Rep, typename Ratio>
      auto operator-=(std::chrono::duration<Rep, Ratio> const &d) noexcept -> time_point &
      {
        ticks_ -= std::chrono::duration_cast<duration>(d).count();
        return *this;
      }

      friend auto operator-(time_point a, time_point b) noexcept -> duration
      {
        return duration{a.ticks_} - duration{b.ticks_};
      }

      template <typename Rep, typename Ratio>
      friend auto
      operator-(time_point t, std::chrono::duration<Rep, Ratio> d) noexcept -> time_point
      {
        time_point tp = t;
        tp -= d;
        return tp;
      }

      template <typename Rep, typename Ratio>
      friend auto
      operator+(time_point t, std::chrono::duration<Rep, Ratio> d) noexcept -> time_point
      {
        time_point tp = t;
        tp += d;
        return tp;
      }

      template <typename Rep, typename Ratio>
      friend auto
      operator+(std::chrono::duration<Rep, Ratio> d, time_point t) noexcept -> time_point
      {
        return t + d;
      }

      friend auto operator==(time_point a, time_point b) noexcept -> bool
      {
        return a.ticks_ == b.ticks_;
      }

      friend auto operator!=(time_point a, time_point b) noexcept -> bool
      {
        return a.ticks_ != b.ticks_;
      }

      friend auto operator<(time_point a, time_point b) noexcept -> bool
      {
        return a.ticks_ < b.ticks_;
      }

      friend auto operator>(time_point a, time_point b) noexcept -> bool
      {
        return a.ticks_ > b.ticks_;
      }

      friend auto operator<=(time_point a, time_point b) noexcept -> bool
      {
        return a.ticks_ <= b.ticks_;
      }

      friend auto operator>=(time_point a, time_point b) noexcept -> bool
      {
        return a.ticks_ >= b.ticks_;
      }

     private:
      // Ticks since Jan 1, 1601 (UTC)
      std::uint64_t ticks_{};
    };

    static auto now() noexcept -> time_point
    {
      FILETIME filetime;
      ::GetSystemTimeAsFileTime(&filetime);

      ULARGE_INTEGER ticks;
      ticks.HighPart = filetime.dwHighDateTime;
      ticks.LowPart  = filetime.dwLowDateTime;

      return time_point::from_ticks(ticks.QuadPart);
    }
  };
}  // namespace experimental::execution::__win32

namespace exec = experimental::execution;
