/**
 * \file
 * \brief Scheduler class header
 *
 * \author Copyright (C) 2014-2016 Kamil Szczygiel http://www.distortec.com http://www.freddiechopin.info
 *
 * \par License
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not
 * distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#ifndef INCLUDE_DISTORTOS_INTERNAL_SCHEDULER_SCHEDULER_HPP_
#define INCLUDE_DISTORTOS_INTERNAL_SCHEDULER_SCHEDULER_HPP_

#include "distortos/internal/scheduler/ThreadControlBlock.hpp"
#include "distortos/internal/scheduler/ThreadList.hpp"
#include "distortos/internal/scheduler/SoftwareTimerSupervisor.hpp"

namespace distortos
{

namespace internal
{

class MainThread;

/// Scheduler class is a system's scheduler
class Scheduler
{
public:

	/**
	 * \brief Scheduler's constructor
	 */

	constexpr Scheduler() :
			currentThreadControlBlock_{},
			runnableList_{},
			suspendedList_{},
			softwareTimerSupervisor_{},
			contextSwitchCount_{},
			tickCount_{}
	{

	}

	/**
	 * \brief Adds new ThreadControlBlock to scheduler.
	 *
	 * ThreadControlBlock's state is changed to "runnable".
	 *
	 * \param [in] threadControlBlock is a reference to added ThreadControlBlock object
	 *
	 * \return 0 on success, error code otherwise:
	 * - EINVAL - thread is already started;
	 * - error codes returned by Scheduler::addInternal();
	 */

	int add(ThreadControlBlock& threadControlBlock);

	/**
	 * \brief Blocks current thread, transferring it to provided container.
	 *
	 * \param [in] container is a reference to destination container to which the thread will be transferred
	 * \param [in] state is the new state of thread that will be blocked
	 * \param [in] unblockFunctor is a pointer to ThreadControlBlock::UnblockFunctor which will be executed in
	 * ThreadControlBlock::unblockHook(), default - nullptr (no functor will be executed)
	 *
	 * \return 0 on success, error code otherwise:
	 * - EINTR - thread was unblocked with ThreadControlBlock::UnblockReason::signal;
	 * - ETIMEDOUT - thread was unblocked with ThreadControlBlock::UnblockReason::timeout;
	 */

	int block(ThreadList& container, ThreadState state, const ThreadControlBlock::UnblockFunctor* unblockFunctor = {});

	/**
	 * \brief Blocks thread, transferring it to provided container.
	 *
	 * The thread must be on "runnable" list - trying to block thread in other state is an error.
	 *
	 * \param [in] container is a reference to destination container to which the thread will be transferred
	 * \param [in] iterator is the iterator to the thread that will be blocked
	 * \param [in] state is the new state of thread that will be blocked
	 * \param [in] unblockFunctor is a pointer to ThreadControlBlock::UnblockFunctor which will be executed in
	 * ThreadControlBlock::unblockHook(), default - nullptr (no functor will be executed)
	 *
	 * \return 0 on success, error code otherwise:
	 * - EINTR - thread was unblocked with ThreadControlBlock::UnblockReason::signal (possible only when blocking
	 * current thread);
	 * - EINVAL - provided thread is not on "runnable" list;
	 * - ETIMEDOUT - thread was unblocked with ThreadControlBlock::UnblockReason::timeout (possible only when blocking
	 * current thread);
	 */

	int block(ThreadList& container, ThreadList::iterator iterator, ThreadState state,
			const ThreadControlBlock::UnblockFunctor* unblockFunctor = {});

	/**
	 * \brief Blocks current thread with timeout, transferring it to provided container.
	 *
	 * \param [in] container is a reference to destination container to which the thread will be transferred
	 * \param [in] state is the new state of thread that will be blocked
	 * \param [in] timePoint is the time point at which the thread will be unblocked (if not already unblocked)
	 * \param [in] unblockFunctor is a pointer to ThreadControlBlock::UnblockFunctor which will be executed in
	 * ThreadControlBlock::unblockHook(), default - nullptr (no functor will be executed)
	 *
	 * \return 0 on success, error code otherwise:
	 * - EINTR - thread was unblocked with ThreadControlBlock::UnblockReason::signal;
	 * - ETIMEDOUT - thread was unblocked because timePoint was reached;
	 */

	int blockUntil(ThreadList& container, ThreadState state, TickClock::time_point timePoint,
			const ThreadControlBlock::UnblockFunctor* unblockFunctor = {});

	/**
	 * \return number of context switches
	 */

	uint64_t getContextSwitchCount() const;

	/**
	 * \return reference to currently active ThreadControlBlock
	 */

	ThreadControlBlock& getCurrentThreadControlBlock() const
	{
		return *currentThreadControlBlock_;
	}

	/**
	 * \return reference to internal SoftwareTimerSupervisor object
	 */

	SoftwareTimerSupervisor& getSoftwareTimerSupervisor()
	{
		return softwareTimerSupervisor_;
	}

	/**
	 * \return const reference to internal SoftwareTimerSupervisor object
	 */

	const SoftwareTimerSupervisor& getSoftwareTimerSupervisor() const
	{
		return softwareTimerSupervisor_;
	}

	/**
	 * \return current value of tick count
	 */

	uint64_t getTickCount() const;

	/**
	 * \brief Scheduler's initialization
	 *
	 * \attention This must be called after constructor, before enabling any scheduling. Priority of main thread must
	 * be higher than priority of idle thread
	 *
	 * \param [in] mainThread is a reference to main thread
	 *
	 * \return 0 on success, error code otherwise:
	 * - error codes returned by Scheduler::addInternal();
	 */

	int initialize(MainThread& mainThread);

	/**
	 * \brief Requests context switch if it is needed.
	 *
	 * \attention This function must be called with interrupt masking enabled.
	 */

	void maybeRequestContextSwitch() const;

	/**
	 * \brief Removes current thread from Scheduler's control.
	 *
	 * Thread's state is changed to "terminated".
	 *
	 * \note This function must be called with masked interrupts.
	 *
	 * \note This function can be used only after thread's function returns an all cleanup is done.
	 *
	 * \return 0 on success, error code otherwise:
	 * - EINVAL - provided thread is not on "runnable" list and cannot be removed/terminated;
	 */

	int remove();

	/**
	 * \brief Resumes suspended thread.
	 *
	 * The thread must be on the "suspended" list - trying to resume thread that is not suspended is an error.
	 *
	 * \param [in] iterator is the iterator to the thread that will be resumed
	 *
	 * \return 0 on success, error code otherwise:
	 * - EINVAL - provided thread is not on "suspended" list;
	 */

	int resume(ThreadList::iterator iterator);

	/**
	 * \brief Suspends current thread.
	 *
	 * \return 0 on success, error code otherwise:
	 * - EINTR - thread was unblocked with ThreadControlBlock::UnblockReason::signal;
	 */

	int suspend();

	/**
	 * \brief Suspends thread.
	 *
	 * The thread must be on "runnable" list - trying to suspend thread in other state is an error.
	 *
	 * \param [in] iterator is the iterator to the thread that will be suspended
	 *
	 * \return 0 on success, error code otherwise:
	 * - EINTR - thread was unblocked with ThreadControlBlock::UnblockReason::signal;
	 * - EINVAL - provided thread is not on "runnable" list;
	 */

	int suspend(ThreadList::iterator iterator);

	/**
	 * \brief Called by architecture-specific code to do final context switch.
	 *
	 * Current task is suspended and the next available task is started.
	 *
	 * \param [in] stackPointer is the current value of current thread's stack pointer
	 *
	 * \return new thread's stack pointer
	 */

	void* switchContext(void* stackPointer);

	/**
	 * \brief Handler of "tick" interrupt.
	 *
	 * \note this must not be called by user code
	 *
	 * \return true if context switch is required, false otherwise
	 */

	bool tickInterruptHandler();

	/**
	 * \brief Unblocks provided thread, transferring it from it's current container to "runnable" container.
	 *
	 * Current container of the thread is obtained with ThreadControlBlock::getList().
	 *
	 * \param [in] iterator is the iterator which points to unblocked thread
	 * \param [in] unblockReason is the reason of unblocking of the thread, default -
	 * ThreadControlBlock::UnblockReason::unblockRequest
	 */

	void unblock(ThreadList::iterator iterator,
			ThreadControlBlock::UnblockReason unblockReason = ThreadControlBlock::UnblockReason::unblockRequest);

	/**
	 * \brief Yields time slot of the scheduler to next thread.
	 */

	void yield();

private:

	/**
	 * \brief Adds new ThreadControlBlock to scheduler.
	 *
	 * Internal version - without interrupt masking and call to Scheduler::maybeRequestContextSwitch()
	 *
	 * \param [in] threadControlBlock is a reference to added ThreadControlBlock object
	 *
	 * \return 0 on success, error code otherwise:
	 * - error codes returned by ThreadControlBlock::addHook();
	 */

	int addInternal(ThreadControlBlock& threadControlBlock);

	/**
	 * \brief Blocks thread, transferring it to provided container.
	 *
	 * Internal version - without interrupt masking and forced context switch.
	 *
	 * \param [in] container is a reference to destination container to which the thread will be transferred
	 * \param [in] iterator is the iterator to the thread that will be blocked
	 * \param [in] state is the new state of thread that will be blocked
	 * \param [in] unblockFunctor is a pointer to ThreadControlBlock::UnblockFunctor which will be executed in
	 * ThreadControlBlock::unblockHook()
	 *
	 * \return 0 on success, error code otherwise:
	 * - EINVAL - provided thread is not on "runnable" list;
	 */

	int blockInternal(ThreadList& container, ThreadList::iterator iterator, ThreadState state,
			const ThreadControlBlock::UnblockFunctor* unblockFunctor);

	/**
	 * \brief Tests whether context switch is required or not.
	 *
	 * Context switch is required in following situations:
	 * - current thread is no longer on "runnable" list,
	 * - current thread is no longer on the beginning of the "runnable" list (because higher-priority thread is
	 * available or current thread was "rotated" due to round-robin scheduling policy).
	 *
	 * \return true if context switch is required
	 */

	bool isContextSwitchRequired() const;

	/**
	 * \brief Unblocks provided thread, transferring it from it's current container to "runnable" container.
	 *
	 * Current container of the thread is obtained with ThreadControlBlock::getList(). Round-robin quantum of thread is
	 * reset.
	 *
	 * \note Internal version - without interrupt masking and yield()
	 *
	 * \param [in] iterator is the iterator which points to unblocked thread
	 * \param [in] unblockReason is the reason of unblocking of the thread
	 */

	void unblockInternal(ThreadList::iterator iterator, ThreadControlBlock::UnblockReason unblockReason);

	/// iterator to the currently active ThreadControlBlock
	ThreadList::iterator currentThreadControlBlock_;

	/// list of ThreadControlBlock elements in "runnable" state, sorted by priority in descending order
	ThreadList runnableList_;

	/// list of ThreadControlBlock elements in "suspended" state, sorted by priority in descending order
	ThreadList suspendedList_;

	/// internal SoftwareTimerSupervisor object
	SoftwareTimerSupervisor softwareTimerSupervisor_;

	/// number of context switches
	uint64_t contextSwitchCount_;

	/// tick count
	uint64_t tickCount_;
};

}	// namespace internal

}	// namespace distortos

#endif	// INCLUDE_DISTORTOS_INTERNAL_SCHEDULER_SCHEDULER_HPP_