/*
 * clock.c -- The status bar clock ($Z), cpu saver, and system timers
 *
 * Copyright (c) 1990 Michael Sandroff.
 * Copyright (c) 1991, 1992 Troy Rollo.
 * Copyright (c) 1992-1996 Matthew Green.
 * Copyright 1997, 2003 EPIC Software Labs
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notices, the above paragraph (the one permitting redistribution),
 *    this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the distribution.
 * 3. The names of the author(s) may not be used to endorse or promote 
 *    products derived from this software without specific prior written
 *    permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
 * SUCH DAMAGE.
 */

#include "irc.h"
#include "ircaux.h"
#include "clock.h"
#include "hook.h"
#include "vars.h"
#include "server.h"
#include "status.h"
#include "screen.h"
#include "termx.h"
#include "timer.h"
#include "input.h"
#include "mail.h"
#include "notify.h"
#include "output.h"

/************************************************************************/
/*			SYSTEM CLOCK					*/
/************************************************************************/
/* 
 * XXX This is bogus -- it gets set when you do /set clock_format.
 * The reason we use a global variable is to cut down on the number of
 * calls to get_string_var, which is truly bogus, but neccesary for any
 * semblance of efficiency.
 */
	char		*time_format = (char *) 0;	/* XXX Bogus XXX */
static	const char	*strftime_24hour = "%R";
static	const char	*strftime_12hour = "%I:%M%p";
static	char		current_clock[256];
	char		clock_timeref[] = "CLKTIM";

static void	reset_broken_clock (void)
{
	snprintf(current_clock, sizeof current_clock, 
		"%c12%c:%c00AM%c",
		BLINK_TOG, BLINK_TOG, BLINK_TOG, BLINK_TOG);
}

static void	reset_standard_clock (void)
{
static		int	min = -1;
static		int	hour = -1;
		Timeval	tv;
	struct 	tm	time_val;
		time_t	hideous;

	/* 
	 * Every time /set clock_interval goes off this function calls
	 * localtime() to update $Z.  Localtime() is very expensive, so
	 * if you /set clock_interval too low you will use a lot of cpu.
	 */
	get_time(&tv);
	hideous = tv.tv_sec;
	time_val = *localtime(&hideous);

	if (time_format)	/* XXX Bogus XXX */
		strftime(current_clock, sizeof current_clock, 
				time_format, &time_val);
	else if (get_int_var(CLOCK_24HOUR_VAR))
		strftime(current_clock, sizeof current_clock,
				strftime_24hour, &time_val);
	else
		strftime(current_clock, sizeof current_clock,
				strftime_12hour, &time_val);

	/*
	 * Either way we only update /on timer and /on idle every minute.
	 */
	if ((time_val.tm_min != min) || (time_val.tm_hour != hour))
	{
		int	old_server = from_server;

		hour = time_val.tm_hour;
		min = time_val.tm_min;

		from_server = primary_server;
		do_hook(TIMER_LIST, "%02d:%02d", hour, min);
		do_hook(IDLE_LIST, "%ld", (tv.tv_sec - idle_time.tv_sec) / 60);
		from_server = old_server;
	}
}

static void	reset_metric_clock (void)
{
static	long	last_milliday;
	double	metric_time;
	long	current_milliday;

	get_metric_time(&metric_time);
	current_milliday = (long)metric_time;

	snprintf(current_clock, sizeof current_clock, 
			"%ld", (long)metric_time);

	if (current_milliday != last_milliday)
	{
		int	old_server = from_server;
		int	idle_milliday;

		idle_milliday = (int)(timeval_to_metric(&idle_time).mt_mdays);
		from_server = primary_server;
		do_hook(TIMER_LIST, "%03ld", current_milliday);
		do_hook(IDLE_LIST, "%ld", 
			(long)(idle_milliday - current_milliday));
		from_server = old_server;
		last_milliday = current_milliday;
	}
}

void	reset_clock (void *stuff)
{
	char *	sclock;

	if (x_debug & DEBUG_BROKEN_CLOCK)
		reset_broken_clock();
	else if (get_int_var(METRIC_TIME_VAR))
		reset_metric_clock();
	else
		reset_standard_clock();

	sclock = get_string_var(STATUS_CLOCK_VAR);
	if (sclock && *sclock)
		update_all_status();
}

/* update_clock: figures out the current time and returns it in a nice format */
const char *	get_clock (void)
{
	return current_clock;
}

void	clock_systimer (void)
{
	/* reset_clock does `update_all_status' for us */
	reset_clock(NULL);
}

void    set_clock_interval (void *stuff)
{
	update_system_timer(clock_timeref);	/* XXX Oh heck, why not? */
}

void     set_clock_format (void *stuff)
{
	const VARIABLE *v;
	const char *value;

	v = (const VARIABLE *)stuff;
	value = v->string;
        malloc_strcpy(&time_format, value);
        reset_clock(NULL);
}

void	my_set_clock (void *stuff)
{
	update_system_timer(clock_timeref);
	update_all_status();
}

/************************************************************************/
/*			CPU SAVER WATCHDOG				*/
/************************************************************************/
	int	cpu_saver = 0;

BUILT_IN_KEYBINDING(cpu_saver_on)
{
        cpu_saver = 1;
        update_all_status(); 
}

static const char cpu_saver_timeref[] = "CPUTIM";

/*
 * This is a watchdog timer that checks to see when we're idle enough to 
 * turn on CPU SAVER mode.  The above timers may honor what we do here but
 * they're not required to.
 */
int	cpu_saver_timer (void *schedule_only)
{
	double	interval;
	double	been_idlin;

	if (cpu_saver)
		return 0;

	get_time(&now);
	been_idlin = time_diff(idle_time, now);
	interval = get_int_var(CPU_SAVER_AFTER_VAR) * 60;

	if (interval < 1)
		return 0;

	if (been_idlin > interval)
		cpu_saver_on(0, NULL);
	else
		add_timer(1, cpu_saver_timeref, interval - been_idlin, 
				1, cpu_saver_timer, NULL, NULL, 
				GENERAL_TIMER, -1, 0, 0);
	return 0;
}

void    set_cpu_saver_after (void *stuff)
{
	VARIABLE *v;
	int	value;

	v = (VARIABLE *)stuff;
	value = v->integer;

        if (value == 0)
	{
		/* Remove the watchdog timer only if it is running. */
		if (timer_exists(cpu_saver_timeref))
			remove_timer(cpu_saver_timeref);
	}
	else
                cpu_saver_timer(NULL);
}

void	set_cpu_saver_every (void *stuff)
{
	VARIABLE *v;
	int	value;

	v = (VARIABLE *)stuff;
	value = v->integer;

	if (value < 60)
	{
		say("/SET CPU_SAVER_EVERY must be set to at least 60");
		v->integer = 60;
	}
}

/************************************************************************/
/*			SYSTEM TIMERS					*/
/************************************************************************/
struct system_timer {
	char *	name;
	int	honors_cpu_saver;
	int *	interval_variable;
	int *	toggle_variable;
	Timeval	last_event;
	void	(*callback) (void);
};

struct system_timer system_timers[] = {
	{ clock_timeref, 	1, 
	  &CLOCK_INTERVAL_VAR, 	&CLOCK_VAR, 	{ 0, 0 },
	  clock_systimer 	},
	{ notify_timeref, 	1, 
	  &NOTIFY_INTERVAL_VAR,  &NOTIFY_VAR, 	{ 0, 0 },
	  notify_systimer	},
	{ mail_timeref, 	1, 
	  &MAIL_INTERVAL_VAR, 	&MAIL_VAR, 	{ 0, 0 },
	  mail_systimer 	},
	{ NULL,			0, 
	  0,			0,		{ 0, 0 },
	  NULL 			}
};

/*
 * This is a unified callback function that handles the processing for all
 * of the system timers.  
 * It calculates how long until the callback should be called again:
 *	If CPU SAVER mode is on, then /set CPU_SAVE_EVERY seconds
 *	If it is off, then the next round /SET *_INTERVAL seconds
 *	  where * is "CLOCK", "NOTIFY" or "MAIL".
 * Then it calls the processing callback ("callback") to do the work.
 */
static int	system_timer (void *entry)
{
	double	timeout = 0;
	int	nominal_timeout;
	struct system_timer *item = NULL;

	item = (struct system_timer *)entry;

	if (item->honors_cpu_saver && cpu_saver)
	{
	    nominal_timeout = get_int_var(CPU_SAVER_EVERY_VAR);
	    timeout = nominal_timeout;
	}
	else
	{
	    nominal_timeout = get_int_var(*item->interval_variable);
	    timeout = time_to_next_interval(nominal_timeout);
	}

	add_timer(1, item->name, timeout, 1, system_timer, entry, NULL, 
				GENERAL_TIMER, -1, 0, 0);

	item->callback();
	item->last_event = now;
	return 0;
}

int	update_system_timer (const char *entry)
{
	int	i;
	int	all = 0;

	if (entry == NULL)
		all = 1;

	for (i = 0; system_timers[i].name; i++)
	{
	    if (all == 1 || !strcmp(system_timers[i].name, entry))
	    {
		/* This needs to be set before calling 'system_timer' */
		get_time(&now);

		if (get_int_var(*system_timers[i].toggle_variable) &&
		    get_int_var(*system_timers[i].interval_variable))
			system_timer(&system_timers[i]);
		else
		{
		    if (timer_exists(system_timers[i].name))
			remove_timer(system_timers[i].name);
		}

		if (all == 0)
			return 0;
	    }
	}

	if (all == 1)
		return 0;

	return -1;
}

/**************************************************************************/
/*
 * When cpu saver mode is turned off (by the user pressing a key), then
 * we immediately do all of the system timers and then they will reset
 * themselves to go off as regular.
 */
void	reset_system_timers (void)
{
	cpu_saver = 0;
	update_system_timer(NULL);
	cpu_saver_timer(NULL);
}

