/*
 * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
 * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com
 * Copyright (C) 2007 Pleyo
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "kjs/wk_config.h"
#include "SharedTimer.h"
#include "SystemTime.h"
#include "BIEventLoop.h"
#include <wtf/Assertions.h>
#include "BALConfiguration.h"
#include "BTLogHelper.h"

#ifndef LITE_OS
	#include <assert.h>
	#include <signal.h>
#else
	void * __dso_handle;
#endif

#include <sys/time.h>
#include <errno.h>

#ifdef __FreeBSD__
#define STLIN_SIG        SIGVTALRM
#define STLIN_TIMER      ITIMER_VIRTUAL
#else /* Linux */
#define STLIN_SIG        SIGALRM
#define STLIN_TIMER      ITIMER_REAL
#endif


/**
 * @file ShareTimerLinux.cpp
 *
 * Linux setitimer implementation of SharedTimers.
 * SDL (used for event loop) may be compiled without timers and threads support.
 */

namespace WebCore {


#ifndef LITE_OS

static struct itimerval itimer;

/* Our SIGALRM signal handler */
static struct sigaction sa;

#else

static struct itimerspec itimer;
static struct sigevent sev;
static timer_t timerid = 0;


#endif

void (*sharedTimerFiredFunction)() = NULL;

// Single timer, shared to implement all the timers managed by the Timer class.
// Not intended to be used directly; use the Timer class instead.


/**
 * Macro to use to protect code from firing timers.
 * This ensures that there is no concurrent access on ressource and prevent deadlocks.
 */
#ifndef LITE_OS
#define ENTER_CRITICAL_SECTION sigset_t newmask, oldmask, zeromask; \
                                sigemptyset(&newmask); sigemptyset(&zeromask); \
                                sigaddset(&newmask, STLIN_SIG); \
                                sigprocmask(SIG_BLOCK, &newmask, &oldmask);

#define LEAVE_CRITICAL_SECTION sigprocmask(SIG_SETMASK, &oldmask, NULL);
#else
#include <pthread.h>
pthread_mutex_t     lock =  PTHREAD_MUTEX_INITIALIZER;
#define ENTER_CRITICAL_SECTION pthread_mutex_lock(&lock);
#define LEAVE_CRITICAL_SECTION pthread_mutex_unlock(&lock);
#endif

static unsigned int m_count = 0;
/**
 * We absolutely need to protect these 2 functions in a critical section.
 * Signals/timers will increment count, event loop will decrement it: 2 concurrent accesses.
 * Increment is usually done in 3 machine instructions, so there's room for a concurrent access.
 * See ticket #123. Maybe we should put timers in a separate thread to be able to use mutexes.
 */

void fireTimerIfNeeded() {
	TQDEBUG_FUNC_IN
    //ENTER_CRITICAL_SECTION;
    if (m_count>0) {
        m_count--;
		if (sharedTimerFiredFunction) {
			TQDEBUG_DEBUG("[DEBUG info] sharedTimerFiredFunction IN");
             sharedTimerFiredFunction();
             TQDEBUG_DEBUG("[DEBUG info] sharedTimerFiredFunction OUT");
		} else {
			TQDEBUG_DEBUG("[DEBUG info] sharedTimerFiredFunction is null");
		}
    } else {
    	TQDEBUG_DEBUG("[DEBUG info] sharedTimerFiredFunction m_count =< 0");
    }
    //LEAVE_CRITICAL_SECTION;
}

void incrementTimerCount()
{
    //ENTER_CRITICAL_SECTION;
    m_count++;
    //LEAVE_CRITICAL_SECTION;
}

#ifndef LITE_OS

void catcher( int sig ) {
	TQDEBUG_FUNC_IN
    // entering critical section
    // because this signal handler uses a malloc it is not reentrant:
    // we must enter in a critical section
    
    if( sharedTimerFiredFunction ) {
        incrementTimerCount();
    }
    else {
    	TQDEBUG_DEBUG("no sharedTimerFiredFunction");
    }

}
#else

void catcher( union sigval val ) {
	TQDEBUG_FUNC_IN
    if( sharedTimerFiredFunction ) {
        incrementTimerCount();
    }
    else {
    	TQDEBUG_DEBUG("no sharedTimerFiredFunction");
    }

}

#endif

void setSharedTimerFiredFunction(void (*f)()) {
	TQDEBUG_FUNC_IN
    if( sharedTimerFiredFunction == NULL )
    {
        sharedTimerFiredFunction = f;
        /* Install our signal handler */
#ifndef LITE_OS
        sa.sa_handler = catcher;
        sigemptyset( &sa.sa_mask );
        sa.sa_flags = SA_RESTART|SA_SIGINFO;
        /*use SIGVTALRM if  ITIMER_VIRTUAL */
        if (sigaction (STLIN_SIG, &sa, 0) == -1) {
            perror("sigaction");
            exit(1);
        }
#else
    {
        //ENTER_CRITICAL_SECTION;
        m_count++;
        //LEAVE_CRITICAL_SECTION;
    }
#endif
    }

}

// The fire time is relative to the classic POSIX epoch of January 1, 1970,
// as the result of currentTime() is.
void setSharedTimerFireTime(double fireTime) {
	TQDEBUG_FUNC_IN
    double interval = fireTime - currentTime();

    if( interval <= 0 )
        interval = 0.000001; 

#ifndef LITE_OS
    /* Request SIGALRM */
    assert(sharedTimerFiredFunction);

    itimer.it_interval.tv_sec = 0;
    itimer.it_interval.tv_usec = 0;
    itimer.it_value.tv_sec = (long)interval;
    itimer.it_value.tv_usec = (long)((interval-(long)interval)*1000000.0);
    //FIXME: must not be zero or does not fire on all kernels
    if( itimer.it_value.tv_sec == 0 && itimer.it_value.tv_usec == 0 )
        itimer.it_value.tv_usec = 1;

   //printf("timer ID is 0x%lx\n", (long) timerid);

   /*modes ITIMER_PROF ITIMER_REAL ITIMER_VIRTUAL*/
    if (-1 == setitimer(STLIN_TIMER, &itimer, NULL))
    	perror("setSharedTimerFireTime");
#else
   /* Create the timer */
    itimer.it_interval.tv_sec = 0;
    itimer.it_interval.tv_nsec = 0;
    itimer.it_value.tv_sec = (long)interval;
    itimer.it_value.tv_nsec = (long)((interval-(long)interval)*1000000.0);
    //FIXME: must not be zero or does not fire on all kernels
    if( itimer.it_value.tv_sec == 0 && itimer.it_value.tv_nsec == 0 )
        itimer.it_value.tv_nsec = 1;


    sev.sigev_notify = SIGEV_THREAD;
    sev.sigev_notify_function = (void (*)(sigval)) catcher;
    //sev.sigev_value.sival_ptr = &timerid;
    if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
    	TQDEBUG_ERRORF("[%d]%s timerid=%ld\n",errno, strerror(errno), timerid);
    	if(errno==0) {
    		if (timer_settime(timerid, 0, &itimer, NULL) == -1) {
    			TQDEBUG_ERRORF("[%d]%s timerid=%ld\n", errno, strerror(errno), timerid);
    		}
    	}
    } else {
    	TQDEBUG_TRACEF("timerid=%ld [%s]", timerid, ctime((const time_t*) &timerid));
		if (timer_settime(timerid, 0, &itimer, NULL) == -1) {
			TQDEBUG_ERRORF("[%d]%s timerid=%ld\n", errno, strerror(errno), timerid);
		}
    }
#endif
}

void stopSharedTimer() {
	TQDEBUG_FUNC_IN
    itimer.it_interval.tv_sec = 0;
    itimer.it_value.tv_sec = 0;


#ifndef LITE_OS

    itimer.it_interval.tv_usec = 0;
    itimer.it_value.tv_usec = 0;

    if (-1 == setitimer(STLIN_TIMER, &itimer, NULL))
        perror("can't stopSharedTimer");
    
    /* Install our signal handler */
    sa.sa_handler = NULL;
    sigemptyset(&sa.sa_mask);

    if (sigaction (STLIN_SIG, 0, 0) == -1) {
        perror("sigaction");
        exit(1);
    }
#else
    itimer.it_interval.tv_nsec = 0;
    itimer.it_value.tv_nsec = 0;

    TQDEBUG_TRACEF("timerid=%ld [%s]", timerid, ctime((const time_t*) &timerid));
	if(!timerid) {
		TQDEBUG_WARNF("[ERROR] no timerid=%d\n", timerid);
		return;
	}


    if (timer_settime(timerid, 0, &itimer, NULL) == -1) {
    	TQDEBUG_ERRORF("%s timerid=%ld\n", strerror(errno), timerid);
        perror("can't stopSharedTimer");
    }

#endif
}

}

