#include <err.h>
#include <debug.h>
#include <kernel/Timer.h>
#include <kernel/AutoSpinlock.h>
#include <dev/Timer.h>
#include <dev/DeviceManager.h>
#include <os/Module.h>
#include <platform/platform.h>

namespace OS {
namespace Kernel {

	// The main kernel timer queue
	class TimerQueue : private Uncopyable {
	public:
		void Add(Timer *t);
		void Remove(Timer *t);

		handlerReturn TimerCallback(time_t now);	
		static handlerReturn devTimerCallback(Dev::Timer *t, time_t now);

		List<Timer> mQueue;
		Spinlock mLock;
	};
	static TimerQueue sKernelQueue;
	
	const time_t kTimerResolution = 10; // 10ms

	static status_t KernelTimerQueueInit(OS::Module *mod)
	{
		TRACE_ENTRY;

		Dev::Timer *devTimer = Dev::OSTimer();
		ASSERT(devTimer);

		devTimer->SetPeriodic(&TimerQueue::devTimerCallback, kTimerResolution);
		dprintf("%s: set up periodic hardware timer at %ld ms\n", __PRETTY_FUNCTION__, kTimerResolution);

		return NO_ERROR;
	}

	OS::Module timerModule("kernel timers", OS::Module::LEVEL_TIMERS-1, &KernelTimerQueueInit);

	void TimerQueue::Add(Timer *t)
	{
		AutoSpinlock al(mLock);

		t->mQueue = this;

		Timer *curr;
		for (curr = mQueue.Head(); curr != NULL; curr = curr->mQueueNode.Next()) {
			if (t->mScheduledTime < curr->mScheduledTime) {
				t->mQueueNode.AddBefore(&curr->mQueueNode);
				return;
			}
		}

		// must have walked off the end, add it to the tail of the list
		mQueue.AddToTail(&t->mQueueNode);
	}

	void TimerQueue::Remove(Timer *t)
	{
		AutoSpinlock al(mLock);

		ASSERT(t->mQueue == this);

		t->mQueueNode.Remove();
		t->mQueue = NULL;
	}

	handlerReturn TimerQueue::devTimerCallback(Dev::Timer *, time_t now)
	{
		return sKernelQueue.TimerCallback(now);
	}

	handlerReturn TimerQueue::TimerCallback(time_t now)
	{
//		dprintf("%s: time %d\n", __PRETTY_FUNCTION__, (int)now);

		AutoSpinlock al(mLock);

		// see if there is going to be at least one event to consume
		Timer *t = mQueue.Head();
		if (likely(!t || t->mScheduledTime > now))
			return INT_NO_RESCHEDULE;

		// look at the timer queue, consume some events from it
		List<Timer> tempList;
		for (t = mQueue.Head(); t != NULL; ) {
			if (t->mScheduledTime > now)
				break; // we're done

			Timer *temp = mQueue.PopHead();
			tempList.AddToTail(&temp->mQueueNode);
			
			t = mQueue.Head();
		}
		
		// we're done with the list for now, release the lock
		al.Release();

//		dprintf("%s: have some events to process:\n", __PRETTY_FUNCTION__);
//		tempList.Dump();
	
		// we now have a list of events to process
		List<Timer> reAddList;
		handlerReturn ret = INT_NO_RESCHEDULE;
		for (t = tempList.PopHead(); t != NULL; t = tempList.PopHead()) {
				
			/* do the callback */
			handlerReturn tempret = t->mCallback(t, t->mData, now);
			if (tempret == INT_RESCHEDULE)
				ret = INT_RESCHEDULE;

			/* if it's periodic, stick it back in the queue */
			if (t->mPeriodic) {
				t->mScheduledTime += t->mPeriod;
				reAddList.AddToTail(&t->mQueueNode);
			}
		}

		if (reAddList.Count() > 0) {
			AutoSpinlock al2(mLock);
			
			for (t = reAddList.PopHead(); t != NULL; t = reAddList.PopHead()) {
				Add(t);
			}
		}
		
		return ret;
	}

// Kernel::Timer
	time_t Timer::SetPeriodic(timerCallback callback, void *data, time_t period)
	{
		time_t time = Platform::GetCurrentTime();

		ASSERT(!mQueue && !mQueueNode.IsInList());

		mCallback = callback;
		mData = data;
		mScheduledTime = time + period;
		mPeriod = period;
		mPeriodic = true;

		// add to queue
		sKernelQueue.Add(this);

		return mScheduledTime;
	}

	time_t Timer::SetOneshot(timerCallback callback, void *data, time_t delay)
	{
		time_t time = Platform::GetCurrentTime();

		ASSERT(!mQueue && !mQueueNode.IsInList());

		mCallback = callback;
		mData = data;
		mScheduledTime = time + delay;
		mPeriod = 0;
		mPeriodic = false;

		// add to queue
		sKernelQueue.Add(this);

		return mScheduledTime;
	}

	status_t Timer::Clear()
	{
		if (mQueue)
			mQueue->Remove(this);

		return NO_ERROR;
	}

	void Timer::Dump() 
	{
		dprintf("Timer struct at %p: time 0x%lx period 0x%lx periodic %d handler %p data %p\n", 
				this, mScheduledTime, mPeriod, mPeriodic, mCallback, mData);
	}
}} // namespace OS::Kernel
