/*
 *   Copyright (c) 1998 David Parsons. 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
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``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 DAVID
 *  PARSONS 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.
 */

static const char rcsid[] = "$Id: s.date.c 1.4 00/02/01 22:28:41-00:00 orc $";

/*
 *Date and time:
 *
 *         (5,0)         (17,0) (23,0)
 *           |              |      |
 *           v              v      v
 *           +-------(-)+   +---+  +----+
 *	Date:|       MMM|   |DAY|  |YEAR|
 *	     +-------(+)+   +---+  +----+
 *
 *   +-----> +--+ +---+ +---+
 *   |	Time:|HR|:|MIN|:|SEC|
 *   |	     +--+ +---+ +---+
 *   |
 *   |	[ SET  ]     [TIMEZONE]    [CANCEL]
 *   |
 * (5,4),(10,4),(15,4)
 */

#include <ndialog.h>
#include <dialog.h>
#include <time.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>

static ndObject o_month, o_day, o_year, o_hour, o_minute, o_second, o_apm;

static ListItem months[] = {
    {  "0", "January" },
    {  "1", "February" },
    {  "2", "March" },
    {  "3", "April" },
    {  "4", "May" },
    {  "5", "June" },
    {  "6", "July" },
    {  "7", "August" },
    {  "8", "September" },
    {  "9", "October" },
    { "10", "November" },
    { "11", "December" },
};
#define NRMONTHS	12	/* This could be a magic number, but may
				 * change on other planets Of course
				 * everything else will change on other
				 * planets, too, but this is not likely
				 * to be a problem during my lifetime.
				 */
static ListItem apm[] = {
    { " 0", "AM" },
    { "12", "PM" },
};
#define NRMERIDIAN	2	/* Antimeridian & postmeridian makes two */


static int betterbenumeric(char*);
static int chk_day(ndObject, ndDisplay);
static int chk_year(ndObject, ndDisplay);
static int chk_year(ndObject, ndDisplay);
static int chk_hour(ndObject, ndDisplay);
static int chk_minute(ndObject, ndDisplay);
static int chk_second(ndObject, ndDisplay);
static int chk_date(ndObject, ndDisplay);

static char day[4];
static char year[7];
static char hour[3];
static char min[3];
static char sec[3];

/*
 * newDateMenu() build a date/time menu and populates it from the current date/time
 */
ndObject
newDateMenu()
{
    time_t now;
    struct tm *tick;
    int x;
    ndObject chain;

    time(&now);
    tick = localtime(&now);

    sprintf(day, "%d", tick->tm_mday);
    sprintf(min, "%d", tick->tm_min);
    sprintf(sec, "%d", tick->tm_sec);

    sprintf(year, "%d", tick->tm_year + 1900);	/* wait 'til 2038... */

    /* we want to display the date in 12 hour format, so we tweak
     * the display appropriately */
    if (tick->tm_hour >= 12)
	sprintf(hour, "%d", tick->tm_hour-12);
    else
	sprintf(hour, "%d", tick->tm_hour);


    o_month = newRadioList(0,0,11,1,NRMONTHS,months,0,"Date:",HIGHLIGHT_SELECTED,0,"date.html");
    for (x=0; x < NRMONTHS; x++) {
	/* set the current month to the selected month */
	months[x].selected = (atoi(months[x].id) == tick->tm_mon);
	if (months[x].selected)
	    setObjCursor(o_month, x);
    }

    o_day = newString(18,0,2,2,day, 0, "", chk_day, "date.html");
    o_year = newString(22,0,4,4,year, 0, "", chk_year, "date.html");

    o_hour = newString(0, 4, 2, 2, hour, 0, "Time:", chk_hour, "date.html");
    o_minute = newString(9, 4, 2, 2, min, 0, ":", chk_minute, "date.html");
    o_second = newString(14, 4, 2, 2, sec, 0, ":", chk_second, "date.html");

    o_apm = newRadioList(20, 4, 4, 1, NRMERIDIAN, apm, 0, "", HIGHLIGHT_SELECTED, 0, "date.html");
    /* set the AM/PM flag according to the raw hour */
    setObjCursor(o_apm, (tick->tm_hour >= 12) ? 1 : 0);
    apm[(tick->tm_hour >= 12) ? 1 : 0].selected = 1;

    chain = ObjChain(0, o_month);
    chain = ObjChain(chain, o_day);
    chain = ObjChain(chain, o_year);
    chain = ObjChain(chain, o_hour);
    chain = ObjChain(chain, o_minute);
    chain = ObjChain(chain, o_second);
    chain = ObjChain(chain, o_apm);

    chain = ObjChain(chain, newOKButton(0, "  OK  ", chk_date, "date.html"));
    chain = ObjChain(chain, newCancelButton(1, "CANCEL", 0, "date.html"));

    return chain;
} /* newDateMenu */


/*
 * betterbenumeric() checks to see if a string is numeric or not.  If the string
 * is numeric, it returns the value of that string, otherwise it spits up an
 * error message and returns EOF
 */
static int
betterbenumeric(char* candidate)
{
    int val = 0;

    for (; *candidate; candidate++)
	if (isdigit(*candidate))
	    val = (val * 10) + (*candidate - '0');
	else {
	    Error("This field must be numeric!");
	    return EOF;
	}
    return val;
} /* betterbenumeric */


/*
 * chk_day() does some stupid checking on the day of the month.
 */
static int
chk_day(ndObject obj, ndDisplay display)
{
    int thismonth;	/* the current month (or 0 for january) */
    int thisday;	/* the day we want to set */
    int thisyear;	/* the current year, for checking february */
    int lastvalidday;	/* the last valid day in this month */

    /* a little list of the last day in the month for each month;  this
     * contains 12 records, with magic being done if the month is february
     */
    unsigned int lastday[] = {
	    31,	/* JANUARY */
	    28,	/* FEBRUARY */
	    31,	/* MARCH */
	    30,	/* APRIL */
	    31,	/* MAY */
	    30,	/* JUNE */
	    31,	/* JULY */
	    31,	/* AUGUST */
	    30,	/* SEPTEMBER */
	    31,	/* OCTOBER */
	    30,	/* NOVEMBER */
	    31,	/* DECEMBER */
    } ;

    for (thismonth = 0; thismonth < NRMONTHS; thismonth++)
	if (months[thismonth].selected)
	    break;

    if ((thisday = betterbenumeric((char*)objDataPtr(obj))) == EOF)
	return 0;

    if (thismonth == 1 /* FEBRUARY */) {
	thisyear = atoi(year);

	if ((thisyear % 4) == 0 && ((thisyear % 100) != 0 || (thisyear % 400) == 0))
	    lastvalidday = 29;
	else
	    lastvalidday = 28;
    }
    else if (thismonth < NRMONTHS)
	lastvalidday = lastday[thismonth];
    else
	return 1;

    if (thisday > lastvalidday || thisday == 0) {
	if (thismonth == 1/* FEBRUARY */)
	    Error("%s %d only has %d days.", months[thismonth].item, thisyear, lastvalidday);
	else
	    Error("%s only has %d days.", months[thismonth].item, lastvalidday);
	return 0;
    }
    return 1;
} /* chk_day */


static int
chk_year(ndObject obj, ndDisplay display)
{
    int year = betterbenumeric((char*)objDataPtr(obj));

    if (year != EOF && year < 1900) {
	Error("The year needs to be 1900 or later");
	return 0;
    }
    return (year == EOF) ? 0 : 1;
} /* chk_year */


static int
chk_hour(ndObject obj, ndDisplay display)
{
    int hour = betterbenumeric((char*)objDataPtr(obj));

    if (hour > 12) {
	Error("The hour should be from 1 to 12");
	return 0;
    }
    return (hour == EOF) ? 0 : 1;
} /* chk_hour */


static int
chk_minute(ndObject obj, ndDisplay display)
{
    int minute = betterbenumeric((char*)objDataPtr(obj));

    if (minute >= 60) {
	Error("There are only 60 minutes in a hour\n"
	      "so you need to set it from 0 to 59");
	return 0;
    }
    return (minute == EOF) ? 0 : 1;
} /* chk_minute */


static int
chk_second(ndObject obj, ndDisplay display)
{
    int second = betterbenumeric((char*)objDataPtr(obj));

    if (second > 61) {
	Error("There can only be 60 seconds in a minute\n"
	      "so you need to set it from 0 to 59.");
	return 0;
    }
    return (second == EOF) ? 0 : 1;
} /* chk_second */


static int
chk_date(ndObject obj, ndDisplay display)
{
    struct tm toset;
    struct timeval settime;
    time_t thetime;
    char bfr[200];

    if (chk_day(o_day, display) && chk_year(o_year, display)
				&& chk_hour(o_hour, display)
				&& chk_minute(o_minute, display)
				&& chk_second(o_second, display)) {
	/* set the time, give a message, and return */

	memset(&toset, 0, sizeof toset);
	toset.tm_isdst = -1;
	toset.tm_sec  = atoi(sec);
	toset.tm_min  = atoi(min);
	toset.tm_hour = atoi(hour);
	if (apm[1].selected)
	    toset.tm_hour += 12;
	toset.tm_mday = atoi(day);
	for (toset.tm_mon=0; toset.tm_mon < NRMONTHS; toset.tm_mon++)
	    if (months[toset.tm_mon].selected)
		break;
	toset.tm_year = atoi(year)-1900;

	if ((thetime = mktime(&toset)) == (time_t)-1) {
	    Error("Bogus time!");
	    return 0;
	}
	settime.tv_usec = 0;
	settime.tv_sec = thetime;

	sprintf(bfr, "Set the time to %s %d, %d %02d:%02d:%02d %cM?",
		    months[toset.tm_mon].item,
		    toset.tm_mday,
		    toset.tm_year + 1900,
		    toset.tm_hour % 12,
		    toset.tm_min,
		    toset.tm_sec,
		    (toset.tm_hour >= 12) ? 'P': 'A');

	if (dialog_yesno(0, bfr, -1, -1) != 0)
	    return 0;

	if (settimeofday(&settime, 0) == -1) {
	    Error("Couldn't set the time");
	    return 0;
	}
	return 1;
    }
    return 0;
} /* chk_date */


#if STAGE2
#  define doit setdate
#endif

/*
 * the date setting program 
 */
int
doit(int argc, char **argv)
{
    int rc;
    ndObject chain = newDateMenu();


    rc = MENU(chain, -1, -1, "Set date and time", 0, 0);

    deleteObjChain(chain);

    return (rc == MENU_OK) ? 0 : 1;
}
