/**************************************************************
 * 
 * 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.
 * 
 *************************************************************/



#include <osl/time.h>

#include <vos/timer.hxx>
#include <vos/diagnose.hxx>
#include <vos/ref.hxx>
#include <vos/thread.hxx>
#include <vos/conditn.hxx>


/////////////////////////////////////////////////////////////////////////////
//
// Timer manager
//

class OTimerManagerCleanup;

class vos::OTimerManager : public vos::OThread
{
    
public:

    ///
  	OTimerManager();
    
	///
  	~OTimerManager();
    
  	/// register timer
    sal_Bool SAL_CALL registerTimer(vos::OTimer* pTimer);

  	/// unregister timer
    sal_Bool SAL_CALL unregisterTimer(vos::OTimer* pTimer);

  	/// lookup timer
    sal_Bool SAL_CALL lookupTimer(const vos::OTimer* pTimer);

	/// retrieves the "Singleton" TimerManager Instance
	static OTimerManager* SAL_CALL getTimerManager();

    
protected:
    
 	/// worker-function of thread
  	virtual void SAL_CALL run();

    // Checking and triggering of a timer event
    void SAL_CALL checkForTimeout();

    // cleanup Method
    virtual void SAL_CALL onTerminated();
    
  	// sorted-queue data
  	vos::OTimer*		m_pHead;
    // List Protection
    vos::OMutex		m_Lock;
    // Signal the insertion of a timer
    vos::OCondition	m_notEmpty;

    // Synchronize access to OTimerManager
	static vos::OMutex m_Access;

    // "Singleton Pattern"
    static vos::OTimerManager* m_pManager;

    friend class OTimerManagerCleanup;
    
};

using namespace vos;

/////////////////////////////////////////////////////////////////////////////
//
// Timer class
//

VOS_IMPLEMENT_CLASSINFO(VOS_CLASSNAME(OTimer, vos),
                        VOS_NAMESPACE(OTimer, vos),
                        VOS_NAMESPACE(OObject, vos), 0);

OTimer::OTimer()
{
	m_TimeOut	  = 0;
	m_Expired     = 0;
	m_RepeatDelta = 0;
  	m_pNext       = 0;
}

OTimer::OTimer(const TTimeValue& Time)
{
	m_TimeOut	  = Time;
	m_RepeatDelta = 0;
	m_Expired     = 0;
  	m_pNext       = 0;

	m_TimeOut.normalize();
}

OTimer::OTimer(const TTimeValue& Time, const TTimeValue& Repeat)
{
	m_TimeOut	  = Time;
	m_RepeatDelta = Repeat;
	m_Expired     = 0;
  	m_pNext       = 0;

	m_TimeOut.normalize();
	m_RepeatDelta.normalize();
}

OTimer::~OTimer()
{
    stop();
}

void OTimer::start()
{
	if (! isTicking())
	{
		if (! m_TimeOut.isEmpty())
			setRemainingTime(m_TimeOut);

		OTimerManager *pManager = OTimerManager::getTimerManager();

		VOS_ASSERT(pManager);

		if ( pManager != 0 )
		{
			pManager->registerTimer(this);
		}
	}
}

void OTimer::stop()
{
	OTimerManager *pManager = OTimerManager::getTimerManager();

	VOS_ASSERT(pManager);

	if ( pManager != 0 )
	{
		pManager->unregisterTimer(this);
	}
}

sal_Bool OTimer::isTicking() const
{
	OTimerManager *pManager = OTimerManager::getTimerManager();

	VOS_ASSERT(pManager);

	if (pManager)
		return pManager->lookupTimer(this);
	else
		return sal_False;

}

sal_Bool OTimer::isExpired() const
{
	TTimeValue Now;

	osl_getSystemTime(&Now);

	return !(Now < m_Expired);
}

sal_Bool OTimer::expiresBefore(const OTimer* pTimer) const
{
	VOS_ASSERT(pTimer);

	if ( pTimer != 0 )
	{
		return m_Expired < pTimer->m_Expired;
	}
	else
	{
		return sal_False;
	}
}

void OTimer::setAbsoluteTime(const TTimeValue& Time)
{
	m_TimeOut     = 0;
	m_Expired     = Time;
	m_RepeatDelta = 0;

	m_Expired.normalize();
}

void OTimer::setRemainingTime(const TTimeValue& Remaining)
{
	osl_getSystemTime(&m_Expired);

	m_Expired.addTime(Remaining);
}

void OTimer::setRemainingTime(const TTimeValue& Remaining, const TTimeValue& Repeat)
{
	osl_getSystemTime(&m_Expired);

	m_Expired.addTime(Remaining);

	m_RepeatDelta = Repeat;
}

void OTimer::addTime(const TTimeValue& Delta)
{
	m_Expired.addTime(Delta);
}

TTimeValue OTimer::getRemainingTime() const
{
	TTimeValue Now;

	osl_getSystemTime(&Now);

	sal_Int32 secs = m_Expired.Seconds - Now.Seconds;

	if (secs < 0)
		return TTimeValue(0, 0);

	sal_Int32 nsecs = m_Expired.Nanosec - Now.Nanosec;

	if (nsecs < 0)
	{
		if (secs > 0)
		{
			secs  -= 1;
			nsecs += 1000000000;
		}
		else
			return TTimeValue(0, 0);
	}

	return TTimeValue(secs, nsecs);
}


/////////////////////////////////////////////////////////////////////////////
//
// Timer manager
//

OMutex vos::OTimerManager::m_Access;
OTimerManager* vos::OTimerManager::m_pManager=0;

OTimerManager::OTimerManager()
{
	OGuard Guard(&m_Access);

	VOS_ASSERT(m_pManager == 0);

	m_pManager = this;

	m_pHead= 0;

	m_notEmpty.reset();

	// start thread
	create();
}

OTimerManager::~OTimerManager()
{
	OGuard Guard(&m_Access);

	if ( m_pManager == this )
		m_pManager = 0;
}

void OTimerManager::onTerminated()
{
    delete this; // mfe: AAARRRGGGHHH!!!
}
    
OTimerManager* OTimerManager::getTimerManager()
{    
	OGuard Guard(&m_Access); 
	
	if (! m_pManager)
		new OTimerManager;

	return (m_pManager);
}	

sal_Bool OTimerManager::registerTimer(OTimer* pTimer)
{
	VOS_ASSERT(pTimer);

	if ( pTimer == 0 )
	{
		return sal_False;
	}
	
	OGuard Guard(&m_Lock);

	// try to find one with equal or lower remaining time.
	OTimer** ppIter = &m_pHead;
	
	while (*ppIter) 
	{
		if (pTimer->expiresBefore(*ppIter))
		{	
			// next element has higher remaining time, 
			// => insert new timer before
			break;
		}
		ppIter= &((*ppIter)->m_pNext);
	} 
  
	// next element has higher remaining time, 
	// => insert new timer before
	pTimer->m_pNext= *ppIter;
	*ppIter = pTimer;
    

	if (pTimer == m_pHead)
	{
		// it was inserted as new head
		// signal it to TimerManager Thread
        m_notEmpty.set();
	}

	return sal_True;
}

sal_Bool OTimerManager::unregisterTimer(OTimer* pTimer)
{
	VOS_ASSERT(pTimer);

	if ( pTimer == 0 )
	{
		return sal_False;
	}
	
	// lock access
	OGuard Guard(&m_Lock);

	OTimer** ppIter = &m_pHead;

	while (*ppIter) 
	{
		if (pTimer == (*ppIter))
		{	
			// remove timer from list
			*ppIter = (*ppIter)->m_pNext;
			return sal_True;
		}
		ppIter= &((*ppIter)->m_pNext);
	} 

	return sal_False;
}

sal_Bool OTimerManager::lookupTimer(const OTimer* pTimer) 
{
	VOS_ASSERT(pTimer);

	if ( pTimer == 0 )
	{
		return sal_False;
	}
	
	// lock access
	OGuard Guard(&m_Lock);

	// check the list
	for (OTimer* pIter = m_pHead; pIter != 0; pIter= pIter->m_pNext)
	{
		if (pIter == pTimer)
        {    
			return sal_True;
        }
	}

	return sal_False;
}

void OTimerManager::checkForTimeout()
{

    m_Lock.acquire();

	if ( m_pHead == 0 )
	{
        m_Lock.release();        
		return;
	}

	OTimer* pTimer = m_pHead;

	if (pTimer->isExpired())
	{
		// remove expired timer
		m_pHead = pTimer->m_pNext;

        pTimer->acquire();
        
		m_Lock.release();
		
		pTimer->onShot();

		// restart timer if specified
		if ( ! pTimer->m_RepeatDelta.isEmpty() )
		{
			TTimeValue Now;

			osl_getSystemTime(&Now);

			Now.Seconds += pTimer->m_RepeatDelta.Seconds;				
			Now.Nanosec += pTimer->m_RepeatDelta.Nanosec;				

			pTimer->m_Expired = Now;

			registerTimer(pTimer);
		}
        pTimer->release();
	}
	else
	{
		m_Lock.release();
	}


	return;
}

void OTimerManager::run() 
{
	setPriority(TPriority_BelowNormal);

	while (schedule())
	{
		TTimeValue		delay;
		TTimeValue*		pDelay=0;

        
		m_Lock.acquire();

		if (m_pHead != 0)
        {
			delay = m_pHead->getRemainingTime();
            pDelay=&delay;
        }
        else
        {
            pDelay=0;
        }
        
        
        m_notEmpty.reset();

        m_Lock.release();

        
        m_notEmpty.wait(pDelay);

        checkForTimeout();
  	}

}


/////////////////////////////////////////////////////////////////////////////
//
// Timer manager cleanup
//

// jbu:
// The timer manager cleanup has been removed (no thread is killed anymore).
// So the thread leaks.
// This will result in a GPF in case the vos-library gets unloaded before
// process termination.
// -> TODO : rewrite this file, so that the timerManager thread gets destroyed,
//           when there are no timers anymore !
