U0 JobDel(CJob *tmpc)
{//Free one cmd node.
    Free(tmpc->aux_str);
    Free(tmpc);
}

U0 JobQueueDel(CJob *head)
{
    CJob *tmpc = head->next, *tmpc1;

    while (tmpc != head)
    {
        tmpc1 = tmpc->next;
        QueueRemove(tmpc);
        JobDel(tmpc);
        tmpc = tmpc1;
    }
}

U0 JobCtrlInit(CJobCtrl *ctrl)
{
    QueueInit(&ctrl->next_waiting);
    QueueInit(&ctrl->next_done);
    ctrl->flags = 0;
}

U0 TaskResetAwaitingMessage(CTask *task=NULL)
{//Pop-ups get parent messages so wake-up our pop-ups if we got a message.
    if (!task)
        task = Fs;

    PUSHFD
    CLI
    do
    {
        if (TaskValidate(task))
            LBtr(&task->task_flags, TASKf_AWAITING_MESSAGE);
        else
            break;
    }
    while (task = task->popup_task);
    POPFD
}

CJob *TaskExe(CTask *server, CTask *master, U8 *data, I64 flags)
{//Queueues a request to compile and execute src code text.
    CJob *res;

    if (!data || !TaskValidate(server) || master && !TaskValidate(master) ||
            server->popup_task && !Bt(&server->task_flags, TASKf_FILTER_INPUT))
        return NULL;

    res = SysCAlloc(sizeof(CJob));
    res->master_task    = master;
    res->job_code       = JOBT_EXE_STR;
    res->flags          = flags;
    res->aux_str        = SysStrNew(data);
    res->ctrl           = &server->server_ctrl;

    PUSHFD
    CLI
    while (LBts(&server->server_ctrl.flags, JOBCf_LOCKED))
        PAUSE
    if (!TaskValidate(server))
    {
        LBtr(&server->server_ctrl.flags, JOBCf_LOCKED);
        POPFD
        JobDel(res);
        return NULL;
    }
    else
    {
        LBtr(&server->task_flags, TASKf_IDLE);
        TaskResetAwaitingMessage(server);
        QueueInsert(res, server->server_ctrl.last_waiting);
        LBtr(&server->server_ctrl.flags, JOBCf_LOCKED);
        if (Bt(&flags, JOBf_WAKE_MASTER))
        {
            Suspend(master);
            Yield;
        }
    }
    POPFD

    return res;
}

CJob *TaskText(CTask *server, CTask *master, U8 *data, I64 flags)
{//Post StdIn text to servant task. Tell who the master task is.
    CJob  *res;
    CTask *task;

    if (!data || !TaskValidate(server) || master && !TaskValidate(master) ||
            server->popup_task && !Bt(&server->task_flags, TASKf_FILTER_INPUT))
        return NULL;

    res = SysCAlloc(sizeof(CJob));
    res->master_task    = master; //in case somebody cares
    res->job_code       = JOBT_TEXT_INPUT;
    res->flags          = flags;
    res->aux_str        = SysStrNew(data);

    PUSHFD
    task = server->last_input_filter_task;
    if (Bt(&flags, JOBf_HIGHEST_PRIORITY) || task == server)
    {
        if (task != server)
            TaskWait(server);
        task = Spawn(&InputFilterTask, NULL, "Input Filter",, server);
        CLI
        task->next_input_filter_task = server->next_input_filter_task;
        task->last_input_filter_task = server;
        server->next_input_filter_task = task;
        task->next_input_filter_task->last_input_filter_task = task;
    }
    else
    {
        CLI
        task = server->next_input_filter_task;
    }
    res->ctrl = &task->server_ctrl;

    while (LBts(&task->server_ctrl.flags, JOBCf_LOCKED))
        PAUSE

    if (!TaskValidate(task))
    {
        JobDel(res);
        res = NULL;
    }
    else
    {
        LBtr(&task->task_flags, TASKf_IDLE);
        TaskResetAwaitingMessage(task);
        QueueInsert(res, task->server_ctrl.last_waiting);
        LBtr(&task->server_ctrl.flags, JOBCf_LOCKED);
    }
    POPFD

    return res;
}

CJob *TaskMessage(CTask *_server, CTask *master, I64 message_code, I64 arg1, I64 arg2, I64 flags)
{//Post message to servant task.    Tell who the master task is.
//See flags and message_code.
    CJob  *tmpc1, *tmpc;
    CTask *server = _server;

    if (!TaskValidate(server) || master && !TaskValidate(master)||
            server->popup_task && !Bt(&server->task_flags, TASKf_FILTER_INPUT))
        return NULL;

    tmpc = SysCAlloc(sizeof(CJob));
    tmpc->master_task   = master;
    tmpc->job_code      = JOBT_MESSAGE;
    tmpc->message_code  = AbsI64(message_code); //negative means do a down and up
    tmpc->aux1          = arg1;
    tmpc->aux2          = arg2;
    tmpc->flags         = flags;

    PUSHFD
    if (Bt(&sys_semas[SEMA_RECORD_MACRO], 0) && server != sys_macro_task && message_code == MESSAGE_KEY_DOWN)
    {
        tmpc1 = SysMAllocIdent(tmpc);
        CLI
        QueueInsert(tmpc1, sys_macro_head.last);
    }
    CLI

    while (Bt(&server->task_flags, TASKf_FILTER_INPUT) && !Bt(&flags, JOBf_DONT_FILTER))
        server = server->next_input_filter_task;

    tmpc->ctrl = &server->server_ctrl;

    while (LBts(&server->server_ctrl.flags, JOBCf_LOCKED))
        PAUSE

    if (!TaskValidate(server))
    {
        JobDel(tmpc);
        tmpc = NULL;
    }
    else
    {
        LBtr(&server->task_flags, TASKf_IDLE);
        TaskResetAwaitingMessage(server);
        QueueInsert(tmpc, server->server_ctrl.last_waiting);
        LBtr(&server->server_ctrl.flags, JOBCf_LOCKED);
    }
    POPFD

    if (message_code < 0) //Down-Up
        TaskMessage(_server, master, -message_code + 1, arg1, arg2, flags);

    return tmpc;
}

Bool JobResScan(CJob *request=NULL, I64 *_res=NULL)
{//Check request complete, return with or without.
    CJobCtrl    *ctrl;
    CJob        *tmpc, *tmpc1;

    if (!request || Bt(&request->flags, JOBf_DONE))
    {
        if (!request || request->master_task)
            ctrl = &Fs->server_ctrl;
        else
            ctrl = request->ctrl;

        PUSHFD
        CLI

        while (LBts(&ctrl->flags, JOBCf_LOCKED))
            PAUSE

        tmpc1 = &ctrl->next_done;
        tmpc = tmpc1->next;

        while (tmpc != tmpc1)
        {
            if (!request || request == tmpc)
            {
                QueueRemove(tmpc);
                LBtr(&ctrl->flags, JOBCf_LOCKED);
                POPFD
                if (_res)
                    *_res = tmpc->res;
                JobDel(tmpc);
                return TRUE;
            }
            tmpc = tmpc->next;
        }
        LBtr(&ctrl->flags, JOBCf_LOCKED);
        POPFD
    }
    if (_res)
        *_res = 0;

    return FALSE;
}

I64 JobResGet(CJob *request=NULL)
{//See ::/Demo/MultiCore/Lock.ZC
    I64   res;
    CJob *tmpc1;

    if (!request)
    {
        tmpc1 = &Fs->server_ctrl.next_done;
        while (tmpc1 == tmpc1->next)
        {
            LBts(&Fs->task_flags, TASKf_IDLE);
            Yield;
        }
    }
    else
    {
        while (!Bt(&request->flags, JOBf_DONE)) {
            LBts(&Fs->task_flags, TASKf_IDLE);
            Yield;
        }
    }
    LBtr(&Fs->task_flags, TASKf_IDLE);
//Could get taken by someone else.
    JobResScan(request, &res);

    return res;
}

U0 TaskWait(CTask *task=NULL, Bool cmd_line_prompt=FALSE)
{//Wait for idle.
    CTask *task1;
    CJob  *tmpc1;

    if (!task)
        task = Fs;
    if (TaskValidate(task))
    {
        PUSHFD
        CLI
        while (TRUE)
        {
            task1 = task->last_input_filter_task;
            tmpc1 = &task1->server_ctrl.next_waiting;
            if (task1 == Fs || !TaskValidate(task1) ||
                    tmpc1 == tmpc1->next && Bt(&task1->task_flags, TASKf_IDLE) &&
                    (!cmd_line_prompt || Bt(&task1->task_flags, TASKf_CMD_LINE_PROMPT)))
                break;
            Yield;
        }
        POPFD
    }
}

U0 MessagePost(CTask *task, I64 message_code, I64 arg1, I64 arg2, I64 flags=0)
{//Post message to a task and return immediately.  See message_code.
    if (TaskValidate(task))
    {
        if (Bt(&task->task_flags, TASKf_INPUT_FILTER_TASK))
            TaskMessage(task->last_input_filter_task, NULL, message_code, arg1, arg2, flags | 1 << JOBf_DONT_FILTER);
        else
            TaskMessage(task, NULL, message_code, arg1, arg2, flags);
    }
}

U0 MessagePostWait(CTask *task, I64 message_code, I64 arg1, I64 arg2, I64 flags=0)
{//Post message to a task and wait until task is idle.See message_code.
    MessagePost(task, message_code, arg1, arg2, flags);
    TaskWait(task);
}

U0 Message(I64 message_code, I64 arg1, I64 arg2, I64 flags=0)
{//Post message to current task and return immediately. See message_code.
    MessagePost(Fs, message_code, arg1, arg2, flags);
}

#define JOB_DONE                0
#define JOB_CONT                1
#define JOB_EXIT                2

I64 JobRunOne(I64 run_flags, CJobCtrl *ctrl)
{//Called with ctrl->flags,JOBCf_LOCKED.
    CJob  *tmpc = ctrl->next_waiting;
    CTask *master;
    I64    res, flags = tmpc->flags, old_flags = RFlagsGet;

    if (Bt(&flags, JOBf_EXIT_ON_COMPLETE))
        res = JOB_EXIT;
    else
        res = JOB_CONT;
    switch (tmpc->job_code)
    {
        case JOBT_SPAWN_TASK:
            QueueRemove(tmpc);
            LBts(&tmpc->flags, JOBf_DISPATCHED);
            LBtr(&ctrl->flags, JOBCf_LOCKED);
            if (tmpc->aux_str)
                tmpc->spawned_task = Spawn(tmpc->addr, tmpc->fun_arg, tmpc->aux_str,, tmpc->aux1, tmpc->aux2, tmpc->flags);
            else
                tmpc->spawned_task = Spawn(tmpc->addr, tmpc->fun_arg, "Unnamed",, tmpc->aux1, tmpc->aux2, tmpc->flags);
            break;

        case JOBT_CALL:
            QueueRemove(tmpc);
            LBts(&tmpc->flags, JOBf_DISPATCHED);
            LBtr(&ctrl->flags, JOBCf_LOCKED);
            RFlagsSet(run_flags);
            LBtr(&Fs->task_flags, TASKf_IDLE);
            try
                tmpc->res = (*tmpc->addr)(tmpc->fun_arg);
            catch
                Fs->catch_except = TRUE;
            RFlagsSet(old_flags);
            break;

        case JOBT_EXE_STR:
            QueueRemove(tmpc);
            LBts(&tmpc->flags, JOBf_DISPATCHED);
            LBtr(&ctrl->flags, JOBCf_LOCKED);
            RFlagsSet(run_flags);
            LBtr(&Fs->task_flags, TASKf_IDLE);
            try
                tmpc->res = ExePrint("%s", tmpc->aux_str);
            catch
                Fs->catch_except = TRUE;
            RFlagsSet(old_flags);
            break;

        default:
            res = JOB_DONE;
    }
    if (res)
    {
        if (master = tmpc->master_task)
        {
            if (!Bt(&flags, JOBf_FREE_ON_COMPLETE))
            {
                CLI

                while (LBts(&master->server_ctrl.flags, JOBCf_LOCKED))
                    PAUSE

                QueueInsert(tmpc, master->server_ctrl.last_done);
                LBts(&tmpc->flags, JOBf_DONE);
                LBtr(&master->server_ctrl.flags, JOBCf_LOCKED);
                RFlagsSet(old_flags);
            }
            if (Bt(&flags, JOBf_FOCUS_MASTER) && !Bt(&master->win_inhibit, WIf_SELF_FOCUS))
                sys_focus_task = master;
            if (Bt(&flags, JOBf_WAKE_MASTER))
                Suspend(master, FALSE);
        }
        if (Bt(&flags, JOBf_FREE_ON_COMPLETE))
            JobDel(tmpc);
        else if (!master)
        {
            CLI

            while (LBts(&ctrl->flags, JOBCf_LOCKED))
                Yield;

            QueueInsert(tmpc, ctrl->last_done);
            LBts(&tmpc->flags, JOBf_DONE);
            LBtr(&ctrl->flags, JOBCf_LOCKED);
            RFlagsSet(old_flags);
        }
    }

    return res;
}

I64 JobsHandler(I64 run_flags, CTask *task=NULL)
{//Handle all waiting cmds and return.
    I64 count = 0, old_flags = RFlagsGet;

    if (!task)
        task = Fs;
    while (TRUE)
    {
        CLI

        while (LBts(&task->server_ctrl.flags, JOBCf_LOCKED))
            PAUSE

        if (task->server_ctrl.next_waiting != &task->server_ctrl)
            switch (JobRunOne(run_flags, &task->server_ctrl))
            {
                case JOB_CONT:
                    count++;
                    break;

                case JOB_EXIT:
                    Exit;

                case JOB_DONE:
                    goto jh_done;
            }
        else
            goto jh_done;
    }
jh_done:
    LBtr(&task->server_ctrl.flags, JOBCf_LOCKED);
    RFlagsSet(old_flags);
    return count;
}

I64 PopUp(U8 *buf, CTask *parent=NULL, CTask **_pu_task=NULL)
{//Execute code in PopUp task.
    I64    res;
    CJob  *tmpc;
    CTask *task = Spawn(&ServerCmdLine, NULL, "Server",, parent);

    if (!parent)
    {
        TaskExe(task, parent, buf, 1 << JOBf_EXIT_ON_COMPLETE | 1 << JOBf_FREE_ON_COMPLETE);
        if (_pu_task)
            *_pu_task = task;
        return 0;
    }
    else
    {
        Fs->popup_task = task;
        tmpc = TaskExe(task, parent, buf, 1 << JOBf_WAKE_MASTER | 1 << JOBf_FOCUS_MASTER);
        if (_pu_task)
            *_pu_task = task;
        JobResScan(tmpc, &res);
        Fs->popup_task = NULL;
        Kill(task);
        if (_pu_task)
            *_pu_task = NULL;
        return res;
    }
}

I64 PopUpPrint(U8 *format, ...)
{//Execute code in PopUp task.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv);
    I64 res;

    res = PopUp(buf, Fs);
    Free(buf);
    return res;
}

I64 Sys(U8 *format, ...)
{//Make sys_task execute code.
    I64   res;
    U8   *buf = StrPrintJoin(NULL, format, argc, argv);
    CJob *tmpc;

    if (Fs  == sys_task)
    {
        tmpc = TaskExe(sys_task, Fs, buf, 0);
        JobsHandler(RFlagsGet);
    }
    else
    {
        TaskWait(sys_task);
        tmpc = TaskExe(sys_task, Fs, buf, 1 << JOBf_WAKE_MASTER);
    }
    JobResScan(tmpc, &res);
    Free(buf);

    return res;
}

U0 SysLog(U8 *format, ...)
{//Display text in sys_task.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv);

    if (Fs == sys_task)
        "%s", buf;
    else if (!IsSingleUser)
        Sys("\"%%s\",%d;", buf);
    Free(buf);
}

U0 SysWarn(U8 *format, ...)
{//Display pink blinking Warn text in sys_task.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv), *st = MStrPrint(ST_WARN_ST "%s", buf);

    if (Fs == sys_task)
        "%s", st;
    else if (!IsSingleUser)
        Sys("\"%%s\",%d;", st);
    Free(st);
    Free(buf);
}

U0 SysErr(U8 *format, ...)
{//Display red blinking Err text in sys_task.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv), *st = MStrPrint(ST_ERR_ST "%s", buf);

    if (Fs == sys_task)
        "%s", st;
    else if (!IsSingleUser)
        Sys("\"%%s\",%d;", st);
    Free(st);
    Free(buf);
}

U0 XTalk(CTask *task, U8 *format, ...)
{//Sends text to other task. See ::/Misc/OSTestSuite.ZC.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv), *st = SysStrNew(buf), *st2 = MStrPrint("\"%%s\",%d;Free(%d);", st, st);

    TaskText(task, NULL, st2, 0);
    Free(st2);
    Free(buf);
}

U0 XTalkWait(CTask *task, U8 *format, ...)
{//Send text to other task and wait for it to idle.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv), *st = SysStrNew(buf), *st2 = MStrPrint("\"%%s\",%d;Free(%d);", st, st);

    TaskText(task, NULL, st2, 0);
    Free(st2);
    Free(buf);
    TaskWait(task);
}

U0 InStr(U8 *format, ...)
{//Send InFile code to self.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv);

    if (Bt(&Fs->task_flags, TASKf_INPUT_FILTER_TASK))
        ExePrint("%s", buf);
    else
        TaskText(Fs, NULL, buf, 1 << JOBf_HIGHEST_PRIORITY);
    Free(buf);
}

U0 InFile(U8 *filename)
{//Send InFile code file to self.
    U8 *name = ExtDefault(filename, "IN");

    InStr("Cd(\"%C:%s\");;#include \"%s\"", Drive2Letter(Fs->cur_dv), Fs->cur_dir, name);
    Free(name);
}

U0 In(U8 *format, ...)
{//Send text to own input buffer. See ::/Demo/AcctExample/TOS/TOSDistro.ZC.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv), *st = SysStrNew(buf);

    InStr("\"%%s\",%d;Free(%d);", st, st);
    Free(buf);
}

U0 XTalkStr(CTask *task, U8 *format, ...)
{//Send InFile code to other task.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv);

    TaskText(task, NULL, buf, 0);
    Free(buf);
}

U0 XTalkStrWait(CTask *task, U8 *format, ...)
{//Send InFile code to other task and wait for it to idle.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv);

    TaskText(task, NULL, buf, 0);
    Free(buf);
    TaskWait(task);
}