/*
 *  Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "webrtc/system_wrappers/source/event_timer_posix.h"

#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>

#include "webrtc/base/checks.h"

namespace webrtc {

// static
EventTimerWrapper* EventTimerWrapper::Create() {
  return new EventTimerPosix();
}

const int64_t kNanosecondsPerMillisecond = 1000000;
const int64_t kNanosecondsPerSecond = 1000000000;

EventTimerPosix::EventTimerPosix()
    : event_set_(false),
      timer_thread_(nullptr),
      created_at_(),
      periodic_(false),
      time_ms_(0),
      count_(0),
      is_stopping_(false) {
  pthread_mutexattr_t attr;
  pthread_mutexattr_init(&attr);
  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
  pthread_mutex_init(&mutex_, &attr);
  pthread_condattr_t cond_attr;
  pthread_condattr_init(&cond_attr);
// TODO(sprang): Remove HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC special case once
// all supported Android platforms support pthread_condattr_setclock.
// TODO(sprang): Add support for monotonic clock on Apple platforms.
#if !(defined(WEBRTC_MAC) || defined(WEBRTC_IOS)) && \
    !(defined(WEBRTC_ANDROID) &&                     \
      defined(HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC))
  pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC);
#endif
  pthread_cond_init(&cond_, &cond_attr);
  pthread_condattr_destroy(&cond_attr);
}

EventTimerPosix::~EventTimerPosix() {
  StopTimer();
  pthread_cond_destroy(&cond_);
  pthread_mutex_destroy(&mutex_);
}

// TODO(pbos): Make this void.
bool EventTimerPosix::Set() {
  RTC_CHECK_EQ(0, pthread_mutex_lock(&mutex_));
  event_set_ = true;
  pthread_cond_signal(&cond_);
  pthread_mutex_unlock(&mutex_);
  return true;
}

EventTypeWrapper EventTimerPosix::Wait(unsigned long timeout_ms) {
  int ret_val = 0;
  RTC_CHECK_EQ(0, pthread_mutex_lock(&mutex_));

  if (!event_set_) {
    if (WEBRTC_EVENT_INFINITE != timeout_ms) {
      timespec end_at;
#ifndef WEBRTC_MAC
      clock_gettime(CLOCK_MONOTONIC, &end_at);
#else
      timeval value;
      struct timezone time_zone;
      time_zone.tz_minuteswest = 0;
      time_zone.tz_dsttime = 0;
      gettimeofday(&value, &time_zone);
      TIMEVAL_TO_TIMESPEC(&value, &end_at);
#endif
      end_at.tv_sec += timeout_ms / 1000;
      end_at.tv_nsec += (timeout_ms % 1000) * kNanosecondsPerMillisecond;

      if (end_at.tv_nsec >= kNanosecondsPerSecond) {
        end_at.tv_sec++;
        end_at.tv_nsec -= kNanosecondsPerSecond;
      }
      while (ret_val == 0 && !event_set_) {
#if defined(WEBRTC_ANDROID) && defined(HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC)
        ret_val = pthread_cond_timedwait_monotonic_np(&cond_, &mutex_, &end_at);
#else
        ret_val = pthread_cond_timedwait(&cond_, &mutex_, &end_at);
#endif  // WEBRTC_ANDROID && HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC
      }
    } else {
      while (ret_val == 0 && !event_set_)
        ret_val = pthread_cond_wait(&cond_, &mutex_);
    }
  }

  RTC_DCHECK(ret_val == 0 || ret_val == ETIMEDOUT);

  // Reset and signal if set, regardless of why the thread woke up.
  if (event_set_) {
    ret_val = 0;
    event_set_ = false;
  }
  pthread_mutex_unlock(&mutex_);

  return ret_val == 0 ? kEventSignaled : kEventTimeout;
}

EventTypeWrapper EventTimerPosix::Wait(timespec* end_at, bool reset_event) {
  int ret_val = 0;
  RTC_CHECK_EQ(0, pthread_mutex_lock(&mutex_));
  if (reset_event) {
    // Only wake for new events or timeouts.
    event_set_ = false;
  }

  while (ret_val == 0 && !event_set_) {
#if defined(WEBRTC_ANDROID) && defined(HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC)
    ret_val = pthread_cond_timedwait_monotonic_np(&cond_, &mutex_, end_at);
#else
    ret_val = pthread_cond_timedwait(&cond_, &mutex_, end_at);
#endif  // WEBRTC_ANDROID && HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC
  }

  RTC_DCHECK(ret_val == 0 || ret_val == ETIMEDOUT);

  // Reset and signal if set, regardless of why the thread woke up.
  if (event_set_) {
    ret_val = 0;
    event_set_ = false;
  }
  pthread_mutex_unlock(&mutex_);

  return ret_val == 0 ? kEventSignaled : kEventTimeout;
}

rtc::PlatformThread* EventTimerPosix::CreateThread() {
  const char* kThreadName = "WebRtc_event_timer_thread";
  return new rtc::PlatformThread(Run, this, kThreadName);
}

bool EventTimerPosix::StartTimer(bool periodic, unsigned long time_ms) {
  pthread_mutex_lock(&mutex_);
  if (timer_thread_) {
    if (periodic_) {
      // Timer already started.
      pthread_mutex_unlock(&mutex_);
      return false;
    } else  {
      // New one shot timer.
      time_ms_ = time_ms;
      created_at_.tv_sec = 0;
      timer_event_->Set();
      pthread_mutex_unlock(&mutex_);
      return true;
    }
  }

  // Start the timer thread.
  timer_event_.reset(new EventTimerPosix());
  timer_thread_.reset(CreateThread());
  periodic_ = periodic;
  time_ms_ = time_ms;
  timer_thread_->Start();
  timer_thread_->SetPriority(rtc::kRealtimePriority);
  pthread_mutex_unlock(&mutex_);

  return true;
}

bool EventTimerPosix::Run(void* obj) {
  return static_cast<EventTimerPosix*>(obj)->Process();
}

bool EventTimerPosix::Process() {
  pthread_mutex_lock(&mutex_);
  if (is_stopping_) {
    pthread_mutex_unlock(&mutex_);
    return false;
  }
  if (created_at_.tv_sec == 0) {
#ifndef WEBRTC_MAC
    RTC_CHECK_EQ(0, clock_gettime(CLOCK_MONOTONIC, &created_at_));
#else
    timeval value;
    struct timezone time_zone;
    time_zone.tz_minuteswest = 0;
    time_zone.tz_dsttime = 0;
    gettimeofday(&value, &time_zone);
    TIMEVAL_TO_TIMESPEC(&value, &created_at_);
#endif
    count_ = 0;
  }

  timespec end_at;
  unsigned long long total_delta_ms = time_ms_ * ++count_;
  if (!periodic_ && count_ >= 1) {
    // No need to wake up often if we're not going to signal waiting threads.
    total_delta_ms =
        std::min<uint64_t>(total_delta_ms, 60 * kNanosecondsPerSecond);
  }

  end_at.tv_sec = created_at_.tv_sec + total_delta_ms / 1000;
  end_at.tv_nsec = created_at_.tv_nsec +
                   (total_delta_ms % 1000) * kNanosecondsPerMillisecond;

  if (end_at.tv_nsec >= kNanosecondsPerSecond) {
    end_at.tv_sec++;
    end_at.tv_nsec -= kNanosecondsPerSecond;
  }

  pthread_mutex_unlock(&mutex_);
  // Reset event on first call so that we don't immediately return here if this
  // thread was not blocked on timer_event_->Wait when the StartTimer() call
  // was made.
  if (timer_event_->Wait(&end_at, count_ == 1) == kEventSignaled)
    return true;

  pthread_mutex_lock(&mutex_);
  if (periodic_ || count_ == 1)
    Set();
  pthread_mutex_unlock(&mutex_);

  return true;
}

bool EventTimerPosix::StopTimer() {
  pthread_mutex_lock(&mutex_);
  is_stopping_ = true;
  pthread_mutex_unlock(&mutex_);

  if (timer_event_)
    timer_event_->Set();

  if (timer_thread_) {
    timer_thread_->Stop();
    timer_thread_.reset();
  }
  timer_event_.reset();

  // Set time to zero to force new reference time for the timer.
  memset(&created_at_, 0, sizeof(created_at_));
  count_ = 0;
  return true;
}

}  // namespace webrtc