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 = ZCAlloc(sizeof(CJob));
	res->master_task	= master;
	res->job_code		= JOBT_EXE_STR;
	res->flags			= flags;
	res->aux_str		= ZStrNew(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 = ZCAlloc(sizeof(CJob));
	res->master_task	= master; //in case somebody cares
	res->job_code		= JOBT_TEXT_INPUT;
	res->flags			= flags;
	res->aux_str		= ZStrNew(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 $LK,"flags",A="MN:JOBf_WAKE_MASTER"$ and $LK,"message_code",A="MN:MESSAGE_CMD"$.
	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 = ZCAlloc(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 = ZMAllocIdent(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 $LK,"::/Demo/MultiCore/Lock.CC"$
	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 $LK,"message_code",A="MN:MESSAGE_CMD"$.
	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 $LK,"message_code",A="MN:MESSAGE_CMD"$.
	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 $LK,"message_code",A="MN:MESSAGE_CMD"$.
	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 $LK,"PopUp",A="MN: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 $LK,"PopUp",A="MN:PopUp"$ task.
	U8 *buf = StrPrintJoin(NULL, format, argc, argv);
	I64 res;

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

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

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

	return res;
}

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

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

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

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

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

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

U0 XTalk(CTask *task, U8 *format, ...)
{//Sends text to other task. See $LK,"::/Misc/OSTestSuite.CC"$.
	U8 *buf = StrPrintJoin(NULL, format, argc, argv), *st = ZStrNew(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 = ZStrNew(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 $LK,"::/Demo/AcctExample/TOS/TOSDistro.CC"$.
	U8 *buf = StrPrintJoin(NULL, format, argc, argv), *st = ZStrNew(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);
}
