#include "all-headers.h"


#define ENABLE_XTR_ASSERTIONS


#ifdef ENABLE_XTR_ASSERTIONS
  #define XTR_ASSERT_NON_NULL_POINTER(ptr) assertNonNullPointer (ptr, __FILE__, __LINE__) ;
#else
  #define XTR_ASSERT_NON_NULL_POINTER(ptr)
#endif


#ifdef ENABLE_XTR_ASSERTIONS
  #define XTR_ASSERT(condition,value) assertion (condition, value, __FILE__, __LINE__) ;
#else
  #define XTR_ASSERT(condition,value)
#endif

// CORTEX-M4 EXCEPTION FRAME (WITHOUT FLOATING-POINT STORAGE)                                                          *
//                                                                                                                     *
//         PSP+32 -> |                            |                                                                    *
//                   |----------------------------| \                                                                  *
//         PSP+28 -> | xPSR                       |  |                                                                 *
//                   |----------------------------|  |                                                                 *
//         PSP+24 -> | PC (after SVC instruction) |  |                                                                 *
//                   |----------------------------|  |                                                                 *
//         PSP+20 -> | LR                         |  |                                                                 *
//                   |----------------------------|  |                                                                 *
//         PSP+16 -> | R12                        |  |  Saved by interrupt response                                    *
//                   |----------------------------|  |                                                                 *
//         PSP+12 -> | R3                         |  |                                                                 *
//                   |----------------------------|  |                                                                 *
//         PSP+8  -> | R2                         |  |                                                                 *
//                   |----------------------------|  |                                                                 *
//         PSP+4  -> | R1                         |  |                                                                 *
//                   |----------------------------|  |                                                                 *
//   /---- PSP ----> | R0                         |  |                                                                 *
//   |               |----------------------------| /                                                                  *
//   |                                                                                                                 *
//   |                                               *---------------------*
//   |                                               | LR return code      | +36 [ 9]
//   |                                               *---------------------*
//   \-----------------------------------------------| R13 (PSP)           | +32 [ 8]
//                                                   *---------------------*
//                                                   | R11                 | +28 [ 7]
//                                                   *---------------------*
//                                                   | R10                 | +24 [ 6]
//                                                   *---------------------*
//                                                   | R9                  | +20 [ 5]
//                                                   *---------------------*
//                                                   | R8                  | +16 [ 4]
//                                                   *---------------------*
//                                                   | R7                  | +12 [ 3]
//                                                   *---------------------*
//                                                   | R6                  | + 8 [ 2]
//                                                   *---------------------*
//                                                   | R5                  | + 4 [ 1]
//  *---------------------------------------*        *---------------------*
//  | var.running.task.control.block.ptr    +------> | R4                  | + 0 [ 0]
//  *---------------------------------------*        *---------------------*
//
//----------------------------------------------------------------------------------------------------------------------

typedef struct {
	uint32_t mR0 ;
	uint32_t mR1 ;
	uint32_t mR2 ;
	uint32_t mR3 ;
	uint32_t mR12 ;
	uint32_t mLR ;
	uint32_t mPC ;
	uint32_t mXPSR ;
} ExceptionFrame_without_floatingPointStorage ;

typedef struct {
	uint32_t mR4 ;
	uint32_t mR5 ;
	uint32_t mR6 ;
	uint32_t mR7 ;
	uint32_t mR8 ;
	uint32_t mR9 ;
	uint32_t mR10 ;
	uint32_t mR11 ;
	ExceptionFrame_without_floatingPointStorage * mPSP ; // R13
	uint32_t mLR_RETURN_CODE ;
} TaskContext ;


static void kernel_set_task_context (INIT_MODE_
		TaskContext & ioTaskContext,
		const uint32_t inStackBufferAddress,
		const uint32_t inStackBufferSize,
		RoutineTaskType inTaskRoutine) {

	// Initialize LR
	ioTaskContext.mLR_RETURN_CODE = 0xFFFFFFFD ;

	// Stack Pointer initial value
	uint32_t initialTopOfStack = inStackBufferAddress + inStackBufferSize ;
	initialTopOfStack -= sizeof (ExceptionFrame_without_floatingPointStorage) ;

	// Initialize SP
	auto ptr = (ExceptionFrame_without_floatingPointStorage *) initialTopOfStack ;
	ioTaskContext.mPSP = ptr ;

	// Initialize PC
	ptr->mPC = (uint32_t) inTaskRoutine ;

	// Initialize CPSR
	ptr->mXPSR = 1 << 24 ; // Thumb bit
}

// T A S K    C O N T R O L    B L O C K                                                                             *

typedef struct TaskControlBlock {

	// Context buffer
	TaskContext mTaskContext ; // SHOULD BE THE FIRST FIELD

	// Task index
	uint8_t mTaskIndex ;
} TaskControlBlock ;


static TaskControlBlock gTaskDescriptorArray [TASK_COUNT] ;


TaskControlBlock * descriptorPointerForTaskIndex (const uint8_t inTaskIndex) {
	XTR_ASSERT (inTaskIndex < TASK_COUNT, inTaskIndex) ;
	return & gTaskDescriptorArray [inTaskIndex] ;
}


uint8_t indexForDescriptorTask (const TaskControlBlock * inTaskPtr) { // should be not nullptr
	XTR_ASSERT_NON_NULL_POINTER (inTaskPtr) ;
	return inTaskPtr->mTaskIndex ;
}

// RUNNING TASK DESCRIPTOR POINTER

TaskControlBlock * gRunningTaskControlBlockPtr asm ("var.running.task.control.block.ptr") ; // Shared with assembly code

// SCHEDULER

static TaskList gReadyTaskList ;


static void kernel_makeTaskReady (IRQ_MODE_ TaskControlBlock * inTaskPtr) {
	XTR_ASSERT_NON_NULL_POINTER (inTaskPtr) ;
	gReadyTaskList.enterTask (MODE_ inTaskPtr) ;
}


void kernelSelectTaskToRun (IRQ_MODE) asm ("kernel.select.task.to.run") ; // Invoked from assembly

void kernelSelectTaskToRun (IRQ_MODE) {
	if (gRunningTaskControlBlockPtr != nullptr) {
		gReadyTaskList.enterTask (MODE_ gRunningTaskControlBlockPtr) ;
	}
	gRunningTaskControlBlockPtr = gReadyTaskList.removeFirstTask (MODE) ;
}

// kernel_createTask

static uint8_t gTaskIndex ;


void kernel_createTask (INIT_MODE_
		uint64_t * inStackBufferAddress,
		uint32_t inStackBufferSize,
		RoutineTaskType inTaskRoutine) {
	XTR_ASSERT (gTaskIndex < TASK_COUNT, gTaskIndex) ;
	TaskControlBlock * taskControlBlockPtr = & gTaskDescriptorArray [gTaskIndex] ;
	taskControlBlockPtr->mTaskIndex = gTaskIndex ;

	// Initialize properties
	// As gTaskDescriptorArray is in bss, all properties are by default initialized to binary 0
	// taskControlBlockPtr->mDeadline = 0 ; // statically initialized to 0
	// taskControlBlockPtr->mUserResult = false ; // statically initialized to false
	// taskControlBlockPtr->mGuardState = GUARD_EVALUATING_OR_OUTSIDE ; // statically initialized to GUARD_EVALUATING_OR_OUTSIDE
	// taskControlBlockPtr->mGuardDescriptor.mCount = 0 ; // statically initialized to 0

	// Initialize Context
	kernel_set_task_context (MODE_
			taskControlBlockPtr->mTaskContext,
			(uint32_t) inStackBufferAddress,
			inStackBufferSize,
			inTaskRoutine) ;

	// Make task ready
	kernel_makeTaskReady (MODE_ taskControlBlockPtr) ;
	gTaskIndex += 1 ;
}

static void kernel_makeNoTaskRunning(KERNEL_MODE) {
	gRunningTaskControlBlockPtr = nullptr; // No running task
}

// Task termination
void service_taskSelfTerminates(KERNEL_MODE) {
	kernel_makeNoTaskRunning(MODE);
}