// --------------------------------------------------------------------------
// Citadel: Cron.CPP
//
// Code to deal with cron events.

#include "ctdl.h"
#pragma hdrstop

#include "log.h"
#include "cron.h"
#include "menus.h"
#include "blurbs.h"
#include "cwindows.h"
#include "net.h"        // did_net
#include "miscovl.h"
#include "term.h"


#ifndef NDEBUG
#include "msg.h"
#endif

// --------------------------------------------------------------------------
// Contents
//
// CronC::SetOnEvent		change current event
// CronC::GetEventNum		look up an event
// CronC::Do				called when system is ready to do an event
// CronEventC::CanDo		Can we do this event?
// ListAllEvents			List all events
// GetEventFromUser 		Ask user for an event
// set_event_successtime	Set success time for a particular event
// cron_commands			Sysop Fn: Cron commands
// zap_event				Zap an event out of the cron list
// reset_event				Reset an even so that it has not been done
// done_event				Set event so it seems to have been done
// did_net					Set all events for a node to done
// do_event 				Actualy do this event
// force_event				Force an event to occur


// --------------------------------------------------------------------------
// CronC::SetOnEvent(): Change current event.

void CronC::SetOnEvent(CronEventC *Set)
	{
	for (CronEventListS *cur = Events; cur;
			cur = (CronEventListS *) getNextLL(cur))
		{
		if (&(cur->Event) == Set)
			{
			OnEvent = cur;
			return;
			}
		}

	OnEvent = Events;
	}


// --------------------------------------------------------------------------
// CronC::GetEventNum(): Look up an event.

CronEventListS *CronC::GetEventNum(int Number)
	{
	return ((CronEventListS *) getLLNum(Events, Number));
	}


// --------------------------------------------------------------------------
// CronC::Do(): Called when the system is ready to do an event.

Bool CronC::Do(CronCallerE)
	{
	DEBUGCODE(VerifyMsgTab());

	Bool done = FALSE;

	if (Events && !Pause)
		{
		CronEventListS *StartEvent = OnEvent;

		do
			{
			if (OnEvent->Event.CanDo(forceevent))
				{
				ExecutingEvent = OnEvent;

				repeatevent = !OnEvent->Event.Do() && repeatevent;

				ExecutingEvent = NULL;

				done = TRUE;
				}

			if (!repeatevent &&
					(OnEvent = (CronEventListS *) getNextLL(OnEvent)) == NULL)
				{
				OnEvent = Events;
				}

			} while (!done && OnEvent != StartEvent);
		}

	if (!done)
		{
		DebugOut(getdbmsg(52));

		CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
				CitWindowsMsg(NULL, getmsg(1611));

		Initport();

		if (w)
			{
			destroyCitWindow(w, FALSE);
			}

		return (FALSE);
		}

	doCR();
	return (TRUE);
	}


// --------------------------------------------------------------------------
// CronEventC::CanDo(): Can we do this event?

Bool CronEventC::CanDo(Bool IgnoreTime) const
	{
	if (Zapped)
		{
		return (FALSE);
		}

	const time_t tn = time(NULL);
	const struct tm *tmnow = localtime(&tn);

	return (IgnoreTime ||

			(
			// right time, day, date, or month
			Hours[tmnow->tm_hour] && Days[tmnow->tm_wday] &&
			Dates[tmnow->tm_mday - 1] && Months[tmnow->tm_mon] &&

			// and beyond #REDO_TIME
			((tn - LastSuccess) / 60 >= RedoTime) &&

			// and beyond #RETRY_TIME
			((tn - LastTried) / 60 >= RetryTime)
			)
		);
	}


// --------------------------------------------------------------------------
// ListAllEvents(): List all events.
//
// Notes:
//	This function was borrowed from Acit.

static void ListAllEvents(void)
	{
	doCR();

	discardable *crdd;
	if ((crdd = readData(9)) == NULL)
		{
		mPrintf(getmsg(59));
		return;
		}

	TI()UserControl.SetOutFlag(OUTOK);

	if (Cron.HasEvents())
		{
		termCap(TERM_BOLD);
		mPrintf(getsysmsg(169));
		termCap(TERM_NORMAL);
		doCR();

		const char **crontypes = (const char **) crdd->aux;
		CronEventListS *theEvent;

		for (int i = 1; (theEvent = Cron.GetEventNum(i)) != NULL; i++)
			{
			CronEventC Evt = theEvent->Event;

			char dtstr[20], etstr[20];

			mPrintf(getsysmsg(352), i,

					Cron.IsOnEvent(theEvent) ? getsysmsg(353) : spc,

					Evt.CanDo() ? getsysmsg(354) :
							(Evt.LastAttemptFailed() ?
							getsysmsg(355) : spc),

					crontypes[Evt.GetType()], Evt.GetString(),

					Evt.IsZapped() ? getsysmsg(170) :
							itoa(Evt.GetRedo(), dtstr, 10),

					Evt.IsZapped() ? getsysmsg(170) :
							itoa(Evt.GetRetry(), etstr, 10));

			if (Evt.GetLastSuccess())
				{
				strftime(dtstr, 19, getsysmsg(351), Evt.GetLastSuccess());
				mPrintf(pcts, dtstr);
				}
			else
				{
				mPrintf(getsysmsg(171));
				}

			if (Evt.GetLastTried())
				{
				strftime(dtstr, 19, getsysmsg(351), Evt.GetLastTried());
				mPrintfCR(pcts, dtstr);
				}
			else
				{
				mPrintfCR(getsysmsg(172));
				}
			}

		if (Cron.IsPaused())
			{
			CRmPrintfCR(getsysmsg(173));
			}

		if (!TI()CurrentUser->IsExpert())
			{
			CRmPrintf(getsysmsg(174));
			CRmPrintf(getsysmsg(175));
			CRmPrintfCR(getsysmsg(176));
			}
		}
	else
		{
		mPrintfCR(getsysmsg(79));
		}

	discardData(crdd);
	}


static CronEventC *GetEventFromUser(void)
	{
	int i;

	Bool List;
	do
		{
		List = FALSE;
		i = (int) getNumber(getsysmsg(78), 0L, Cron.GetNumEvents(), 0, TRUE, &List);

		if (List)
			{
			ListAllEvents();
			}
		} while (List);

	if (i)
		{
		return (&(Cron.GetEventNum(i)->Event));
		}
	else
		{
		return (NULL);
		}
	}


// --------------------------------------------------------------------------
// set_event_successtime(): Set success time for a particular event.

static void set_event_successtime(void)
	{
	CronEventC *theEvent = GetEventFromUser();

	if (theEvent)
		{
		Bool First = TRUE;

		struct date dt;
		struct time tm;
		datestruct ds;
		timestruct ts;

		do
			{
			if (!HaveConnectionToUser())
				{
				return;
				}

			if (!First)
				{
				mPrintfCR(getmsg(1080));
				}

			First = FALSE;

			label usrIn;
			if (!GetStringWithBlurb(getmsg(1078), usrIn, LABELSIZE, ns,
					B_DATESET))
				{
				return;
				}

			gdate(usrIn, &ds);
			} while (!ds.Date);

		First = TRUE;

		do
			{
			if (!HaveConnectionToUser())
				{
				return;
				}

			if (!First)
				{
				mPrintfCR(getmsg(1081));
				}

			First = FALSE;

			label usrIn;
			if (!GetStringWithBlurb(getmsg(1079), usrIn, LABELSIZE, ns,
					B_TIMESET))
				{
				return;
				}

			gtime(usrIn, &ts);
			} while (ts.Hour < 0);

		tm.ti_min = ts.Minute;
		tm.ti_hour = ts.Hour;
		tm.ti_sec = ts.Second;
		dt.da_year = ds.Year + 1900;	// i hate dostounix
		dt.da_day = ds.Date;
		dt.da_mon = ds.Month + 1;		// i hate dostounix

		time_t tried = dostounix(&dt, &tm);

		theEvent->SetLastSuccess(tried);
		theEvent->SetLastTried(tried);

		label dtstr;

		strftime(dtstr, LABELSIZE, getmsg(1431), tried);
		CRmPrintf(getmsg(598), dtstr);

		strftime(dtstr, LABELSIZE, getmsg(1432), tried);
		CRmPrintfCR(getmsg(607), dtstr);
		}
	}


// --------------------------------------------------------------------------
// zap_event(): Zap an event out of the cron list.

static void zap_event(void)
	{
	CronEventC *theEvent = GetEventFromUser();

	if (theEvent)
		{
		theEvent->ToggleZap();
		}
	}


// --------------------------------------------------------------------------
// reset_event(): Reset an even so that it has not been done.

static void reset_event(void)
	{
	CronEventC *theEvent = GetEventFromUser();

	if (theEvent)
		{
		theEvent->Reset();
		}
	}


// --------------------------------------------------------------------------
// done_event(): Set event so it seems to have been done.

static void done_event(void)
	{
	CronEventC *theEvent = GetEventFromUser();

	if (theEvent)
		{
		theEvent->SetDone();
		}
	}


// --------------------------------------------------------------------------
// did_net(): Set all events for a node to done.

void did_net(const char *callnode)
	{
	CronEventListS *theEvent;
	const char *alias, *locID, *address;

	const l_slot logslot = nodexists(callnode);
	if (logslot == CERROR)		// extremely unlikely
		{
		alias = locID = address = ns;
		}
	else
		{
		alias = LogTab->GetEntry(logslot)->GetAlias();
		locID = LogTab->GetEntry(logslot)->GetLocID();
		address = makeaddress(alias, locID);
		}


	for (int i = 1; (theEvent = Cron.GetEventNum(i)) != NULL; i++)
		{
		if (theEvent->Event.GetType() == CR_NET && (
				SameString(theEvent->Event.GetString(), callnode) ||
				SameString(theEvent->Event.GetString(), address) ||
				SameString(theEvent->Event.GetString(), alias)))
			{
			theEvent->Event.SetDone();
			}
		}
	}


// --------------------------------------------------------------------------
// force_event(): Force an event to occur.

static void force_event(void)
	{
	CronEventC *theEvent = GetEventFromUser();

	if (theEvent)
		{
		theEvent->Do();
		}
	}


// --------------------------------------------------------------------------
// cron_commands() Sysop Fn: Cron commands

void cron_commands(void)
	{
	int ich;

	SetDoWhat(SYSCRON);

	switch (toupper(ich = iCharNE()))
		{
		case 'A':
			{
			mPrintfCR(getsysmsg(68));

			CronEventListS *theEvent;
			for (int i = 1; (theEvent = Cron.GetEventNum(i)) != NULL; i++)
				{
				theEvent->Event.SetDone();
				}

			CRmPrintfCR(getsysmsg(69));
			break;
			}

		case 'D':
			{
			mPrintfCR(getsysmsg(70));
			done_event();
			break;
			}

		case 'E':
			{
			mPrintfCR(getsysmsg(71));
			Cron.ReadCronCit();
			break;
			}

		case 'F':
			{
			mPrintfCR(getsysmsg(72));
			force_event();
			break;
			}

		case 'L':
			{
			mPrintfCR(getsysmsg(73));
			ListAllEvents();
			break;
			}

		case 'N':
			{
			mPrintfCR(getsysmsg(74));
			Cron.SetOnEvent(GetEventFromUser());
			break;
			}

		case 'P':
			{
			Cron.TogglePause();
			displayOnOff(getsysmsg(75), Cron.IsPaused());
			doCR();
			break;
			}

		case 'R':
			{
			mPrintfCR(getsysmsg(76));
			reset_event();
			break;
			}

		case 'S':
			{
			mPrintfCR(getsysmsg(268));
			set_event_successtime();
			break;
			}

		case 'Z':
			{
			mPrintfCR(getsysmsg(77));
			zap_event();
			break;
			}

		case '?':
			{
			oChar('?');
			showMenu(M_CRON);
			break;
			}

		default:
			{
			BadMenuSelection(ich);
			break;
			}
		}
	}
