/*
 *  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 "event_linux.h"

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

namespace webrtc {
const long int E6 = 1000000;
const long int E9 = 1000 * E6;

EventWrapper* EventLinux::Create()
{
    EventLinux* ptr = new EventLinux;
    if (!ptr)
    {
        return NULL;
    }

    const int error = ptr->Construct();
    if (error)
    {
        delete ptr;
        return NULL;
    }
    return ptr;
}


EventLinux::EventLinux()
    : _timerThread(0),
      _timerEvent(0),
      _periodic(false),
      _time(0),
      _count(0),
      _state(kDown)
{
}

int EventLinux::Construct()
{
    // Set start time to zero
    memset(&_tCreate, 0, sizeof(_tCreate));

    int result = pthread_mutex_init(&mutex, 0);
    if (result != 0)
    {
        return -1;
    }
#ifdef WEBRTC_CLOCK_TYPE_REALTIME
    result = pthread_cond_init(&cond, 0);
    if (result != 0)
    {
        return -1;
    }
#else
    pthread_condattr_t condAttr;
    result = pthread_condattr_init(&condAttr);
    if (result != 0)
    {
        return -1;
    }
    result = pthread_condattr_setclock(&condAttr, CLOCK_MONOTONIC);
    if (result != 0)
    {
        return -1;
    }
    result = pthread_cond_init(&cond, &condAttr);
    if (result != 0)
    {
        return -1;
    }
    result = pthread_condattr_destroy(&condAttr);
    if (result != 0)
    {
        return -1;
    }
#endif
    return 0;
}

EventLinux::~EventLinux()
{
    StopTimer();
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);
}

bool EventLinux::Reset()
{
    if (0 != pthread_mutex_lock(&mutex))
    {
        return false;
    }
    _state = kDown;
    pthread_mutex_unlock(&mutex);
    return true;
}

bool EventLinux::Set()
{
    if (0 != pthread_mutex_lock(&mutex))
    {
        return false;
    }
    _state = kUp;
     // Release all waiting threads
    pthread_cond_broadcast(&cond);
    pthread_mutex_unlock(&mutex);
    return true;
}

EventTypeWrapper EventLinux::Wait(unsigned long timeout)
{
    int retVal = 0;
    if (0 != pthread_mutex_lock(&mutex))
    {
        return kEventError;
    }

    if (kDown == _state)
    {
        if (WEBRTC_EVENT_INFINITE != timeout)
        {
            timespec tEnd;
#ifndef WEBRTC_MAC
#ifdef WEBRTC_CLOCK_TYPE_REALTIME
            clock_gettime(CLOCK_REALTIME, &tEnd);
#else
            clock_gettime(CLOCK_MONOTONIC, &tEnd);
#endif
#else
            timeval tVal;
            struct timezone tZone;
            tZone.tz_minuteswest = 0;
            tZone.tz_dsttime = 0;
            gettimeofday(&tVal,&tZone);
            TIMEVAL_TO_TIMESPEC(&tVal,&tEnd);
#endif
            tEnd.tv_sec  += timeout / 1000;
            tEnd.tv_nsec += (timeout - (timeout / 1000) * 1000) * E6;

            if (tEnd.tv_nsec >= E9)
            {
                tEnd.tv_sec++;
                tEnd.tv_nsec -= E9;
            }
            retVal = pthread_cond_timedwait(&cond, &mutex, &tEnd);
        } else {
            retVal = pthread_cond_wait(&cond, &mutex);
        }
    }

    _state = kDown;
    pthread_mutex_unlock(&mutex);

    switch(retVal)
    {
    case 0:
        return kEventSignaled;
    case ETIMEDOUT:
        return kEventTimeout;
    default:
        return kEventError;
    }
}

EventTypeWrapper EventLinux::Wait(timespec& tPulse)
{
    int retVal = 0;
    if (0 != pthread_mutex_lock(&mutex))
    {
        return kEventError;
    }

    if (kUp != _state)
    {
        retVal = pthread_cond_timedwait(&cond, &mutex, &tPulse);
    }
    _state = kDown;

    pthread_mutex_unlock(&mutex);

    switch(retVal)
    {
    case 0:
        return kEventSignaled;
    case ETIMEDOUT:
        return kEventTimeout;
    default:
        return kEventError;
    }
}

bool EventLinux::StartTimer(bool periodic, unsigned long time)
{
    if (_timerThread)
    {
        if(_periodic)
        {
            // Timer already started.
            return false;
        } else  {
            // New one shot timer
            _time = time;
            _tCreate.tv_sec = 0;
            _timerEvent->Set();
            return true;
        }
    }

    // Start the timer thread
    _timerEvent = static_cast<EventLinux*>(EventWrapper::Create());
    const char* threadName = "WebRtc_event_timer_thread";
    _timerThread = ThreadWrapper::CreateThread(Run, this, kRealtimePriority,
                                               threadName);
    _periodic = periodic;
    _time = time;
    unsigned int id = 0;
    if (_timerThread->Start(id))
    {
        return true;
    }
    return false;
}

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

bool EventLinux::Process()
{
    if (_tCreate.tv_sec == 0)
    {
#ifndef WEBRTC_MAC
#ifdef WEBRTC_CLOCK_TYPE_REALTIME
        clock_gettime(CLOCK_REALTIME, &_tCreate);
#else
        clock_gettime(CLOCK_MONOTONIC, &_tCreate);
#endif
#else
        timeval tVal;
        struct timezone tZone;
        tZone.tz_minuteswest = 0;
        tZone.tz_dsttime = 0;
        gettimeofday(&tVal,&tZone);
        TIMEVAL_TO_TIMESPEC(&tVal,&_tCreate);
#endif
        _count=0;
    }

    timespec tEnd;
    unsigned long long time = _time * ++_count;
    tEnd.tv_sec  = _tCreate.tv_sec + time/1000;
    tEnd.tv_nsec = _tCreate.tv_nsec + (time - (time/1000)*1000)*E6;

    if ( tEnd.tv_nsec >= E9 )
    {
        tEnd.tv_sec++;
        tEnd.tv_nsec -= E9;
    }

    switch(_timerEvent->Wait(tEnd))
    {
    case kEventSignaled:
        return true;
    case kEventError:
        return false;
    case kEventTimeout:
        break;
    }
    if(_periodic || _count==1)
    {
        Set();
    }
    return true;
}

bool EventLinux::StopTimer()
{
    if(_timerThread)
    {
        _timerThread->SetNotAlive();
    }
    if (_timerEvent)
    {
        _timerEvent->Set();
    }
    if (_timerThread)
    {
        if(!_timerThread->Stop())
        {
            return false;
        }

        delete _timerThread;
        _timerThread = 0;
    }
    if (_timerEvent)
    {
        delete _timerEvent;
        _timerEvent = 0;
    }

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