///
/// @file micron.c
/// @authors k. Headley
/// @date 05 dec 2019

/// Lightweight cron-like scheduler

/// @sa doxygen-examples.c for more examples of Doxygen markup


/////////////////////////
// Terms of use
/////////////////////////
/*
 Copyright Information

 Copyright 2002-2019 MBARI
 Monterey Bay Aquarium Research Institute, all rights reserved.

 Terms of Use

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version. You can access the GPLv3 license at
 http://www.gnu.org/licenses/gpl-3.0.html

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details
 (http://www.gnu.org/licenses/gpl-3.0.html)

 MBARI provides the documentation and software code "as is", with no warranty,
 express or implied, as to the software, title, non-infringement of third party
 rights, merchantability, or fitness for any particular purpose, the accuracy of
 the code, or the performance or results which you may obtain from its use. You
 assume the entire risk associated with use of the code, and you agree to be
 responsible for the entire cost of repair or servicing of the program with
 which you are using the code.

 In no event shall MBARI be liable for any damages, whether general, special,
 incidental or consequential damages, arising out of your use of the software,
 including, but not limited to, the loss or corruption of your data or damages
 of any kind resulting from use of the software, any prohibited use, or your
 inability to use the software. You agree to defend, indemnify and hold harmless
 MBARI and its officers, directors, and employees against any claim, loss,
 liability or expense, including attorneys' fees, resulting from loss of or
 damage to property or the injury to or death of any person arising out of the
 use of the software.

 The MBARI software is provided without obligation on the part of the
 Monterey Bay Aquarium Research Institute to assist in its use, correction,
 modification, or enhancement.

 MBARI assumes no responsibility or liability for any third party and/or
 commercial software required for the database or applications. Licensee agrees
 to obtain and maintain valid licenses for any additional third party software
 required.
 */

/////////////////////////
// Headers
/////////////////////////
#include "micron.h"

#include <string.h>
//#include "proto.h"

#include "timers.h"
#include "ioserial.h"
#include "iodigital.h"
#include "iowfile.h"
//#include "licor8xx.h"
//#include "licor8xx-shared.h"
#include "ui.h"
#include "log.h"
//#include "z85.h"
#include "sequence.h"
#include "fileutils.h"



/////////////////////////
// Macros
/////////////////////////

// These macros should only be defined for
// application main files rather than general C files
/*
/// @def PRODUCT
/// @brief header software product name
#define PRODUCT "carbonwg"

/// @def COPYRIGHT
/// @brief header software copyright info
#define COPYRIGHT "Copyright 2002-2019 MBARI Monterey Bay Aquarium Research Institute, all rights reserved."
/// @def NOWARRANTY
/// @brief header software terms of use
#define NOWARRANTY  \
"This program is distributed in the hope that it will be useful,\n"\
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"\
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"\
"GNU General Public License for more details (http://www.gnu.org/licenses/gpl-3.0.html)\n"
*/

#define CRON_WAKE_MARGIN_MSEC 3000UL


/////////////////////////
// Declarations
/////////////////////////

static char *s_trim_str(char *src);

/////////////////////////
// Imports
/////////////////////////

/////////////////////////
// Module Global Variables
/////////////////////////

#if CRON_LINE_BUF_BYTES > 0
    char cron_line_buf[CRON_LINE_BUF_BYTES];
#endif

cron_task_t usr_tasks[]={
    {TSK_INVALID,0,{0},{0},SCR_HND_INVALID},
    {TSK_INVALID,0,{0},{0},SCR_HND_INVALID},
    {TSK_INVALID,0,{0},{0},SCR_HND_INVALID},
    {TSK_INVALID,0,{0},{0},SCR_HND_INVALID},
    {TSK_LIST_END,0,{0},{0},SCR_HND_INVALID}
};
cron_task_t sys_tasks[]={
    {TSK_INVALID,0,{0},{0},SCR_HND_INVALID,{0}},
    {TSK_INVALID,0,{0},{0},SCR_HND_INVALID,{0}},
    {TSK_INVALID,0,{0},{0},SCR_HND_INVALID,{0}},
    {TSK_INVALID,0,{0},{0},SCR_HND_INVALID,{0}},
    {TSK_LIST_END,0,{0},{0},SCR_HND_INVALID,{0}}
};

cron_tab_t cron_dfl_tabs[]={
    {SYS_CRONTAB_STR,TABID_SYS,TAB_MASK_NONE,0,SYS_CRONTAB_DFL,sys_tasks,NULL,{0}},
    {USR_CRONTAB_STR,TABID_USR,TAB_MASK_NONE,0,USR_CRONTAB_DFL,usr_tasks,NULL,{0}},
    {NULL,TABID_INVALID,TAB_MASK_NONE,0,{0},NULL,NULL,{0}}
};

cron_instance_t cron_instances[]={
    {cron_dfl_tabs,TABID_COUNT,true,CST_INVALID}
};

/////////////////////////
// Function Definitions
/////////////////////////

// remove leading, trailing space, comments
static char *s_trim_str(char *src)
{
    char *retval=NULL;
//    OUT_CTTY("%s - in [%s]\r\n",__func__,src);
   if(NULL!=src){

        // strip trailing whitespace
        char *sp=src+strlen(src)-1;
        while(sp>src && SYS_ISSPC(*sp)){
            *sp='\0';
            sp--;
        }
        // strip leading whitespace
        sp=src;
        while(SYS_ISSPC(*sp))sp++;

        char *head=sp;

        // strip comments
        if( (sp=strchr(head,'#'))!=NULL){
            *sp='\0';
        }else if((sp=strstr(head,"//"))!=NULL){
            *sp='\0';
        }
        retval=head;
//        OUT_CTTY("%s - out [%s]\r\n",__func__,retval);
    }
        return retval;
}

char *micron_qtrim_str(char *src)
{
    char *retval=NULL;
    // remove leading/trailing space and quotes from a string

//    OUT_CTTY("%s - in [%s]\r\n",__func__,src);
    if(NULL!=src){

        // strip trailing whitespace/quotes
        char *sp=src+strlen(src)-1;
        while(sp>src && ( SYS_ISSPC(*sp) || SYS_ISQUOTE(*sp))){
            *sp='\0';
            sp--;
        }
        // strip leading whitespace/quotes
        sp=src;
        while(SYS_ISSPC(*sp) || SYS_ISQUOTE(*sp))sp++;
        // point to first char after space/quotes
        if(strlen(sp)>0){
            retval=sp;
        }
//        OUT_CTTY("%s - out [%s]\r\n",__func__,retval);
    }
        return retval;
}


void micron_task_show(cron_task_t *self, int indent)
{
    int wkey=10;
    int wval=16;
    OUT_CTTY("%*s%*s %*s\r\n",indent,"",wkey,"scr",wval,self->scr);
    OUT_CTTY("%*s%*s %*d\r\n",indent,"",wkey,"hnd",wval,self->hnd);
    OUT_CTTY("%*s%*s %*d\r\n",indent,"",wkey,"state",wval,self->state);
    OUT_CTTY("%*s%*s %*d\r\n",indent,"",wkey,"req_n",wval,self->req_n);
    char tstr[32]={0};
    sprintf(tstr,"{%c,%lu,%lu}",BOOLCHAR(self->run_timer.active),self->run_timer.ticksLeft,self->run_timer.lastRead);
    OUT_CTTY("%*s%*s %*s\r\n",indent,"",wkey,"tmr",wval,tstr);
    sched_r_show(self->sch,indent);
}

void micron_tab_show(cron_tab_t *self, int indent)
{
    int wkey=10;
    int wval=10;
    OUT_CTTY("%*s%*s %*s\r\n",indent,"",wkey,"name",wval,self->name);
    OUT_CTTY("%*s%*s %*d\r\n",indent,"",wkey,"id",wval,self->id);
    OUT_CTTY("%*s%*s %*s\r\n",indent,"",wkey,"path",wval,self->tab_path);
    OUT_CTTY("%*s%*s %*X\r\n",indent,"",wkey,"flags",wval,self->flags);
    OUT_CTTY("%*s%*s %*p\r\n",indent,"",wkey,"fp",wval,self->fp);
    OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"n_tasks",wval,self->n_tasks);
    cron_task_t *task = self->tasks;
    int i=0;
    while(NULL!=task && i<self->n_tasks){
        OUT_CTTY("%*s--- task[%d/%d]\r\n",indent,"",i+1,self->n_tasks);
        micron_task_show(task++,indent+3);
        i++;
    }
}

void micron_show(cron_instance_t *self, int indent)
{
    if(NULL!=self){
        int wkey=10;
        int wval=10;
        OUT_CTTY("%*s%*s %*c\r\n",indent,"",wkey,"enabled",wval,BOOLCHAR(self->enabled));
        OUT_CTTY("%*s%*s %*d\r\n",indent,"",wkey,"len",wval,self->len);
        OUT_CTTY("%*s%*s %*d\r\n",indent,"",wkey,"state",wval,self->state);
        char tstr[32]={0};
        sprintf(tstr,"{%c,%lu,%lu}",BOOLCHAR(self->suspend_timer.active),self->suspend_timer.ticksLeft,self->suspend_timer.lastRead);
        OUT_CTTY("%*s%*s %*s\r\n",indent,"",wkey,"stmr",wval,tstr);
        cron_tab_t *next=self->tabs;
        int i=0;
        while(next!=NULL && next->id!=TABID_INVALID){
           OUT_CTTY("%*s--- tab[%d]\r\n",indent,"",i++);
            micron_tab_show(next,indent+2);
             next++;
        }
    }
}

cron_instance_t *micron_get_instance(cron_hnd_t hnd)
{
    cron_instance_t *retval=NULL;
    if(CRON_HND_ISVALID(hnd))
        retval=&cron_instances[hnd];
    return retval;
}

int micron_parse_cron(char **psch, char **pscr, char *src, char *del)
{
    int retval=-1;
    if(NULL!=src && NULL!=psch && NULL!=pscr){
        char *toks[2]={NULL,NULL};
        // tokenize breaks into two tokens at '='
        int ntoks=tokenize(src, toks,(NULL!=del?del:"="),2);
        if(ntoks==2){
            // expecting "<schedule>" = <script>
            // trim and remove quotes
            toks[0]=s_trim_str(toks[0]);
            toks[1]=s_trim_str(toks[1]);
            *psch = micron_qtrim_str(toks[0]);
            *pscr = micron_qtrim_str(toks[1]);
            if(NULL!=*psch && NULL!=*psch &&
                    strlen(*psch)>0 && strlen(*pscr)>0){
                retval=0;
            }
        }
     }
    return retval;
}

int micron_set_enable(cron_hnd_t hnd, bool enable)
{
    int retval=-1;
    cron_instance_t *self=micron_get_instance(hnd);
    if(NULL!=self){
        self->enabled=enable;
        retval=0;
    }
    return retval;
}

bool micron_get_enable(cron_hnd_t hnd)
{
    bool retval=-1;
    cron_instance_t *self=micron_get_instance(hnd);
    if(NULL!=self){
        retval=self->enabled;
    }
    return retval;
}

int micron_suspend(cron_hnd_t hnd, uint32_t period_msec)
{
    int retval=-1;
    cron_instance_t *self=micron_get_instance(hnd);
    if(NULL!=self){
        micron_set_enable(hnd,false);
        // deactivate timer (indefinite default)
        tkTmrReset(&self->suspend_timer);
        if(period_msec>0){
            // start timer if period>0
            tkTmrSet(&self->suspend_timer,period_msec);
            tkTmrStart(&self->suspend_timer);
        }
        retval=0;
    }
    return retval;
}

bool micron_suspended(cron_hnd_t hnd, uint32_t *r_stimer)
{
    bool retval = false;
    cron_instance_t *self=micron_get_instance(hnd);
    if(NULL!=self){
        if(self->suspend_timer.active){
            retval=true;
            if(NULL!=r_stimer){
                *r_stimer=tkTmrUpdate(&self->suspend_timer);
            }
        }
    }
    return retval;

}

int micron_resume(cron_hnd_t hnd)
{
    int retval=-1;
    cron_instance_t *self=micron_get_instance(hnd);
    if(NULL!=self){
        micron_set_enable(hnd,true);
        tkTmrReset(&self->suspend_timer);

        // update task timers so they don't fire immediately
        cron_tab_t *tab = self->tabs;
        while(NULL!=tab && tab->name!=NULL){
            cron_task_t *task=tab->tasks;
            while(task->state!=TSK_LIST_END){
                if(task->state!=TSK_INVALID){
                    micron_task_reset_timer(task);
                }
                task++;
            }
            tab++;
        }

        retval=0;
    }
    return retval;
}

int micron_init(cron_instance_t *self)
{
    int retval=0;

    if(NULL!=self){
        // set state invalid
        self->state=CST_INVALID;
        // close/invalidate tab file pointers
        cron_tab_t *next=self->tabs;
        while(next!=NULL && next->id!=TABID_INVALID){
            // init tab, use default tab name
            micron_tab_init(next,NULL);
            next++;
        }
        self->tt_last=0;
        self->state=CST_READY;
    }

    return retval;
}

// cron state machine
// Each call advances state.

int micron_state_update(cron_hnd_t hnd)
{
    int retval=-1;

    cron_instance_t *self=micron_get_instance(hnd);

    if(NULL!=self){
        if(self->enabled){
            if(self->state==CST_INVALID){
                micron_init(self);
                self->state=CST_READY;
            }else if(self->state==CST_READY){

                 Time now_s={0},*now=&now_s;
                 time_t tt_now=rtc_mktime(now);
                // get number of tabs ready to run
                if(tt_now!=self->tt_last){
                    if(micron_update_tabs(hnd,now)==0){
                        retval=0;
                    }else{
                        TRACE();
                    }
                }
                self->tt_last=tt_now;

                self->state=CST_READY;
            }
        }else{
            if(self->suspend_timer.active){
                // if the suspend timer is running
                if(tkTmrUpdate(&self->suspend_timer)==0){
                    // resume when timer expired
                    micron_resume(hnd);
                }// else still suspended
            }
        }
    }
    return retval;
}

cron_tab_t *micron_tab_slookup(cron_hnd_t hnd, char *key)
{
    cron_tab_t *retval=NULL;
    cron_instance_t *self=micron_get_instance(hnd);
    if(NULL!=self){
    cron_tab_t *next=self->tabs;
        while(next->id!=TABID_INVALID){
            if(strcmp(next->name,key)==0){
                retval=next;
                break;
            }
            next++;
        }
    }
    return retval;
}

cron_tab_t *micron_tab_ilookup(cron_hnd_t hnd, cron_tab_id id)
{
    cron_tab_t *retval=NULL;
    cron_instance_t *self=micron_get_instance(hnd);
    if(NULL!=self){
    cron_tab_t *next=self->tabs;
        while(next->id!=TABID_INVALID){
            if(next->id==id){
                retval=next;
                break;
            }
            next++;
        }
    }
    return retval;
}

int micron_tab_stat(cron_hnd_t hnd, cron_tab_id id,int indent)
{
    int retval=-1;
    cron_instance_t *self=micron_get_instance(hnd);
     if(NULL!=self){
        // iterate over tabs
        cron_tab_t *next = self->tabs;
        while(NULL!=next && next->name!=NULL){
            if(id<0 || next->id==id){
                OUT_CTTY("--- crontab[%s] ---\r\n",next->tab_path);
                micron_tab_show(next,indent);
            }
            next++;
        }
        retval=0;
     }else{ TRACE(); }
    return retval;
}

int micron_tab_list(cron_hnd_t hnd, cron_tab_id id)
{
    int retval=-1;
    cron_instance_t *self=micron_get_instance(hnd);
     if(NULL!=self){
        // iterate over tabs
        cron_tab_t *next = self->tabs;
        int err_count=0;
            while(NULL!=next && next->name!=NULL){
                if(id<0 || next->id==id){

                    xcat_config_t cfg_s={
                        XCAT_MODE_LINES,
                        XCAT_MODE_LINES,
                        NULL,
                        NULL,
                        -1,
                        {0,0},
                        {0,0}
                    },*xc_cfg=&cfg_s;

                    OUT_CTTY("--- crontab[%s] ---\r\n",next->tab_path);
                    if(xcat(next->tab_path,xc_cfg)!=0){
                        OUT_CTTY("xcat[%s] returned error\r\n",next->tab_path);
                        err_count++;
                    }
                }
                next++;
            }
    }
    return retval;
}


int micron_tab_init(cron_tab_t *self, char *tab_path)
{
    int retval=0;

    if(NULL!=self && NULL!=self->tab_path ){
        self->flags &= ~(TAB_CTRL_EN);

        if(NULL!=self->fp)
            iow_fclose(self->fp);
        self->fp=NULL;

        if(NULL!=tab_path){
            size_t plen = strlen(tab_path);
            if(plen>0 && plen<CRON_PATH_LEN){
                sprintf(self->tab_path,"%s",tab_path);
            }else{
                EPRINT("tab path [%s] invalid len[%"FMT_SZ"] - max %d bytes\r\n",tab_path,plen,(CRON_PATH_LEN-1));
            }
        }
        cron_task_t *task=self->tasks;
        while(task->state!=TSK_LIST_END){
            micron_task_init(task);
            task++;
        }
        self->n_tasks=0;

#if CRON_LINE_BUF_BYTES <= 0
       char linebuf[80];
       size_t read_len=80;
#else
        char *line_buf=cron_line_buf;
        size_t read_len=CRON_LINE_BUF_BYTES;
#endif

        // re-read tab file
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
        FILE_T file_s, *fp=&file_s;
#else
        FILE_T *fp;
#endif
        if(strlen(self->tab_path)>0 && iow_fopen(&fp, self->tab_path, IOW_READ)==0 && NULL!=fp){

            while( !iow_feof(fp) ){
                // read next line
                memset(line_buf,0,read_len);
                if( NULL!=iow_fgets(fp,line_buf,read_len)){
                    if(strlen(line_buf)>0){
                        char *pflg=NULL;

                        if( (pflg=strstr(line_buf,"//"))!=NULL || (pflg=strstr(line_buf,"#"))!=NULL  ){
                            *pflg = '\0';
                        }

                        if( (pflg=strstr(line_buf,"flags"))!=NULL){
                            if( (pflg=strstr(pflg,"="))!=NULL){
                                micron_tab_parse_flags(self,pflg+1);
                            }
                        }else{
                            cron_task_t task_s;
                            cron_task_t *task_buf=&task_s;
                            micron_task_init(task_buf);
                            // parse task
                            retval=micron_task_parse(task_buf,line_buf);
                            if(retval==0){
                                memcpy(&self->tasks[self->n_tasks++],task_buf,sizeof(cron_task_t));
                                // found valid task
                                sched_r_show(task_buf->sch,5);
                            }
                            if(self->n_tasks>=CRON_MAX_TASKS){
                                WPRINT("reached CRON_MAX_TASKS[%d] - end processing\r\n",CRON_MAX_TASKS);
                                break;
                            }
                        }
                    }
                }
            }
            iow_fclose(fp);
        }else{
            EPRINT("could not open file [%s]\r\n",self->tab_path);
        }
    }
    return retval;
}

int micron_tab_set_path(cron_tab_t *self, char *path, bool keep_flags)
{
    int retval=-1;

    if(NULL!=self && NULL!=path){
         MPRINT(MOD_CRON,"tab[%s] path[%s]\r\n",self->name,path);

        tab_flags_t ocfg=0;
        if(keep_flags)ocfg=self->flags;

        micron_tab_init(self,path);

        if(keep_flags)self->flags=ocfg;

        retval=0;
    }
    return retval;
}

int micron_tab_set_task(cron_tab_t *self, int idx, schedule_t sch, char *scr)
{
    int retval=-1;
    if(NULL!=self &&
            idx>=0 && idx<CRON_MAX_TASKS &&
            NULL!=sch ){

        if(SCR_HRANGE_VALID(self->tasks[idx].hnd)){
            script_unload(self->tasks[idx].hnd);
        }
        schedule_t save_sch;
        // save sch
        memcpy(save_sch,self->tasks[idx].sch,sizeof(schedule_t));
        memcpy(self->tasks[idx].sch,sch,sizeof(schedule_t));
        retval=0;
        if(NULL!=scr){
            size_t len=strlen(scr);
            if(len>0 && len<CRON_PATH_LEN){
                // script is optional
                sprintf(self->tasks[idx].scr,"%s",scr);
                char *cp=self->tasks[idx].scr;
                while( *cp != '\0'){
                    *cp=toupper(*cp);
                    cp++;
                }
            }else{
                EPRINT("invalid script name [%s]",scr);
                // restore sch
                memcpy(self->tasks[idx].sch,save_sch,sizeof(schedule_t));
                // invalid len
                retval=-1;
            }
        }
    }else{TRACE();}

    if(MMOD_IS_EN(MOD_CRON)){
    MPRINT(MOD_CRON,"scr[%s] sch:\r\n",self->tasks[idx].scr);
    sched_r_show(self->tasks[idx].sch,5);
    MPRINT(MOD_CRON,"\r\n");
    }

    return retval;
}

int micron_tab_reset_task(cron_tab_t *self, int idx)
{
    int retval=-1;

    if(NULL!=self &&
           idx>=0 && idx<CRON_MAX_TASKS ){

        if(SCR_HRANGE_VALID(self->tasks[idx].hnd)){
            script_unload(self->tasks[idx].hnd);
        }
        self->tasks[idx].hnd=SCR_HND_INVALID;
        self->tasks[idx].req_n=0;
        self->tasks[idx].state=TSK_PENDING;
        micron_task_reset_timer(&self->tasks[idx]);
        retval=0;
    }
    return retval;
}
int micron_tab_suspend_task(cron_tab_t *self, int idx, bool suspend)
{
    int retval=-1;

    if(NULL!=self &&
           idx>=0 && idx<CRON_MAX_TASKS){
        if(suspend){
            self->tasks[idx].state=TSK_SUSPENDED;
        }else{
            self->tasks[idx].state=TSK_PENDING;
        }
        retval=0;
    }
    return retval;
}

cron_task_t *micron_tab_get_task(cron_tab_t *self, int idx)
{
   cron_task_t *retval=NULL;
    if(NULL!=self &&
        idx>=0 && idx<CRON_MAX_TASKS){
        retval = &self->tasks[idx];
    }
   return retval;
}

byte *micron_tab_get_schedule(cron_tab_t *self, int idx)
{
   byte *retval=NULL;
    if(NULL!=self &&
        idx>=0 && idx<CRON_MAX_TASKS){
        retval = (byte *)(&self->tasks[idx].sch[0]);
    }
   return retval;
}

char *micron_tab_get_script(cron_tab_t *self, int idx)
{
   char *retval=NULL;
    if(NULL!=self &&
        idx>=0 && idx<CRON_MAX_TASKS){
        retval = self->tasks[idx].scr;
    }
   return retval;
}

int micron_tab_set_flags(cron_tab_t *self, tab_flags_t flags)
{
    int retval=-1;
     if(NULL!=self){
         self->flags |= (flags);
        retval=0;
    }
    return retval;
}

int micron_tab_clr_flags(cron_tab_t *self, tab_flags_t flags)
{
    int retval=-1;
     if(NULL!=self){
         self->flags &= ~(flags);
        retval=0;
    }
    return retval;
}

int micron_tab_parse_flags(cron_tab_t *self, char *flag_str)
{
    int retval=-1;
     if(NULL!=self && NULL!=flag_str){
         if(strstr(flag_str,"+X")!=NULL || strstr(flag_str,"+x")!=NULL)
             micron_tab_set_flags(self,TAB_MODE_EXCL);
         if(strstr(flag_str,"-X")!=NULL || strstr(flag_str,"-x")!=NULL)
             micron_tab_clr_flags(self,TAB_MODE_EXCL);
         if(strstr(flag_str,"+E")!=NULL || strstr(flag_str,"+e")!=NULL)
             micron_tab_set_flags(self,TAB_CTRL_EN);
         if(strstr(flag_str,"-E")!=NULL || strstr(flag_str,"-e")!=NULL)
             micron_tab_clr_flags(self,TAB_CTRL_EN);
         int task=-1;
         char *sp=NULL;
         if( (sp=strstr(flag_str,"+S"))!=NULL || (sp=strstr(flag_str,"+s"))!=NULL){
             if(sscanf(sp+2,"%d",&task)==1 && CRON_TASK_IDX_VALID(task)){
              micron_tab_suspend_task(self,task, true);
             }
         }
         if( (sp=strstr(flag_str,"-S"))!=NULL || (sp=strstr(flag_str,"-s"))!=NULL){
             if(sscanf(sp+2,"%d",&task)==1 && CRON_TASK_IDX_VALID(task)){
              micron_tab_suspend_task(self,task, false);
             }
         }

         retval=0;
    }
    return retval;
}

int micron_tab_update(cron_tab_t *self, Time *ptm)
{
    int retval=-1;

    if(NULL!=self && NULL!=ptm){
        cron_task_t *task=self->tasks;
        int err_count=0;
        while(task->state!=TSK_LIST_END){
            if(task->state!=TSK_INVALID){
                err_count += ( micron_task_update(task,ptm,self->flags)==0 ? 0 : 1);
            }
            task++;
        }
        retval = (err_count==0 ? 0 : -1);
    }
    return retval;
}

int micron_update_tabs(cron_hnd_t hnd, Time *ptm)
{
    int retval=0;
    cron_instance_t *self=micron_get_instance(hnd);

    if(NULL!=self){
        // iterate over tabs
        cron_tab_t *next = self->tabs;
        int err_count=0;

        while( self->enabled && NULL!=next && next->name!=NULL){
            if(TAB_ENABLED(next)){
                 if(micron_tab_update(next, ptm)!=0){
                     err_count++;;
                }
            }
            next++;
        }
        retval=(err_count==0 ? 0 : -1);
    }
    return retval;
}


void micron_task_init(cron_task_t *self)
{
    if(NULL!=self){
        self->hnd=SCR_HND_INVALID;
        self->req_n=0;
        self->state=TSK_INVALID;
        tkTmrReset(&self->run_timer);
        memset(self->sch,0,sizeof(schedule_t));
        memset(self->scr,0,CRON_PATH_LEN);
    }
}

int micron_task_parse(cron_task_t *dest, char *src)
{
    int retval=-1;

    if(NULL!=dest && NULL!=src){

         // copy the line (this alters it)
        char *cpy=pstrdup(src);
        if(NULL!=cpy){
            char *sch_str=NULL;
            char *scr_str=NULL;

            if(micron_parse_cron(&sch_str,&scr_str,cpy,NULL)==0){
                schedule_t test_sch={0};
                // parse the schedule
                 MPRINT(MOD_CRON,"sch[%p/%s] scr[%p/%s]\r\n",sch_str,sch_str,scr_str,scr_str);
                if(sched_parse(test_sch,sch_str)==0 && strlen(scr_str)<CRON_PATH_LEN){
                    memcpy(dest->sch,test_sch,sizeof(schedule_t));
                    sprintf(dest->scr,"%s",scr_str);

                    char *cp=dest->scr;
                    while( *cp != '\0'){
                        *cp=toupper(*cp);
                        cp++;
                    }
                    dest->state=TSK_PENDING;
                    retval=0;
                }else{
                    // invalid
                    TRACE();
                }
            }
            // release line copy
            pfree(cpy);
        }// allocation error
    }

   return retval;
}

uint32_t micron_task_reset_timer(cron_task_t *self)
{
    uint32_t retval=0xFFFFFFFF;
    if(NULL!=self && NULL!=self->sch){
        tkTmrReset(&self->run_timer);
        Time tm={0};
        rtc_mktime(&tm);
        uint32_t next_msec = 1000UL*sched_next(self->sch,&tm);
        tkTmrSet(&self->run_timer, next_msec);
        MPRINT(MOD_CRON,"%s: %s, nextms, %lu, tkrem, %lu\r\n",__func__,self->scr,next_msec,self->run_timer.ticksLeft);
        retval=tkTmrStart(&self->run_timer);
    }
    return retval;
}

int micron_task_update(cron_task_t *self, Time *ptm, tab_flags_t flags)
{
    int retval=-1;
    if(NULL!=self){
        retval=0;
        typedef enum{TA_NOP=0x0,TA_RESET=0x2,TA_RUN_EXCL=0x4,TA_RUN_CONC=0x8}task_action_t;

        // update run timer & status
        bool tmr_expired=false;
        if(self->state!=TSK_SUSPENDED){
         tkTmrUpdate(&self->run_timer);
         tmr_expired = (self->run_timer.active && self->run_timer.ticksLeft<=0);
        }
        // get script state
        script_state_t scr_state=script_state(self->hnd);

        task_action_t action=TA_NOP;
//        if(tk_sflags!=0)
//        MPRINT(MOD_CRON,"%s: %s tmr[%c/%lu/%lu] ss[%d]\r\n",__func__,self->scr,BOOLCHAR(self->run_timer.active),self->run_timer.ticksLeft,rem_ms,scr_state);

        // update state
        switch(self->state){
           case TSK_INVALID:
                if(tmr_expired){
                    MPRINT(MOD_CRON,"TSK_INVALID: TMR EXP\r\n");
                    log_syslog("cron,EINVAL,tmr_exp/TSK_INVALID");
                    action|=TA_RESET;
                }else if(!self->run_timer.active){
                    MPRINT(MOD_CRON,"TSK_INVALID: TMR INACT\r\n");
                   // time to start
                   self->state=TSK_PENDING;
                }else{TRACE();}
               break;
             case TSK_PENDING:
                if(tmr_expired){
                    // time to run
                    if( (flags&TAB_MODE_EXCL)!=0){
                       MPRINT(MOD_CRON,"TSK_PENDING: RUNEX\r\n");
                       action|=TA_RUN_EXCL;
                    }else{
                       MPRINT(MOD_CRON,"TSK_PENDING: RUNCON\r\n");
                       action|=TA_RUN_CONC;
                    }
                    self->state=TSK_RUNNING;
                }else if(!self->run_timer.active){
                    // reset
                    MPRINT(MOD_CRON,"TSK_PENDING: RST\r\n");
                    // uninitialized or script done
                    // start a new cycle
                    action|=TA_RESET;
                    self->state=TSK_PENDING;
                }else{
                    // waiting for timer to expire
                }
                break;
            case TSK_RUNNING:
                 if(scr_state==SS_READY){
//                   OUT_CTTY("TSK_RUN: SS_READY\r\n");
                   log_syslog("cron,done,%s,st=%d,rqn=%d,rdy=%c",self->scr,scr_state,self->req_n,BOOLCHAR(tmr_expired));
                    MPRINT(MOD_CRON,"scr DONE: scr/hnd[%s/%d]\r\n",self->scr,self->hnd);
                    script_unload(self->hnd);
                    self->hnd=SCR_HND_INVALID;
                    // reset run timer
                    action|=TA_RESET;
                    self->state=TSK_PENDING;
                }else if(scr_state==SS_ERR){
//                    OUT_CTTY("TSK_RUN: SS_ERR\r\n");
                    log_syslog("cron,SSERR,scr/hnd/ss[%s/%d/%d]",self->scr,self->hnd,script_state);
                     MPRINT(MOD_CRON,"scr ERR: scr/hnd[%s/%d]\r\n",self->scr,self->hnd);
                   // reset run timer
                    action|=TA_RESET;
                    self->state=TSK_PENDING;
                }else{
//                    OUT_CTTY("TSK_RUN: RUNNING\r\n");
                    // still running
                }
               break;
            case TSK_SUSPENDED:
//                MPRINT(MOD_CRON,"TSK_SUSPENDED scr/hnd/ss[%s/%d/%d]\r\n",self->scr,self->hnd,scr_state);
                break;
            default:
                  EPRINT("ERR : INVALID STATE scr/st[%s/%d\r\n",self->scr,self->state);
               break;
        }

        // evaluate action(s)
        if( (action&TA_RESET)!=0){
            // reset timer
            micron_task_reset_timer(self);
        }

        if( (action&TA_RUN_EXCL)!=0){
            // run script (exclusive mode - does no yield)
            MPRINT(MOD_CRON,"RUNEX %s\r\n",self->scr);
            return_code_t test=0;
            if((test=script_run_excl(self->scr,false))==ERR_SUCCESS){
                 retval=0;
            }else{
                EPRINT("ERUNEX %s/%d\r\n",self->scr,self->hnd);
                log_syslog("cron,ERUNEX,%s,st=%drv=%d",self->scr,scr_state,test);
            }
            log_syslog("cron,runex,%s,st=%drv=%d",self->scr,scr_state,test);
            tkTmrReset(&self->run_timer);
            self->hnd=SCR_HND_INVALID;
            self->state=TSK_PENDING;
        }
        if( (action&TA_RUN_CONC)!=0){
            // run script (concurrent mode - like other scripts)
            self->hnd=script_load(self->scr, -1, false);

            if(SCR_HRANGE_VALID(self->hnd)){
                return_code_t test=0;
                MPRINT(MOD_CRON,"RUNCON %s/%d\r\n",self->scr,self->hnd);
                if((test=script_run(self->hnd))==ERR_SUCCESS){
                    self->state=TSK_RUNNING;
                    retval=0;
                }else{
                    EPRINT("ERUNCON %s/%d (unloading)\r\n",self->scr,self->hnd);
                    TRACE();
                    log_syslog("cron,ERUNCON,%s,hnd[%d],err[%d]",self->scr,self->hnd,test);
                    script_unload(self->hnd);
                    self->hnd=SCR_HND_INVALID;
                    tkTmrReset(&self->run_timer);
                    self->state=TSK_PENDING;
                }
                log_syslog("cron,runcon,%s,st=%d,rv=%d",self->scr,script_state(self->hnd),test);
            }else{
                // script load error
                EPRINT("RUNCON EINVHND %s/%d\r\n",self->scr,self->hnd);
                log_syslog("cron,EINVHND,%s,hnd[%d]",self->scr,self->hnd);
                self->hnd=SCR_HND_INVALID;
                tkTmrReset(&self->run_timer);
                self->state=TSK_PENDING;
            }
        }
    }else{
        // invalid argument (NULL task)
        EPRINT("Invalid arg %p\r\n",self);
        log_syslog("cron:%d,invalid arg",__LINE__);
    }
    return retval;
}

uint32_t micron_next_event(cron_hnd_t hnd, Time *tm)
{
    uint32_t retval=SYS_MAX_SLEEP_MS;
    cron_instance_t *self=micron_get_instance(hnd);
    uint32_t min_task_msec=SYS_MAX_SLEEP_MS;

    if(NULL!=self && self->enabled){

        // iterate over tabs
        cron_tab_t *next_tab=self->tabs;

        while(next_tab->id!=TABID_INVALID){
             if(CRON_TAB_ENABLED(next_tab)){
                 // iterate over (active) tasks
                cron_task_t *task=next_tab->tasks;
                while(task->state!=TSK_LIST_END){
                    uint32_t rem_msec=0xFFFFFFFFUL;
                    if(task->state==TSK_RUNNING){
                        rem_msec=0UL;
                    }else if(task->state==TSK_PENDING && task->run_timer.active){
                        // read run timer
                        // and update min time remaining
                        rem_msec = tkTmrUpdate(&task->run_timer);
                    }
                    if(rem_msec<min_task_msec){
                        min_task_msec=rem_msec;
                    }
                    task++;
                }
            }
            next_tab++;
        }
        retval = min_task_msec;
 //        OUT_CTTY("%s:%d - %lu\r\n",__func__,__LINE__,retval);
    }
    return retval;
}