// npx.cpp - Non-Preemptive eXecutive
//           Copyright 1990 by Cnapse
//           Written by: M. de Champlain

#include <stdio.h>
#include <stdlib.h>

#include "npx.hpp"

Task   *running;

// ---- base class StateQ -------------------
class StateQ {
friend class ReadyQ;
    Task      *header;
    taskState  state;
public:
    StateQ(int nTasks, taskState st);
    void Insert(Task *thisTask);
    void Transfer(Task *thisTask);
    ~StateQ( );
};

inline StateQ::StateQ(int nTasks, taskState st) 
     { 
     header = List_Allocate(nTasks, sizeof(Task));
     state  = st;
     }

inline void StateQ::Insert(Task *thisTask)
     {
     thisTask->state = state;
     List_InsertTail(thisTask, header);
     }

inline void StateQ::Transfer(Task *thisTask)
     {
     thisTask->state = state;
     List_Remove(thisTask);
     List_InsertTail(thisTask, header);
     }

inline StateQ::~StateQ() 
     { 
     List_Free( header );
     }

// ---- derived class ReadyQ -------------------

class ReadyQ : StateQ {
public:
    ReadyQ() : StateQ(0, READY) {}
    void GetNextRunning(void);
};

inline void ReadyQ::GetNextRunning(void)
     {
     (running = List_RemoveHead(header))->state = RUNNING;
     }

StateQ *terminatedQ, *suspendedQ;
ReadyQ *readyQ;

Task *Task::Start(void)
    {
    if (stackBase = new word[stackSizeInBytes/2])
        {
        /* establish new task's SP */
        sp = (reg)((word)stackBase + stackSizeInBytes);
        *--sp = (word) taskStartingAddress;  
        --sp;                     /* push bp */
        parent = running;
        self   = this;
        readyQ->Insert(this);
        return self;
        }
    else
        return 0;
    }

Task *Task::Self(void)
    {
    return self;
    }

Task *Task::Parent(void)
    {
    return parent;
    }

void Task::Schedule(void)
    {
    readyQ->GetNextRunning();  // assume at least one task is READY 
    }

extern void ContextSwitch(void);
extern void RunNext(void);
reg *addrRunningTcbSp;

void Task::ReSchedule(void)
    {
    // save the address of the runningTcb's stack ptr for ContextSwitch.
    addrRunningTcbSp = &running->sp; 
    // put the running task in the READY queue
    readyQ->Insert(running);
    Schedule();
    ContextSwitch();
    }

void Task::Terminate(Task *id)
    {
    if ( id->state != TERMINATED )
        {
        delete id->stackBase;
        terminatedQ->Transfer(id);
        delete id;
        if ( id == running )
            {
            Schedule();
            RunNext();
            // should never back here
            }
        }
    }

void Task::Suspend(Task *id)
    {
    if ( id->state != SUSPENDED )
        {
        suspendedQ->Transfer(id);
        if ( id == running )
            {
            // save address of the runningTcb's stack ptr for ContextSwitch.
            addrRunningTcbSp = &running->sp; 
            Schedule();
            ContextSwitch();
            // will come back here after a Resume
            }
        }
    }

void Task::Resume(Task *id)
    {
    if ( id->state == SUSPENDED )
        readyQ->Transfer(id);
    }

extern void StartUpUserTasks(void);

main(void)
    {
    printf("Non-Preemptive eXecutive, Copyright 1990 by Cnapse\n\n");
    terminatedQ = new StateQ(10, TERMINATED );
    suspendedQ  = new StateQ( 0, SUSPENDED  );
    readyQ      = new ReadyQ();

    // Make StartUpUserTasks RUNNING 
    (new Task(StartUpUserTasks, 1024))->Start();
    readyQ->GetNextRunning();
    RunNext();
    }
