/* libs/graphics/ports/SkOSEvent_android.cpp
**
** Copyright 2006, 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.
*/

#include "SkEvent.h"
#include "utils/threads.h"
#include <stdio.h>

using namespace android;

Mutex gEventQMutex;
Condition gEventQCondition;

void SkEvent::SignalNonEmptyQueue()
{
    gEventQCondition.broadcast();
}

///////////////////////////////////////////////////////////////////

#ifdef FMS_ARCH_ANDROID_ARM

// don't have pthreads.h, and therefore no timedwait(), so we punt for the demo

void SkEvent::SignalQueueTimer(SkMSec delay)
{
}

void SkEvent_start_timer_thread()
{
}

void SkEvent_stop_timer_thread()
{
}

#else

#include <pthread.h>
#include <errno.h>

static pthread_t        gTimerThread;
static pthread_mutex_t  gTimerMutex;
static pthread_cond_t   gTimerCond;
static timespec         gTimeSpec;

static void* timer_event_thread_proc(void*)
{
    for (;;)
    {
        int status;
        
        pthread_mutex_lock(&gTimerMutex);

        timespec spec = gTimeSpec;
        // mark our global to be zero
        // so we don't call timedwait again on a stale value
        gTimeSpec.tv_sec = 0;
        gTimeSpec.tv_nsec = 0;

        if (spec.tv_sec == 0 && spec.tv_nsec == 0)
            status = pthread_cond_wait(&gTimerCond, &gTimerMutex);
        else
            status = pthread_cond_timedwait(&gTimerCond, &gTimerMutex, &spec);
        
        if (status == 0)    // someone signaled us with a new time
        {
            pthread_mutex_unlock(&gTimerMutex);
        }
        else
        {
            SkASSERT(status == ETIMEDOUT);  // no need to unlock the mutex (its unlocked)
            // this is the payoff. Signal the event queue to wake up
            // and also check the delay-queue
            gEventQCondition.broadcast();
        }
    }
    return 0;
}

#define kThousand   (1000)
#define kMillion    (kThousand * kThousand)
#define kBillion    (kThousand * kThousand * kThousand)

void SkEvent::SignalQueueTimer(SkMSec delay)
{
    pthread_mutex_lock(&gTimerMutex);

    if (delay)
    {
        struct timeval tv;
        gettimeofday(&tv, NULL);

        // normalize tv
        if (tv.tv_usec >= kMillion)
        {
            tv.tv_sec += tv.tv_usec / kMillion;
            tv.tv_usec %= kMillion;
        }

        // add tv + delay, scale each up to land on nanoseconds
        gTimeSpec.tv_nsec   = (tv.tv_usec + (delay % kThousand) * kThousand) * kThousand;
        gTimeSpec.tv_sec    = (tv.tv_sec + (delay / kThousand) * kThousand) * kThousand;
        
        // check for overflow in nsec
        if ((unsigned long)gTimeSpec.tv_nsec >= kBillion)
        {
            gTimeSpec.tv_nsec -= kBillion;
            gTimeSpec.tv_sec += 1;
            SkASSERT((unsigned long)gTimeSpec.tv_nsec < kBillion);
        }

    //  printf("SignalQueueTimer(%d) timespec(%d %d)\n", delay, gTimeSpec.tv_sec, gTimeSpec.tv_nsec);
    }
    else    // cancel the timer
    {
        gTimeSpec.tv_nsec = 0;
        gTimeSpec.tv_sec = 0;
    }

    pthread_mutex_unlock(&gTimerMutex);
    pthread_cond_signal(&gTimerCond);
}

void SkEvent_start_timer_thread()
{
    int             status;
    pthread_attr_t  attr;
    
    status = pthread_attr_init(&attr);
    SkASSERT(status == 0);
    status = pthread_create(&gTimerThread, &attr, timer_event_thread_proc, 0);
    SkASSERT(status == 0);
}

void SkEvent_stop_timer_thread()
{
    int status = pthread_cancel(gTimerThread);
    SkASSERT(status == 0);
}

#endif