/****
 * Example 2: CoData pointers for scheduling selection
 ****/

#define NUMTASKS 4

typedef struct task {
    CoData cd;
    int id;
    int cycles;
    int next;
} task;

main(void) {
    /* CoData structures for tasks */
    task t[NUMTASKS];

    /* First "on" task and "off" task */
    int firston, firstoff;

    /* Next task to execute, and the one to execute after that */
    int nexttask, pending;

    /* CoData pointer for scheduling */
    CoData* pcd;

    int i;

    /* Initialize tasks */
    for (i = 0; i < NUMTASKS; ++i) {
        t[i].id = i+1;
        t[i].cycles = 0;
        t[i].next = i+1;
        CoBegin(&t[i].cd);
    }
    t[0].next = -1;
    t[NUMTASKS-1].next = -1;
    firston = 0;
    firstoff = 1;

    nexttask = 0;
    while(1) {
        /* Keyboard handler */
        costate {
            while(1) {
                waitfor(kbhit());
                switch(getchar()) {
                    /* Stop one of the currently running tasks */
                    case '-':
                        /* Make sure there is an enabled task */
                        if (firston == -1) {
                            printf("All tasks stopped.\n");
                            break;
                        }

                        /* Is this the only one enabled task? */
                        i = firston;
                        firston = t[i].next;

                        /* Add task to front of disabled stack */
                        t[i].next = firstoff;
                        firstoff = i;

                        /*
                         * Was that task scheduled to go next?  If so,
                         * reset the 'next task' id.
                         */
                        if (nexttask == i) nexttask = firston;
                        break;

                    case '+':
                        /* Make sure there is a disabled task */
                        if (firstoff == -1) {
                            printf("All tasks running.\n");
                            break;
                        }

                        /* Is this the only one disabled task? */
                        i = firstoff;
                        firstoff = t[i].next;

                        /* Add task to front of enabled stack */
                        t[i].next = firston;
                        firston = i;
                        break;
                }
            }
        }

        /* Task selection code */
        if (nexttask == -1 && firston == -1)
            /* Nothing enabled, nothing to run */
            continue;
        else if (nexttask == -1)
            nexttask = firston;
        pending = t[nexttask].next;
        pcd = &t[nexttask].cd;
        ++t[nexttask].cycles;

        /* Task execution */
        costate pcd {
            while (1) {
                switch (t[nexttask].id) {
                    // Task 1 and 4's code; just print its name each time
                    case 1:
                    case 4:
                        printf("Task #%d\n", t[nexttask].id);
                        break;

                    // Task 2's code; four parts, yield between each
                    case 2:
                        printf("Task #2, part 1\n");
                        yield;
                        printf("Task #2, part 2\n");
                        yield;
                        printf("Task #2, part 3\n");
                        yield;
                        printf("Task #2, part 4\n");
                        break;

                    // Task 3's code; run once for every 10 cycles of task 1
                    // then run task #1 again immediately (usually #2 would run
                    // next and then #1...).
                    case 3:
                        waitfor(t[0].cycles % 10 == 0);
                        printf("Task #3\n");
                        pending = 0;        /* Named yield to thread #1 */
                        break;
                }
                yield;
            }
        }

        /* Select the next task to run */
        nexttask = pending;
    }
}

