/** @file

  NetTimeout

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you 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 "tscore/List.h"
#include "tscore/ink_hrtime.h"

#include "iocore/eventsystem/EventSystem.h"

/**
  NetTimeout - handle active & inactive timeout
 */
class NetTimeout
{
public:
  void       set_active_timeout(ink_hrtime timeout_in);
  void       set_inactive_timeout(ink_hrtime timeout_in);
  void       cancel_active_timeout();
  void       cancel_inactive_timeout();
  void       reset_active_timeout();
  void       reset_inactive_timeout();
  bool       is_active_timeout_expired(ink_hrtime now);
  bool       is_inactive_timeout_expired(ink_hrtime now);
  ink_hrtime idle_time(ink_hrtime now);
  void       update_inactivity();

private:
  static DbgCtl &
  _dbg_ctl()
  {
    static DbgCtl dc{"activity_cop"};
    return dc;
  }

  ink_hrtime _active_timeout_in        = 0;
  ink_hrtime _inactive_timeout_in      = 0;
  ink_hrtime _next_active_timeout_at   = 0;
  ink_hrtime _next_inactive_timeout_at = 0;
};

/**
  ActivityCop - Check activity of T in the List in every @f seconds

  T have to handle VC_EVENT_ACTIVE_TIMEOUT and VC_EVENT_INACTIVITY_TIMEOUT events.

  TODO: add concepts like below with C++20
  ```
  template <class T, class List = DLL<T>>
  concept Timeoutable = requires(T *t, List *list, ink_hrtime time) {
    t->handleEvent();
    t->head;
    {list->next(t)} -> std::convertible_to<T *>;
    {t->is_active_timeout_expired(time)} -> std::same_as<bool>;
    {t->is_inactive_timeout_expired(time)} -> std::same_as<bool>;
  };
  ```
 */
template <class T, class List = DLL<T>> class ActivityCop : public Continuation
{
public:
  ActivityCop(){};
  ActivityCop(Ptr<ProxyMutex> &m, List *l, int f);

  void start();
  void stop();
  int  check_activity(int event, Event *e);

private:
  Event *_event = nullptr;
  List  *_list  = nullptr;
  int    _freq  = 1;
};

////
// Inline functions

//
// NetTimeout
//
inline void
NetTimeout::set_active_timeout(ink_hrtime timeout_in)
{
  if (timeout_in == 0) {
    return;
  }

  _active_timeout_in      = timeout_in;
  _next_active_timeout_at = ink_get_hrtime() + timeout_in;
}

inline void
NetTimeout::set_inactive_timeout(ink_hrtime timeout_in)
{
  if (timeout_in == 0) {
    return;
  }

  _inactive_timeout_in      = timeout_in;
  _next_inactive_timeout_at = ink_get_hrtime() + timeout_in;
}

inline void
NetTimeout::cancel_active_timeout()
{
  _active_timeout_in      = 0;
  _next_active_timeout_at = 0;
}

inline void
NetTimeout::cancel_inactive_timeout()
{
  _inactive_timeout_in      = 0;
  _next_inactive_timeout_at = 0;
}

inline void
NetTimeout::reset_active_timeout()
{
  if (_active_timeout_in == 0) {
    return;
  }

  _next_active_timeout_at = ink_get_hrtime() + _active_timeout_in;
}

inline void
NetTimeout::reset_inactive_timeout()
{
  if (_inactive_timeout_in == 0) {
    return;
  }

  _next_inactive_timeout_at = ink_get_hrtime() + _inactive_timeout_in;
}

inline bool
NetTimeout::is_active_timeout_expired(ink_hrtime now)
{
  ink_assert(now > 0);

  if (_active_timeout_in == 0) {
    return false;
  }

  if (0 < _next_active_timeout_at && _next_active_timeout_at < now) {
    Dbg(_dbg_ctl(), "active timeout cont=%p now=%" PRId64 " timeout_at=%" PRId64 " timeout_in=%" PRId64, this,
        ink_hrtime_to_sec(now), ink_hrtime_to_sec(_next_active_timeout_at), ink_hrtime_to_sec(_active_timeout_in));
    return true;
  }

  return false;
}

inline bool
NetTimeout::is_inactive_timeout_expired(ink_hrtime now)
{
  ink_assert(now > 0);

  if (_inactive_timeout_in == 0) {
    return false;
  }

  if (0 < _next_inactive_timeout_at && _next_inactive_timeout_at < now) {
    Dbg(_dbg_ctl(), "inactive timeout cont=%p now=%" PRId64 " timeout_at=%" PRId64 " timeout_in=%" PRId64, this,
        ink_hrtime_to_sec(now), ink_hrtime_to_sec(_next_inactive_timeout_at), ink_hrtime_to_sec(_inactive_timeout_in));
    return true;
  }

  return false;
}

/**
  Return how log this was inactive.
 */
inline ink_hrtime
NetTimeout::idle_time(ink_hrtime now)
{
  if (now < _next_inactive_timeout_at) {
    return 0;
  }

  return ink_hrtime_to_sec((now - _next_inactive_timeout_at) + _inactive_timeout_in);
}

inline void
NetTimeout::update_inactivity()
{
  if (_inactive_timeout_in == 0) {
    return;
  }

  _next_inactive_timeout_at = ink_get_hrtime() + _inactive_timeout_in;
}

//
// ActivityCop
//
template <class T, class List>
inline ActivityCop<T, List>::ActivityCop(Ptr<ProxyMutex> &m, List *l, int f) : Continuation(m.get()), _list(l), _freq(f)
{
  SET_HANDLER((&ActivityCop<T, List>::check_activity));
}

template <class T, class List>
inline void
ActivityCop<T, List>::start()
{
  if (_event == nullptr) {
    _event = this_ethread()->schedule_every(this, HRTIME_SECONDS(_freq));
  }
}

template <class T, class List>
inline void
ActivityCop<T, List>::stop()
{
  if (_event != nullptr) {
    _event->cancel();
    _event = nullptr;
  }
}

template <class T, class List>
inline int
ActivityCop<T, List>::check_activity(int /* event */, Event *e)
{
  ink_hrtime now = ink_get_hrtime();

  // Traverse list & check inactivity or activity
  T *t = _list->head;
  while (t) {
    T *next = _list->next(t);
    if (t->mutex == nullptr) {
      t = next;
      continue;
    }

    MUTEX_TRY_LOCK(lock, t->mutex, this_ethread());
    if (!lock.is_locked()) {
      t = next;
      continue;
    }

    if (t->is_inactive_timeout_expired(now)) {
      t->handleEvent(VC_EVENT_INACTIVITY_TIMEOUT, e);
    } else if (t->is_active_timeout_expired(now)) {
      t->handleEvent(VC_EVENT_ACTIVE_TIMEOUT, e);
    }

    t = next;
  }

  return EVENT_DONE;
}
