/*      Timed execution routine. Starts a timer and executes a sequence
 *  of commands when expired.
 *
 *  Added by IW0CNB - Feb 1992
 *  'at mm' format added by WG7J - 920805
 *
 *      Added by WA7TAS Oct 1992:
 *
 *         'at k' command
 *
 *         Repeating AT command
 *           - bug fix June 2, 1993
 *           - merged changes into Johan's new code June 14, 1993
 *
 */
#include <time.h>
#ifdef MSDOS
#include <dos.h>
#endif
#include "global.h"
#ifdef ATCMD
#include "timer.h"
#include "proc.h"
#include "cmdparse.h"
#include "socket.h"
#include "commands.h"
  
void atcmd __ARGS((char *command));
void atproc __ARGS((int i,void *p1,void *p2));
char *requote __ARGS((char *s, char *prepend, char *append));
  
/* List of events; We keep note of all timer processes generated by the
 * at command.
 */
struct at_list {
    struct at_list *next;   /* Linked-list pointer */
    struct timer *at_timer;
    char   recur[10];   /* used in recursive 'at' commands*/
    unsigned int id;    /* numerical 'id' of this 'at' */
};
  
#define NULLATLIST  (struct at_list *)0
  
static struct at_list *Head_loe = NULLATLIST;   /* Head of List Of Events */
static int id=1;        /* next 'at' assigned gets this number */
static char DFAR AtErrmsg[] = "Usage:\nat yymmddhhmm <cmd>\nat dhhmm <cmd>\nat hhmm <cmd>\nat mm <cmd>\nat now+hhmm <cmd>\nat k <id num> <id num> ...\n";
  
int
doat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct date *exp_date;
    struct time *exp_time;
    struct timer *t;
  
    char *cp,*dp;
    unsigned int tid;
    int i,notf;
    time_t nowtime;
    unsigned long time1;
    struct tm tm;
    extern struct timer *Timers;
    struct at_list *loe,*pp;    /* List of events */
  
    if(argc < 2){       /* Print list of pending at commands */
        tputs("List of events:\n");
  
        for(t = Timers;t != NULLTIMER;t = t->next){
            if(t->func == (void (*)__ARGS((void*)))atcmd){
                loe=Head_loe;
                while(loe!=NULLATLIST) {
                    if(loe->at_timer == t) break;
                    loe=loe->next;
                }
                time(&nowtime);
                nowtime = (time_t)((uint32)read_timer(t) / 1000L + (unsigned long)nowtime);
                cp = ctime(&nowtime);
                dp = strrchr(t->arg,'|');
                *dp = 0;
                tprintf("At: %.24s - ID: %5u - Command: %s\n",cp,loe->id,t->arg);
                *dp = '|';
#ifdef notdef
		tprintf(" dur %lu, exp %lu; Clk now %lu\n", t->duration, t->expiration, Clock);
#endif
            }
        }
        return 0;
    }
  
    if(argc < 3){
        tputs(AtErrmsg);
        return 1;
    }
  
    if(argv[1][0]=='k') {
        i=2;
        while(i<argc) {
            tid=atoi(argv[i]);
            if(strlen(argv[i])>5 || tid==0 || atol(argv[i])>65535L) {
                tprintf("Invalid ID #.\n");
                return 1;
            }
            loe=Head_loe;
            notf=1;
            pp = NULL;
            while(loe!=NULLATLIST) {
                if(loe->id==tid) {
                    stop_timer(loe->at_timer);
                    free(loe->at_timer->arg);
                    free(loe->at_timer);
                    if(loe == Head_loe) {
                        Head_loe=loe->next;
                    }
                    else {
                        pp->next=loe->next;
                    }
                    free(loe);
                    tprintf("at id: %u--Killed.\n",tid);
                    notf=0;
                    break;
                }
                pp=loe;
                loe=loe->next;
            }
            if(notf) tprintf("  ID %u not found.\n",tid);
            i++;
        }
        return 0;
    }
    exp_date = (struct date *)mallocw(sizeof(struct date));
    exp_time = (struct time *)mallocw(sizeof(struct time));
  
    cp=mallocw(5);
  
    switch(strlen(argv[1])){
        case 10: /* Full date and time given */
            cp[0]=argv[1][0];
            cp[1]=argv[1][1];
            cp[2]='\0';
  
            exp_date->da_year = 1900 + atoi(cp);
            if(exp_date->da_year < 1970) exp_date->da_year += 100; /* 21-st century */
  
            cp[0]=argv[1][2];
            cp[1]=argv[1][3];
            cp[2]='\0';
  
            exp_date->da_mon = (char)atoi(cp);
            if(exp_date->da_mon > 12) goto error;
  
            cp[0]=argv[1][4];
            cp[1]=argv[1][5];
            cp[2]='\0';
  
            exp_date->da_day = (char)atoi(cp);
            if(exp_date->da_day > 31) goto error;
  
            cp[0]=argv[1][6];
            cp[1]=argv[1][7];
            cp[2]='\0';
  
            exp_time->ti_hour = (char)atoi(cp);
            if(exp_time->ti_hour > 23) goto error;
  
            cp[0]=argv[1][8];
            cp[1]=argv[1][9];
            cp[2]='\0';
  
            exp_time->ti_min = (char)atoi(cp);
            if(exp_time->ti_min > 59) goto error;
  
            exp_time->ti_sec = 5;     /* n5knx: assure we're never early */
            exp_time->ti_hund = 0;
  
            time(&nowtime);
            time1 = (unsigned long)dostounix(exp_date,exp_time);
            if(time1 < (unsigned long)nowtime) goto error;
  
            break;
  
        case 5:  /* DOW+time given */
            i = (*argv[1]++ - '0') % 7;  /* dow: 0=Sun, 1=Mon,...,6=Sat, 7=Sun */
            goto hhmm_common;

        case 4:  /* Only time given, so apply current date */
            i=-1;  /* we're sharing code with dow+hhmm case */

        hhmm_common:
            getdate(exp_date);
            cp[0]=argv[1][0];
            cp[1]=argv[1][1];
            cp[2]='\0';
  
            exp_time->ti_hour = (char)atoi(cp);
            if(exp_time->ti_hour > 23) goto error;
  
            cp[0]=argv[1][2];
            cp[1]=argv[1][3];
            cp[2]='\0';
  
            exp_time->ti_min = (char)atoi(cp);
            if(exp_time->ti_min > 59) goto error;
  
            exp_time->ti_sec = 5;     /* n5knx: assure we're never early */
            exp_time->ti_hund = 0;
  
            time(&nowtime);
            time1 = (unsigned long)dostounix(exp_date,exp_time);
            if (i!=-1) {  /* get weekday corresponding to time1 */
                memcpy((void *)&tm, (void *)localtime((time_t *)&time1), sizeof(struct tm));
                argv[1]--;  /* backup to start of whhmm, so resched works later on */
            }

            while(time1 <= (unsigned long)nowtime || (i!=-1 && i!=tm.tm_wday)){ /* Requested time has passed */
                time1 += 86400L;        /* So book him 24hrs later */
                tm.tm_wday = (++tm.tm_wday % 7);
            }
            break;
  
  
        case 2:  /* Only minutes given, so apply current time & date - WG7J */
            time(&nowtime);
            memcpy((void *)&tm, (void *)localtime(&nowtime), sizeof(struct tm));

            i = atoi(argv[1]);
            if(i > 59) goto error;
  
        /* if we're already at or past the minute, do it next hour ! */
            if(tm.tm_min >= i)
                tm.tm_hour++;
            tm.tm_min = (char)i;
  
        /* now adjust this for day boundaries, etc. */
            tm.tm_sec = 5;  /* n5knx: assure we're never early */

            time1 = mktime(&tm);
            break;
  
        case 8:  /* now+hhmm given */
            strncpy(cp,argv[1],4);
            cp[4]='\0';
            if(strcmp(cp,"now+") != 0) goto error;
  
            cp[0]=argv[1][4];
            cp[1]=argv[1][5];
            cp[2]='\0';
  
            time1=(unsigned long)atoi(cp)*3600L;
  
            cp[0]=argv[1][6];
            cp[1]=argv[1][7];
            cp[2]='\0';
  
            time1+=(unsigned long)atoi(cp)*60L;
            time(&nowtime);
            time1+=(unsigned long)nowtime;
            break;
  
        default:
        error:
            tprintf(AtErrmsg);
            free(exp_date);
            free(exp_time);
            free(cp);
            return 1;
  
    } /* switch */
  
    free(cp);
    free(exp_time);
    free(exp_date);
  
#define MAX_DELTA_TIME	(uint32)(MAX_TIME / 1000L)
    if ((int32)(time1 - nowtime) > MAX_DELTA_TIME) {  /* nowtime is good enough */
        tprintf("Time too far in future.\n");
        return 1;
    }
    t=(struct timer *)mallocw(sizeof(struct timer));
  
    set_timer(t,(time1 - /* n5knx: could be stale! (unsigned long)nowtime*/ time(NULL)) * 1000L);
    t->state=TIMER_RUN;
    t->func=(void (*)__ARGS((void*)))atcmd;
    t->arg=(char *)mallocw(strlen(argv[2])+12); /*crh*/
    sprintf(t->arg,"%s|%d",argv[2],id);
  
    /* Add the new timer to the head of List Of Events */
    loe=(struct at_list *)mallocw(sizeof(struct at_list));
/*
 * if timer is recursive, set at_list->recur and tack
 *  the '+' character to the end of the timer argument
 */
    loe->recur[0]=0;
    if(argv[2][strlen(argv[2])-1] == '+') {
        strcpy(loe->recur,argv[1]);
        strcat(t->arg,"+");
    }
    loe->at_timer=t;
    loe->id=id++;
/*
 * start the timer
 */
    start_timer(t);
/*
 * an id of 0 is invalid
 */
    if(id==0) id=1;
    loe->next=Head_loe;
    Head_loe=loe;
  
    return 0;
}
  
struct proc *Aproc;
  
/* Process that actually handles 'at' execution */
void
atproc(int i,void *p1,void *p2)
{
    extern struct cmds DFAR Cmds[];
    char *command;
    struct at_list *loe, *p;
    int recur, id;
    char *pp;
    char *cmd=NULLCHAR;
  
    command =  (char *)p1;
  
    log(-1,"AT command: %s",command);
  
/*
 * check for recursion
 */
    if(command[strlen(command)-1] == '+')
        recur=1;
    else
        recur=0;
/*
 * locate id in command string
 */
    pp = strrchr(command,'|');
    *pp++ = 0;
    id = atoi(pp);
  
    /* Free up memory for expired at commands */
    p=Head_loe;
    loe=Head_loe;
    while(loe != NULLATLIST){
        if(loe->at_timer->state == TIMER_EXPIRE){
            if(loe->id != id) continue; /* is this the proper entry? */
            if(recur) {
                char prefix[sizeof(loe->recur)+4];
                sprintf(prefix, "at %s ", loe->recur);
                cmd=requote(command, prefix, "");
                command[strlen(command)-1]=0;  /* kill the + */
            }
            free(loe->at_timer);    /* Free timer */
            if(loe == Head_loe){
                Head_loe=loe->next;
                p=loe->next;
                free(loe);
                loe=p;
                p=Head_loe;
            } else {
                p->next=loe->next;
                free(loe);
                loe=p->next;
            }
            break;  /* exit while loop */
        } else {    /* Not expired, go on */
            if(loe != Head_loe) p=p->next;
            loe=loe->next;
        }
    }
  
    cmdparse(Cmds,command,NULL);    /* Go with requested command */
  
    if(recur) {
        cmdparse(Cmds,cmd,NULL);
        free (cmd);
    }
  
    free(command);
  
}
  
struct proc *Aproc;
  
/* Function to be called on timer expiration to execute a command */
void
atcmd(command)
char *command;
{
    Aproc = newproc("AT handler",1024,atproc,0,(void *)command,NULL,0);
}

/* Quote and copy string s, to allocated storage, while prefixing any '"'
 * with a backslash.  This enables cmdparse() to rescan the resultant string
 * back into itself.  String s is not modified.  Don't forget to free the
 * return pointer.  N5KNX 1.10n.
 * [Ought to be in cmdparse.c but only needed here (so far).]
 */
char *
requote(char *s, char *prepend, char *append)
{
    register char *result, *p, *q;
    unsigned reslen = strlen(prepend)+strlen(append)+strlen(s)+3;  /* room for: prepend " s " append NUL */

    result = mallocw(reslen);
    strcpy(result,prepend);
    strcat(result,"\"");
    while(s) {
        p = strchr(s, '"');
        if (p) {
            q=mallocw(++reslen);  /* extend result by 1 char */
            strcpy(q,result);
            free(result);
            result = q;
            strncat(result, s, (int)(p-s));
            strcat(result, "\\\"");  /* \ " */
            p++;
        }
        else
            strcat(result, s);
        s=p;
    }
    strcat(result,"\"");
    strcat(result,append);
    return(result);
}

#endif /* ATCMD */
  
