///
/// @file script.c
/// @authors k. Headley
/// @date 01 feb 2019

/// script parsing

/// @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 <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#include "script.h"
#include "commands.h"
#include "cwgsys.h"
#include "cwg-shared.h"
#include "config.h"
#include "iowfile.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"
 */

// these directly access the script table, so can't be accessed from outside
// since the script table is not exported
#define SCR_CTX_VALID(hnd) (SCR_HRANGE_VALID(hnd) && NULL!=script_table[hnd].ctx)
#define SCR_HND2PS(hnd) (SCR_HRANGE_VALID(hnd) ? &script_table[hnd] : NULL)
#define SCR_HND2PCTX(hnd) (SCR_HRANGE_VALID(hnd) ? script_table[hnd].ctx : NULL)

#define SCR_SET_EVENTS(ps,msk) (NULL!=ps ? ps->ctx->events |= (script_event_t)msk : SE_INVALID)
#define SCR_CLR_EVENTS(ps,msk) (NULL!=ps ? ps->ctx->events &= ~((script_event_t)msk) : SE_INVALID)
#define SCR_GET_EVENTS(ps) (NULL!=ps ? ps->ctx->events : SE_INVALID)
#define SCR_MGET_EVENTS(ps,msk) (ps !=NULL ? ps->ctx->events&msk : SE_INVALID)

#define SCR_SET_MODES(ps,msk) (NULL!=ps ? ps->ctx->modes |= (script_mode_t)msk : SM_INVALID)
#define SCR_CLR_MODES(ps,msk) (NULL!=ps ? ps->ctx->modes &= ~((script_mode_t)msk) : SM_INVALID)
#define SCR_GET_MODES(ps) (NULL!=ps ? ps->ctx->modes : SM_INVALID)
#define SCR_MGET_MODES(ps,msk) (NULL!=ps ? ps->ctx->modes&msk : SM_INVALID)

#define SCR_SET_ACT(act,msk) (act |= (script_action_t)msk)
#define SCR_CLR_ACT(act,msk) (act &= ~((script_action_t)msk))
#define SCR_MGET_ACT(act,msk) (act&msk)

#define TMR_PTR(pc,id) (&pc->timer[id])
#define TMR_ACTIVE(pc,id) ( pc->timer[id].active )

#define SPDEBUG(ps,...) if(NULL!=ps && SCR_MGET_MODES(ps,SM_DEBUG)){ \
serPrintf(SER_CONS,__VA_ARGS__);\
}
#define SHDEBUG(hnd,...) if(SCR_HRANGE_VALID(hnd) && SCR_MGET_MODES(SCR_HND2PS(hnd),SM_DEBUG)){ \
serPrintf(SER_CONS,__VA_ARGS__);\
}

typedef enum {TT_INVALID=-1,TT_BOOL=0x1,TT_INT=0x2,TT_FLT=0x4,TT_NUM=0x6}tok_types;

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

// operand overlay structures
// (not instantiated, pointers only)
return_code_t script_set_name(script_hnd_t hnd, char *name);
return_code_t script_update(script_hnd_t hnd);

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

/////////////////////////
// Module Global Variables
/////////////////////////
script_t script_table[SCR_SCRIPT_COUNT]={{0},{0},{0},{0}};
static char g_line_buffer[SCR_LINE_BUFFER_BYTES];

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

char *script_name(script_hnd_t hnd)
{
    return ( (NULL!=script_table[hnd].ctx) ? script_table[hnd].ctx->name : "");
}

return_code_t script_bootstrap(char *src)
{
    return_code_t retval=ERR_NOT_FOUND;
    script_hnd_t hnd=script_load((NULL!=src?src:SYS_BOOSTRAP_DFL),(SCR_SCRIPT_COUNT-1),true);

    if( SCR_HRANGE_VALID(hnd) ){
        script_t *ps = SCR_HND2PS(hnd);
        SCR_SET_EVENTS(ps,SE_PLAY);
        // execute the bootstrap script
        while(script_update(hnd)!=ERR_EOF){
        }
        script_unload(hnd);
        retval=ERR_SUCCESS;
    }else{
        WPRINT("bootstrap script_load failed src[%s] hnd[%d]\r\n",src,hnd);
    }

    return retval;
}

return_code_t script_run_excl(char *src, bool overwrite)
{
    return_code_t retval=ERR_NOT_FOUND;

    if(NULL!=src){

        script_hnd_t hnd=script_load(src,-1,overwrite);
        MPRINT(MOD_SCH,"%s:%d - src[%s] hnd[%d] ovr[%c]\r\n",__func__,__LINE__,src,hnd,BOOLCHAR(overwrite));

        if( SCR_HRANGE_VALID(hnd) ){
            script_t *ps = SCR_HND2PS(hnd);
            SCR_SET_EVENTS(ps,SE_PLAY);
            // execute the bootstrap script
            while(script_update(hnd)!=ERR_EOF){
            }
            script_unload(hnd);
            retval=ERR_SUCCESS;
        }else{
            WPRINT("bootstrap script_run_excl failed src[%s] hnd[%d]\r\n",src,hnd);
            retval=ERR_FAILURE;
        }
    }

    return retval;
}

void script_show(int hnd, bool verbose, uint16_t indent)
{
    if( SCR_HRANGE_VALID(hnd) ){
        int wkey=10;
        int wval=20;

        script_t *self=&script_table[hnd];
        OUT_CTTY("%*s%*s %*p%s",indent,"",wkey,"self",wval,self,LINEEND);
        OUT_CTTY("%*s%*s %*p%s",indent,"",wkey,"ctx",wval,self->ctx,LINEEND);
        if(NULL!=self->ctx){
            OUT_CTTY("%*s%*s %*s%s",indent,"",wkey,"name",wval,self->ctx->name,LINEEND);
            OUT_CTTY("%*s%*s %*p%s",indent,"",wkey,"fp",wval,self->ctx->fp_src,LINEEND);
            OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"otty",wval,self->ctx->otty,LINEEND);
            OUT_CTTY("%*s%*s %*c%04X%s",indent,"",wkey,"state",wval-4,'x',self->ctx->state,LINEEND);
            OUT_CTTY("%*s%*s %*c%04X%s",indent,"",wkey,"modes",wval-4,'x',self->ctx->modes,LINEEND);
            OUT_CTTY("%*s%*s %*c%04X%s",indent,"",wkey,"events",wval-4,'x',self->ctx->events,LINEEND);
            OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"error",wval,self->ctx->error,LINEEND);
            OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"rstat",wval,self->ctx->rstat,LINEEND);
            int i=0;
            for(i=0;i<SCR_TMR_COUNT;i++){
             OUT_CTTY("%*s%*s[%d] %*c{%c,%6lu,%6lu}%s",indent,"",wkey-3,"tmr",i,wval-17,' ',
                    BOOLCHAR(self->ctx->timer[i].active),
                    self->ctx->timer[i].ticksLeft,
                    self->ctx->timer[i].lastRead,LINEEND);
            }
            OUT_CTTY("%*s%*s %*lu%s",indent,"",wkey,"period",wval,self->ctx->period,LINEEND);
            OUT_CTTY("%*s%*s %*u%s",indent,"",wkey,"line",wval,self->ctx->line,LINEEND);
        }// if ctx
    }// if SCR_HRANGE_VALID
}

return_code_t script_set_event(script_hnd_t hnd, script_event_t mask)
{
    return_code_t retval=ERR_INVALID;
    if(SCR_HRANGE_VALID(hnd)){
//        EPRINT("set_event - arg[%d/%04X] ptr[%p]",hnd,mask,SCR_HND2PS(hnd));
        SCR_SET_EVENTS(SCR_HND2PS(hnd),mask);
//        EPRINT("set_event - events[%04X]",SCR_GET_EVENTS(SCR_HND2PS(hnd)));
        retval=ERR_SUCCESS;
    }
    return retval;
}

return_code_t script_set_mode(script_hnd_t hnd, script_mode_t mask)
{
    return_code_t retval=ERR_INVALID;
   if(SCR_HRANGE_VALID(hnd)){
        SHDEBUG(hnd,"script_set_mode - arg[%d/%04X] ptr[%p]\r\n",hnd,mask,SCR_HND2PS(hnd));

        SCR_SET_MODES(SCR_HND2PS(hnd),mask);
//        EPRINT("set_event - events[%04X]",SCR_GET_EVENTS(SCR_HND2PS(hnd)));
        retval=ERR_SUCCESS;
   }
    return retval;
}
return_code_t script_clr_mode(script_hnd_t hnd, script_mode_t mask)
{
    return_code_t retval=ERR_INVALID;
    if(SCR_HRANGE_VALID(hnd)){
//        EPRINT("set_event - arg[%d/%04X] ptr[%p]",hnd,mask,SCR_HND2PS(hnd));
        SCR_CLR_MODES(SCR_HND2PS(hnd),mask);
//        EPRINT("set_event - events[%04X]",SCR_GET_EVENTS(SCR_HND2PS(hnd)));
       retval=ERR_SUCCESS;
     }

    return retval;
}

return_code_t script_tdispatch(int argc, char **argv, const ScrCmdStruct* cmds, script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;
      int i=0;
     char* args=NULL;
     DSHOW_TOKS(argc,argv);
     if(argc>0){
        // compare name against each name in CmdStruct
        for (i = 0; cmds[i].name[0] != '\0'; i++) {
//            if (strstr(argv[0], cmds[i].name)!=NULL) {
            if (NULL != (args = uiStrName(argv[0], cmds[i].name))) {
                // execute command
                return(cmds[i].func(argc-1,&argv[1],hnd));
            }
        }
     }
     return retval;
}

return_code_t script_dispatch(char* line, const ScrCmdStruct* cmds, script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;
    int i=0;
    char* args=NULL;

    // OK to pass NULL script
    if(NULL!=line){
        if (NULL == cmds) {
            cmds = cmdsScr;
        }
        while (isspace(*line)) //skip whitespace
            line++;

        // compare name against each name in CmdStruct
        for (i = 0; cmds[i].name[0] != '\0'; i++) {
            if (NULL != (args = uiStrName(line, cmds[i].name))) {
                while (isspace(*args)) //skip whitespace
                    args++;
                SHDEBUG(hnd,"%s - line[%s] args[%s] hnd[%d]\r\n",__FUNCTION__,line,args,hnd);
                char *tokens[16]={0};
                int ntoks=ui_tokenize(line,tokens," ",16,true);
                // execute command
                retval=(cmds[i].func(ntoks,tokens,hnd));
                ui_release_toks(tokens,ntoks);
                return retval;
            }
        }
    }

    return ERR_NO_COMMAND;
}

//return_code_t s_eval_bool_token(char *tok, bool *result)
//{
//   return_code_t retval=ERR_FAILURE;
//
//   if(NULL!=tok && NULL!=result){
//        uint32_t counter=0;
//        bool flag= false;
//        if(config_contains(tok)){
//            *result = config_bool_val(tok,&retval);
//
//        }else if(script_get_counter(tok,&counter)==ERR_SUCCESS){
//            *result = (counter==0 ? false : true);
//            retval=ERR_SUCCESS;
//        }else if(script_get_flag(tok,&flag)==ERR_SUCCESS){
//            *result = flag;
//            retval=ERR_SUCCESS;
//        }
//   }
//
//   return retval;
//}

bool s_tok_isnum(char *tok, double *dval, int64_t *ival, int *type)
{
    bool retval=false;
    uint64_t u64val=0ULL;
    char *cp=tok;
    bool ishex=false;
    bool isnum=true;
    bool isfloat=false;
    while(*cp!='\0' && !isspace(*cp)){
        if( !isdigit(*cp) ){
            if( isxdigit(*cp) ){
                ishex=true;
                *cp=toupper(*cp);
            }else if(*cp=='.' || *cp=='e' || *cp=='E'){
                isfloat=true;
            }else if( (*cp=='-' || *cp=='+') && cp==tok){
                // signed, OK
            }else if( (*cp=='-' || *cp=='+') && toupper(*(cp-1))=='E'){
                // exponent, OK
            }else{
                isnum=false;
                break;
            }
        }
        cp++;
    }
    if(isnum){
        if(ishex){
            if(sscanf(tok,"%llX",&u64val)==1){
                *dval=(double)u64val;
                *ival=u64val;
                *type=TT_INT;
                retval=true;
            }
        }else if(isfloat){
            if(sscanf(tok,"%lf",dval)==1){
                *ival=*dval;
                *type=TT_FLT;
                retval=true;
            }
        }else{
             if(sscanf(tok,"%llu",&u64val)==1){
                *dval=(double)u64val;
                *ival=u64val;
                *type=TT_INT;
                retval=true;
            }else if(sscanf(tok,"%lld",ival)==1){
                *dval=(double)u64val;
                *type=TT_INT;
                retval=true;
            }

       }
    }
//    DPRINT("tok[%s] isnum[%c] ishex[%c] isfloat[%c] uv[%llu] dv[%lf] iv[%lld]\r\n",tok,(isnum?'Y':'N'),(ishex?'Y':'N'),(isfloat?'Y':'N'),u64val,*dval,*ival);
    return retval;
}

bool s_tok_isvar(char *tok, double *dval, int64_t *ival, int *type)
{
    bool retval=true;

    if(config_contains(tok)){
        config_int_val(tok,ival);
        config_double_val(tok,dval);
        // ok
        config_type_id t=config_type(tok);
        switch(t){
            case CTI_BOOL:
            *type=TT_BOOL;
            break;
            case CTI_STRING:
            case CTI_INVALID:
            *type=TT_INVALID;
            break;
            case CTI_DOUBLE:
                *type=TT_FLT;
            default:
            *type=TT_INT;
                break;
        }
    }else{
        retval=false;
        *type=TT_INVALID;
    }
    return retval;
}

bool s_tok_isbool(char *tok, double *val)
{
    bool retval=true;
    if(strcmp(tok,"TRUE")==0){
        *val=1.0;
    }else if(strcmp(tok,"FALSE")==0){
        *val=0.0;
    }else{
        retval=false;
    }

    return retval;
}

return_code_t script_parse_boolex(char *expr, bool *result)
{
    return_code_t retval=ERR_FAILURE;

    if(NULL!=expr){
        char *acpy = (char *)ptstrdup(expr,true,true);
        if(NULL!=acpy){
            char *pex[2]={strstr(acpy,"["),strstr(acpy,"]")};
            char *cur=NULL;
            char *toks[5]={0};
            int ntoks=0;

            if(NULL==pex[0]){
                pex[0]=acpy;
                cur=pex[0];
            }else{
                cur=pex[0]+1;
            }
            if(NULL==pex[1]){
                pex[1]=acpy+strlen(acpy);
            }

            // mark/count up to 5 tokens
            while(cur<pex[1] && ntoks<=5){
                while(isspace(*cur) && cur<pex[1]){
                    *cur='\0';
                    cur++;
                }
                if(!isspace(*cur) && cur<pex[1]){
                    // found token
                    // mark it, update count
                    toks[ntoks++]=cur;
                    while(!isspace(*cur) && cur<pex[1])cur++;
                }else{
                    cur++;
                }
            }

            enum {PENDA=0,PENDOP,PENDB,ERR, DONE};
            enum {NOP=0,NEGA,NEGB,VTOK,NTOK,BTOK,MARKOP,QUIT};
            bool negate[2] = {false,false};
            int t=0;
            char *op=NULL;
            bool var_bvals[2]={false,false};
            double var_dvals[2]={0.0,0.0};
            int64_t var_ivals[2]={0LL,0LL};
            tok_types var_types[2]={TT_INVALID,TT_INVALID};
            int state=PENDA;
            int action=NOP;
            int vsel=0;
            while(t<ntoks && state!=ERR && state!=DONE){
//                DPRINT("TOK[%s] STATE[%d]\r\n",toks[t],state);
                switch(state){
                    case PENDA:
                    case PENDB:
                        vsel=(state==PENDA?0:1);
                        if(strcmp(toks[t],"!")==0){
                            action = (state==PENDA?NEGA:NEGB);
                            // no state change
                        }else if(s_tok_isvar(toks[t],&var_dvals[vsel],&var_ivals[vsel],&var_types[vsel])){
                            action = VTOK;
                            state  = (state==PENDA ? PENDOP : DONE);
                        }else if(s_tok_isnum(toks[t],&var_dvals[vsel],&var_ivals[vsel],&var_types[vsel])){
                            action = NTOK;
                            state  = (state==PENDA ? PENDOP : DONE);
                        }else if(s_tok_isbool(toks[t],&var_dvals[vsel])){
                            action = BTOK;
                            state  = (state==PENDA ? PENDOP : DONE);
                        }else{
                            state=ERR;
                            action=QUIT;
                             EMSG("invalid token");
                        }
                        break;
                    case PENDOP:
                        action=MARKOP;
                        state=PENDB;
                        break;
                    default:
                        state=ERR;
                        action=QUIT;
                          EMSG("invalid state");
                        break;
                }

                switch(action){
                    case NEGA:
                        negate[0]=true;
                        break;
                    case NEGB:
                        negate[1]=true;
                        break;
                     case VTOK:
                        if(var_types[vsel]==TT_BOOL){
                            var_bvals[vsel]=((var_dvals[vsel]==0.0 ? false : true));
                            if (negate[vsel]){
                               var_dvals[vsel]=(var_dvals[vsel]==0.0 ? 1.0 : 0.0);
                               var_bvals[vsel]=(var_bvals[vsel] ? false : true);
                            }
                        }
                        break;
                     case NTOK:
                         // nothing to do
//                         var_types[vsel]=TT_NUM;
                        break;
                     case BTOK:
                        var_types[vsel]=TT_BOOL;
                        var_bvals[vsel]=((var_dvals[vsel]==0.0 ? false : true));
                        if(negate[vsel]){
                             var_dvals[vsel]=(var_dvals[vsel]==0.0 ? 1.0 : 0.0);
                             var_bvals[vsel]=(var_bvals[vsel] ? false : true);
                        }
                       break;
                     case NOP:
                        break;
                    case MARKOP:
                        op=toks[t];
                         break;
                    default:
                        // invalid action
                         EMSG("invalid action");
                        break;
                }
                t++;
            }

            if(state!=ERR){

                if(ntoks>0 && ntoks<3){
                   // single boolean value
                    // (negation already applied))
                    if(var_types[0]==TT_BOOL){
                       *result = var_bvals[0];
                        retval = ERR_SUCCESS;
                    }else{
                             EMSG("invalid type for boolean op");
                       }
                }else if(ntoks>2 && NULL!=op){
                    // L,R reduced to double values
                    // compare : L op R
                    retval=ERR_SUCCESS;
                    if(var_types[0] == var_types[1]){
                        if(var_types[0]==TT_INT || var_types[0]==TT_FLT){
//                               DPRINT("var types[%d/%d]\r\n",var_types[0],var_types[1]);
                           if(var_types[0] != var_types[1]){
                               WPRINT("var type mismatch[%d/%d]\r\n",var_types[0],var_types[1]);
                           }
                           if(strcmp(op,"==")==0){
//                               DPRINT("OP[%s] A[%lf] B[%lf] [%c]\r\n",op,var_dvals[0],var_dvals[1],((var_dvals[0]==var_dvals[1])?'T':'F'));
                               if(var_types[0]==TT_FLT)
                                *result = (var_dvals[0]==var_dvals[1]);
                               else
                                *result = (var_ivals[0]==var_ivals[1]);
                            }else if(strcmp(op,"!=")==0){
//                               DPRINT("OP[%s] A[%lf] B[%lf] [%c]\r\n",op,var_dvals[0],var_dvals[1],((var_dvals[0]==var_dvals[1])?'T':'F'));
                                if(var_types[0]==TT_FLT)
                                *result = (var_dvals[0]!=var_dvals[1]);
                               else
                                *result = (var_ivals[0]!=var_ivals[1]);
                            }else if(strcmp(op,">")==0){
//                               DPRINT("OP[%s] A[%lf] B[%lf] [%c]\r\n",op,var_dvals[0],var_dvals[1],((var_dvals[0]==var_dvals[1])?'T':'F'));
                                if(var_types[0]==TT_FLT)
                                *result = (var_dvals[0]>var_dvals[1]);
                               else
                                *result = (var_ivals[0]>var_ivals[1]);
                            }else if(strcmp(op,"<")==0){
//                               DPRINT("OP[%s] A[%lf] B[%lf] [%c]\r\n",op,var_dvals[0],var_dvals[1],((var_dvals[0]==var_dvals[1])?'T':'F'));
                                if(var_types[0]==TT_FLT)
                                *result = (var_dvals[0]<var_dvals[1]);
                               else
                                *result = (var_ivals[0]<var_ivals[1]);
                            }else if(strcmp(op,">=")==0){
//                               DPRINT("OP[%s] A[%lf] B[%lf] [%c]\r\n",op,var_dvals[0],var_dvals[1],((var_dvals[0]==var_dvals[1])?'T':'F'));
                                if(var_types[0]==TT_FLT)
                                *result = (var_dvals[0]>=var_dvals[1]);
                               else
                                *result = (var_ivals[0]>=var_ivals[1]);
                            }else if(strcmp(op,"<=")==0){
//                               DPRINT("OP[%s] A[%lf] B[%lf] [%c]\r\n",op,var_dvals[0],var_dvals[1],((var_dvals[0]==var_dvals[1])?'T':'F'));
                                if(var_types[0]==TT_FLT)
                                *result = (var_dvals[0]<=var_dvals[1]);
                               else
                                *result = (var_ivals[0]<=var_ivals[1]);
                            }else{
                                retval=ERR_FAILURE;
                                EMSG("invalid num operator");
                            }
                        }else if(var_types[0]==TT_BOOL){
                           if(strcmp(op,"==")==0){
                                *result = (var_bvals[0] == var_bvals[1]);
                            }else if(strcmp(op,"!=")==0){
                                *result = (var_bvals[0] != var_bvals[1]);
                            }else if(strcmp(op,"&&")==0){
                                *result = (var_bvals[0] && var_bvals[1]);
                            }else if(strcmp(op,"||")==0){
                                *result = (var_bvals[0] || var_bvals[1]);
                            }else{
                                retval=ERR_FAILURE;
                                 EMSG("invalid bool operator");
                            }
                       }else{
                             EMSG("eval type invalid");
                       }

                    }else{
                          EMSG("eval type mismatch");
                    }
                }// error zero tokens
            }
            pfree(acpy);
        }else{
             EMSG("palloc failed");
        }
    }
    return retval;
}

// intercept any script context commands
// pass CLI commands to UI parser
// line must have already been validated
return_code_t script_parse_line(char *line, size_t len, script_hnd_t hnd)
{
    return_code_t retval=ERR_INVALID;

    if(NULL!=line && SCR_HRANGE_VALID(hnd) && len>0){

        // TODO: check/evaluate conditional
        // [handle as scr cmd]

        // execute script or UI command
        return_code_t rtn_test=ERR_INVALID;
        if( (rtn_test=script_dispatch(line,cmdsScr,hnd))==ERR_SUCCESS){
            // valid script command
            retval=ERR_SUCCESS;
        }else if( (rtn_test=uiParse(line,NULL))==ERR_SUCCESS){
            // valid CLI command
           retval=ERR_SUCCESS;
        }// else invalid
        script_t *ps = &script_table[hnd];
        script_ctx_t *pc=ps->ctx;
        pc->rstat=rtn_test;
    }
    return retval;
}

script_hnd_t script_parse_hnd(char *arg)
{
    // try lookup (script name)
    script_hnd_t retval=script_lookup(arg);
    // if lookup fails, parse as numeric ID
    if( retval == SCR_HND_INVALID ){
        sscanf(arg,"%d",&retval);
    }
    // validate result
    if(!SCR_HRANGE_VALID(retval)){
        retval=SCR_HND_INVALID;
    }

    return retval;
}

// read line
// strip leading whitespace
// NULL terminate at comment or CR/LF
// return line length
int32_t script_read_line(script_hnd_t hnd, char *dest, size_t len, int *err)
{
    int32_t retval = -1;
    char *dp=dest;
    char *test=NULL;
    bool line_flag=true;
    bool quit=false;
    unsigned int br=0;
    int ret=0;

    if( SCR_HRANGE_VALID(hnd) && NULL!=dest && len>0){
        script_t *ps = &script_table[hnd];
        script_ctx_t *pc=ps->ctx;
        FILE_T *fp=pc->fp_src;
        if(NULL!=pc && NULL!=fp){
            // must leave one byte for NULL
            while(dp<(dest+len-1) && !quit){
                br=iow_fread(fp,dp,1);
                ret=iow_errno;

                if(br!=1){
                    uint32_t flen=iow_fsize(fp);
                    uint32_t fofs=iow_ftell(fp);
                    if(NULL!=err)
                    *err=ret;

                    if(fofs>=flen && ret==IOW_OK && br==0){
                        // terminate and return
                        *dp=toupper(*dp);
                        dp[1]='\0';
                        // unterminated line at end of file
                        WPRINT("EOF (unterminated END statement?) [%s]",dest);
                        retval=strlen(dest);
                        quit=true;
                    }
                    SHDEBUG(hnd,"ERR - iow_fread bofs[%lu] fofs[%lu] flen[%lu] br[%u] ret[%d]\r\n",(unsigned long)(dp-dest),fofs,flen,br,ret);
                    break;
                }

                if(iow_feof(fp)){
                   // unterminated line at end of file
                    // terminate and return
                    *dp=toupper(*dp);
                    dp[1]='\0';
                    if( !(*dp=='\r' || *dp=='\n') )
                    WPRINT("EOF (unterminated END statement?) [%s]",dest);
                    retval=strlen(dest);
                    quit=true;
                    break;
                }

                // convert to upper case
                *dp=toupper(*dp);
                // NULL terminate dest
                dp[1]='\0';

                 switch (*dp) {
                    case '\n':
                    case '\r':
                    if(dp>dest){
                        // stop at line end
                         *dp='\0';
                        retval=strlen(dest);
                        quit=true;
                    }else{
                    // skip empty line
                        line_flag=true;
                        dp=dest;
                        *dp='\0';
                    }
                    break;

                    case ' ':
                    case '\t':
                    if(line_flag){
                        //skip space at beginning of line
                        dp=dest;
                    }
                    break;

                    default:
                    line_flag=false;
                    break;
                } // switch

                if(!line_flag){
                    dp++;
                }
            } // while

            // terminate at any comments
            if( (test=strstr(dp,"#"))!=NULL){
                *test='\0';
                retval=strlen(dp);
            }else if((test=strstr(dp,"//"))!=NULL){
                *test='\0';
                retval=strlen(dp);
            }
        }
    }else{
        EPRINT("script_read_line - invalid arg [%d/%p/%d]",hnd,dest,len);
    } // else invalid arg
    return retval;
}

return_code_t script_init(script_hnd_t hnd,char *name)
{
    return_code_t retval=ERR_INVALID;

    if(SCR_HRANGE_VALID(hnd)){

        int j=0;
        script_t *ps=&script_table[hnd];
        script_ctx_t *pc=NULL;
        int test=0;
        for(j=0;j<SCR_CONTEXT_DEPTH;j++){

            pc=&ps->ctx_table[j];
            if(NULL!=pc){

                // if name is NULL, will NULL-fill name
                script_set_name(hnd,name);
                if(NULL!=name){

    #if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
                   pc->fp_src = &pc->file;
    #endif
                   if( (test=iow_fopen(&pc->fp_src,name,(FA_READ)))==0){
                       ///// test
                       script_set_name(hnd,name);

                        SCR_CLR_MODES(ps,SM_ALL);
                        SCR_CLR_EVENTS(ps,SE_ALL);
                        pc->state=SS_READY;
                        pc->period=0;
                        pc->line=0;
                        pc->otty=SER_CONS;
                        memset(pc->timer,0,SCR_TMR_COUNT*sizeof(Timer));
                        memset(pc->reg16,0,SCR_R16_COUNT*sizeof(reg16_t));
                        retval=ERR_SUCCESS;
                        ps->ctx=ps->ctx_table;

                   }else{
                       // invalidate context file pointer
                       pc->fp_src = NULL;
                       EPRINT("could not open source[%s] err[%d]",pc->name,test);
                   }
                }
            }
        }

    }
    return retval;
}

/// initialize scripts
return_code_t script_init_all()
{
    return_code_t retval=ERR_INVALID;

    int i=0;
    for(i=0;i<SCR_SCRIPT_COUNT;i++){
        script_init(i,NULL);
     }

    return retval;
}

return_code_t script_delay(script_hnd_t hnd, uint32_t msec)
{
    return_code_t retval=ERR_FAILURE;

    if(SCR_HRANGE_VALID(hnd)){
        script_ctx_t *pc = SCR_HND2PCTX(hnd);
        if(NULL!=pc){
//           SHDEBUG(hnd,"script_delay[%ul ms]\r\n",msec);
            // set and start the timer
            tkTmrSet(TMR_PTR(pc,T_DELAY), msec);
            tkTmrStart(TMR_PTR(pc,T_DELAY));
            // flip the DELAY flag to notify the state machine
            SCR_SET_EVENTS(SCR_HND2PS(hnd),SE_DELAY);
            // TODO: consider whether to stop T_RUN or
            // let it run during delay
        }
        retval=ERR_SUCCESS;
    }
    return retval;
}

// set script run timer period (0 to disable)
// optionally restart timer (otherwise just sets period)
return_code_t script_set_period(script_hnd_t hnd, uint32_t period, bool sync)
{
   return_code_t retval=ERR_FAILURE;
//    SHDEBUG(hnd,"%s - hnd[%d]\r\n",__FUNCTION__,hnd);

    if(SCR_HRANGE_VALID(hnd)){
        script_ctx_t *pc = SCR_HND2PCTX(hnd);
        if(NULL!=pc){
            // set and start run timer
            pc->period=period;
            if(period==0){
                tkTmrReset(TMR_PTR(pc,T_RUN));
            }
            if(sync){
                script_sync(hnd,0);
            }
            retval=ERR_SUCCESS;
        }// else not active
    }// else invalid arg
    return retval;
}

return_code_t script_set_otty(script_hnd_t hnd, serial_port_t otty)
{
   return_code_t retval=ERR_FAILURE;
//    SHDEBUG(hnd,"%s - hnd[%d]\r\n",__FUNCTION__,hnd);

    if(SCR_HRANGE_VALID(hnd)){
        script_ctx_t *pc = SCR_HND2PCTX(hnd);
        if(NULL!=pc){
            // validate and set
            if(otty==SER_CONS || otty==SER_HOST){
                pc->otty=otty;
                retval=ERR_SUCCESS;
            }
        }// else not active
    }// else invalid arg
    return retval;
}

return_code_t script_sync(script_hnd_t hnd, uint32_t delay)
{
    return_code_t retval=ERR_FAILURE;
//    SHDEBUG(hnd,"%s - hnd[%d]\r\n",__FUNCTION__,hnd);

    if(SCR_HRANGE_VALID(hnd)){
        script_ctx_t *pc = SCR_HND2PCTX(hnd);
        script_t *ps = SCR_HND2PS(hnd);
        if(NULL!=pc){
            script_reset(hnd);
            script_delay(hnd, delay);
            SCR_SET_EVENTS(ps,SE_PLAY);

            // set and start run timer
            tkTmrSet(TMR_PTR(pc,T_RUN),delay);
            tkTmrStart(TMR_PTR(pc,T_RUN));
            retval=ERR_SUCCESS;
        }// else not active
    }// else invalid arg
    return retval;
}

return_code_t script_reset(script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;
//    SHDEBUG(hnd,"%s - hnd[%d]\r\n",__FUNCTION__,hnd);

    if(SCR_HRANGE_VALID(hnd)){
        script_t *ps = &script_table[hnd];
        script_ctx_t *pc = ps->ctx;
        if(NULL!=pc){
            if(NULL!=pc->fp_src){
                iow_fseek(pc->fp_src,0);
            }else{
                EPRINT("%s - warning NULL fp",__FUNCTION__);
            }
            pc->line=0;
            pc->state=SS_READY;
            SCR_CLR_EVENTS(ps,SE_ALL);
            pc->error=EOK;

            // reset delay timer
            // (leave run timer, reset using sch/sync)
            tkTmrReset(TMR_PTR(pc,T_DELAY));
        }else{
            EPRINT("%s - warning NULL ctx",__FUNCTION__);
       }
       retval=ERR_SUCCESS;
    }else{
        EPRINT("%s - invalid arg hnd[%d]",__FUNCTION__,hnd);
    }
    return retval;
}

return_code_t script_jump(script_hnd_t hnd,int n)
{
    return_code_t retval=ERR_NO_IMPL;
    WMSG("not implemented");
    return retval;
}

return_code_t script_step(script_hnd_t hnd, int steps)
{
    return_code_t retval=ERR_INVALID;
    char *line=g_line_buffer;
    int test=1;
    if(SCR_HRANGE_VALID(hnd)){
        script_t *ps = &script_table[hnd];
        script_ctx_t *pc = ps->ctx;
        //SHDEBUG(hnd,"script_step - hnd[%d] ctx[%p] fp[%p/%p] ofs[%c] steps[%d]\r\n",hnd,pc,pc->fp_src,&pc->file,iow_ftell(pc->fp_src),steps);
        if(NULL!=pc && NULL!=pc->fp_src && steps!=0){
            // number of lines to read
            int read_lines=0;

            if(steps>0){
                read_lines=steps;
            }else {
                // DPRINT("script_step - !!! SEEKING 0\r\n");
                iow_fseek(pc->fp_src,0);
                if( abs(steps) >= ps->ctx->line ){
                   read_lines=1;
                   pc->line=0;
                }else{
                   read_lines=pc->line+steps;
                   pc->line=0;
                }
            }

            int i=0;
            int err=0;
            for(i=0;i<read_lines;i++){
                memset(line,0,SCR_LINE_BUFFER_BYTES);
                if( (test=script_read_line(hnd,line, SCR_LINE_BUFFER_BYTES,&err))>=0 ){
                    //SHDEBUG(hnd,"script_step - read [%s]\r\n",line);
                    // ctx line should always
                    // reflect ** number of lines already read **
                    pc->line++;
                    // don't parse comments (sets script error)
                    char *cp=line;
                    while(*cp!='\0' && isspace(*cp))cp++;
                    if(*cp=='#' || *cp=='/')test=0;
                }else{
                    if(iow_feof(pc->fp_src)){
                        if(strcmp(line,"END")==0){
                            SHDEBUG(hnd,"script_step EOF (unterminated END statement) err[%d] [%s]\r\n",err,line);
                            pc->line++;
                        }else{
                            EPRINT("invalid/missing END line test[%d] err[%d] [%s]",test,err,line);
                            pc->error=EREAD;
                            SCR_SET_EVENTS(ps,SE_ERROR);
                            retval=ERR_FAILURE;
                        }
                    }else{
                        // read error
                        SHDEBUG(hnd,"script_step - read test[%d] err[%d] [%s]\r\n",test,err,line);
                        pc->error=EREAD;
                        SCR_SET_EVENTS(ps,SE_ERROR);
                        retval=ERR_FAILURE;
                    }
                    break;// TODO
                }
            }

            if(test>0){
                if(script_parse_line(line, strlen(line), hnd)==ERR_SUCCESS){
                    SHDEBUG(hnd,"script_step - parse [%s]\r\n",line);
                    SCR_SET_EVENTS(ps,SE_PLAY);
                    retval=ERR_SUCCESS;
                }else{
                   SHDEBUG(hnd,"script_step - reject [%s][%d]\r\n",line,hnd);
                    // parse/exec error
                    pc->error=EEXEC;
                    SCR_SET_EVENTS(ps,SE_ERROR);
                }
            }
        }
    }
    return retval;
}


return_code_t script_update(script_hnd_t hnd)
{
    return_code_t retval=ERR_INVALID;

    if(SCR_HRANGE_VALID(hnd)){

        script_t *ps = &script_table[hnd];
        script_ctx_t *pc=NULL;
        script_action_t action=SA_NOP;
        int nsteps=0;
        pc = ps->ctx;
        if(NULL!=pc){

            retval=ERR_SUCCESS;

            serial_port_t save_ctty=g_ctty;
            // set output tty
            g_ctty=pc->otty;

            // update timers
            int j=0;
            for(j=0;j<SCR_TMR_COUNT;j++){
               tkTmrUpdate(TMR_PTR(pc,j));
            }

            if(pc->period>0 ){
                if( pc->timer[T_RUN].active!=FALSE && pc->timer[T_RUN].ticksLeft<=0){
                    // run time expired - reset, run
                    tkTmrSet(TMR_PTR(pc,T_RUN),pc->period);
                    tkTmrStart(TMR_PTR(pc,T_RUN));
                    SCR_SET_EVENTS(ps,SE_PLAY);
                }
            }

            // check state
            switch(pc->state){
                case SS_IDLE:
                case SS_READY:
                    // check event flags
                    if(SCR_MGET_EVENTS(ps,SE_PAUSE)){
                        pc->reg16[R16_B]=pc->state;
                        action=SA_PAUSE;
                        pc->state=SS_PAUSE;
                        break;
                    }
                    if(SCR_MGET_EVENTS(ps,SE_DELAY)){
                        action=SA_DELAY;
                        // save current state
                        pc->reg16[R16_B]=pc->state;
                        pc->state=SS_DELAY;
                        break;
                    }
                    // careful what you else...
                    if(SCR_MGET_EVENTS(ps,SE_PLAY)){

    //                     SHDEBUG(hnd,"%d - hnd[%d] ev[%04X] mo[%04X]\r\n",__LINE__,hnd,pc->events,pc->modes);
                        action=SA_STEP;
                        nsteps=1;
                        pc->state = SS_RUN;
                    }else if(SCR_MGET_EVENTS(ps,SE_FWD)){
                        // the number of steps is ctx reg16_A
                        action=SA_FWD;
                        nsteps=pc->reg16[R16_A];
                    }else if(SCR_MGET_EVENTS(ps,SE_REV)){
                        // the number of steps is ctx reg16_A
                       action=SA_REV;
                       nsteps=pc->reg16[R16_A];
                    }

                    if(SCR_MGET_EVENTS(ps,SE_END)){
                        // the number of steps is ctx reg16_A
                       action=SA_END;
                       pc->state = SS_READY;
                    }
                    break;

                case SS_RUN:
                    if(SCR_MGET_EVENTS(ps,SE_PAUSE)){
                        pc->reg16[R16_B]=pc->state;
                        action=SA_PAUSE;
                        pc->state=SS_PAUSE;
                       break;
                    }
                    if(SCR_MGET_EVENTS(ps,SE_DELAY)){
                      action=SA_DELAY;
                        // save current state
                        pc->reg16[R16_B]=pc->state;
                        pc->state=SS_DELAY;
                        break;
                    }
                    if(SCR_MGET_EVENTS(ps,SE_PLAY)){
                        action=SA_STEP;
                        nsteps=1;
                        pc->state = SS_RUN;
    //                    SHDEBUG(hnd,"%d - hnd[%d] ev[%04X] mo[%04X]\r\n",__LINE__,hnd,pc->events,pc->modes);
                    }
                    if(SCR_MGET_EVENTS(ps,SE_END)){
                        action=SA_END;
                        pc->state = SS_READY;
                    }
                    break;
                case SS_DELAY:
                       action=SA_DELAY;
                    break;
               case SS_PAUSE:
                    action=SA_PAUSE;
                    if(SCR_MGET_EVENTS(ps,SE_RESUME)){
                        action=SA_NOP;
                        pc->state = pc->reg16[R16_B];

                        SCR_CLR_EVENTS(ps,SE_RESUME);
                        tkTmrStart(TMR_PTR(pc,T_RUN));
    //                    SHDEBUG(hnd,"%d - hnd[%d] ev[%04X] mo[%04X]\r\n",__LINE__,hnd,pc->events,pc->modes);
                    }else if(SCR_MGET_EVENTS(ps,SE_DELAY)){
                        action=SA_DELAY;
                         // keep saved current state
//                        pc->reg16[R16_B]=pc->state;
                        pc->state=SS_DELAY;

                        tkTmrStart(TMR_PTR(pc,T_RUN));

                        break;
                    }else if(SCR_MGET_EVENTS(ps,SE_FWD)){
                        // the number of steps is ctx reg16_A
                        action=SA_FWD;
                        nsteps=pc->reg16[R16_A];
                    }else if(SCR_MGET_EVENTS(ps,SE_REV)){
                        // the number of steps is ctx reg16_A
                       action=SA_REV;
                       nsteps=pc->reg16[R16_A];
                    }
                    if(SCR_MGET_EVENTS(ps,SE_END)){
                         action=SA_END;
                         pc->state = SS_READY;
                         SCR_CLR_EVENTS(ps,SE_PAUSE);
                   }
                    break;
                case SS_INVALID:
                case SS_ERR:
                           TRACE();
                   break;
            }// switch state

            // do actions
            if(SCR_MGET_ACT(action,SA_PAUSE)){

                SCR_CLR_EVENTS(ps,SE_PAUSE);
                tkTmrStop(TMR_PTR(pc,T_RUN));

            }
            if(SCR_MGET_ACT(action,SA_STEP)){
                if(script_step(hnd, nsteps)!=0){
                    if(iow_feof(pc->fp_src)){
                        // reached end of file
                        script_reset(hnd);
                        retval=ERR_EOF;
                    }
                }
                if(SCR_MGET_MODES(ps,SM_MAN)){
                   // clear the play event if in manual mode
                    SCR_CLR_EVENTS(ps,SE_PLAY);
                }
            }
            if(SCR_MGET_ACT(action,SA_DELAY)){

                // check timer
                if(pc->timer[T_DELAY].ticksLeft<=0){
                   // stop/reset the timer
                    tkTmrReset(TMR_PTR(pc,T_DELAY));
                    // restore previous state)
                    pc->state = pc->reg16[R16_B];
                    pc->reg16[R16_B]=0;
                   // clear the delay event
                    SCR_CLR_EVENTS(ps,SE_DELAY);
                }
            }
            if(SCR_MGET_ACT(action,SA_END)){
                script_reset(hnd);
                retval=ERR_EOF;
            }
    //       if(SCR_MGET_ACT(action,SA_FWD)){
    //        SCR_CLR_EVENTS(script,SE_FWD);
    //        }
    //       if(SCR_MGET_ACT(action,SA_REV)){
    //        SCR_CLR_EVENTS(script,SE_REV);
    //        }
            g_ctty=save_ctty;
        }// else ctx NULL
    }// else invalid hnd
   return retval;
}

// check script execution status
// update state
return_code_t script_state_update()
{
    return_code_t retval=ERR_SUCCESS,test=ERR_FAILURE;
    int i=0;
//    g_ctty=SER_CONS;
//    TODO: set script output at runtime
    // iterate over script table
    for(i=0;i<SCR_SCRIPT_COUNT;i++){
        if( (test=script_update(i))!=ERR_SUCCESS){
            retval=test;
        }
    }
    return retval;
}

return_code_t script_set_name(script_hnd_t hnd, char *name)
{
    return_code_t retval=ERR_INVALID;
    if(SCR_HRANGE_VALID(hnd)){
        script_ctx_t *pc = script_table[hnd].ctx;
        if(NULL!=pc){
            memset(pc->name,0,SCR_PATH_BYTES);
            if(NULL!=name){
                strncpy(pc->name,name,SCR_PATH_BYTES-1);
                pc->name[SCR_PATH_BYTES-1]='\0';
            }
            retval=ERR_SUCCESS;
        }//else{DPRINT("NULL pc\r\n");}
    }//else{DPRINT("invalid hnd\r\n");}
    return retval;
}

int32_t script_run(script_hnd_t hnd)
{
    int retval = ERR_FAILURE;

    if(SCR_HRANGE_VALID(hnd)){
        script_t *ps = &script_table[hnd];
        if(NULL!=ps->ctx){
            retval=ERR_SUCCESS;
            SCR_SET_EVENTS(ps,SE_PLAY);
        }
    }
    return retval;
}

script_hnd_t script_lookup(char *src)
{
    script_hnd_t retval=SCR_HND_INVALID;
    int i=0;
    script_t *ps=script_table;
    for(i=0;i<SCR_SCRIPT_COUNT;i++,ps++){
        if(NULL!=ps->ctx && strcmp(ps->ctx->name,src)==0){
            retval=i;
        }
    }
    return retval;
}

script_state_t script_state(script_hnd_t hnd)
{
    script_state_t retval=SS_INVALID;
    if(SCR_HRANGE_VALID(hnd)){
        script_t *ps = &script_table[hnd];
        if(NULL!=ps->ctx){
            retval=ps->ctx->state;
        }
    }
    return retval;
}

/// find first unused slot in script table
/// return pointer, or NULL if none available
script_hnd_t script_alloc(script_t *pscr, int slot)
{
    script_hnd_t retval=SCR_HND_INVALID;
    int i=0;
    int start=0,end=SCR_SCRIPT_COUNT-1;
    script_t *ps=script_table;
    if(slot>=0){
        if(slot<SCR_SCRIPT_COUNT){
            start=end=slot;
            ps=&script_table[slot];
        }else{
            // invalidate, return error
            start=1;
            end=0;
        }
    }

    for(i=start;i<=end;i++,ps++){
        // ctx pointer NULL indicates avail
        if(NULL==ps->ctx){
            retval=i;
            ps->ctx = ps->ctx_table;
            ps->ctx->state=SS_IDLE;
            if(NULL!=pscr)
                pscr=&script_table[i];
            break;
        }
    }

    return retval;
}

return_code_t script_unload(script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;

    if( SCR_HRANGE_VALID(hnd) ){
        script_init(hnd,NULL);
        script_t *ps= SCR_HND2PS(hnd);
        if(NULL!=ps && NULL!=ps->ctx){
            iow_fclose(ps->ctx->fp_src);
            ps->ctx->fp_src=NULL;
            ps->ctx=NULL;
        }
        retval=ERR_SUCCESS;
    }
    return retval;
}

/// reserve a slot for a script
/// initialize script context
/// optionally overwrite the script if already loaded
script_hnd_t script_load(char *src, int slot, bool overwrite)
{
    script_hnd_t retval = SCR_HND_INVALID;
    bool ovwr_err=false;
    if(NULL!=src){

        script_hnd_t hnd=script_lookup(src);
        if( SCR_HRANGE_VALID(hnd) ){
            if(overwrite == false){
                EPRINT("script_load src/hnd[%s/%d] - addr conflict\r\n",src,hnd);
                ovwr_err=true;
            }
        }else{
            // find first empty slot
            hnd=script_alloc(NULL,slot);
        }

        if(!ovwr_err ){
            if( SCR_HRANGE_VALID(hnd) ){
                if(script_init(hnd,src)==ERR_SUCCESS){
                    retval=hnd;
                }else{
                    EPRINT("%s - init failed src[%s] hnd[%d]",__FUNCTION__,src,hnd);
                     script_unload(hnd);
                }
            }else{
                    EPRINT("%s - invalid hnd src[%s] hnd[%d]",__FUNCTION__,src,hnd);
                     script_unload(hnd);
            }
        }
    }
    return retval;
}

uint32_t script_next_event()
{
    uint32_t retval=SYS_MAX_SLEEP_MS;
    int i=0;
    uint32_t min_delay = SYS_MAX_SLEEP_MS;
    uint32_t delay=SYS_MAX_SLEEP_MS;
    script_t *ps = script_table;
    script_ctx_t *pc =NULL;

    for (i = 0; i < SCR_SCRIPT_COUNT; i++) {

        pc = ps->ctx;

        if (NULL!=pc && pc->state!=SS_IDLE) {

            if (SCR_MGET_MODES(ps,SM_NOSLEEP)  ) {
                // script NOSLEEP mode enabled
                // or is running (not delayed)
                return (0);
            }

            switch(pc->state){
                case SS_READY:
                    if(TMR_ACTIVE(pc,T_RUN)){
                        delay=tkTmrUpdate(TMR_PTR(pc,T_RUN));
                        min_delay = ( (delay<min_delay) ? delay : min_delay);
                    }
                    break;
                case SS_RUN:
                    min_delay = 0;
                    break;
                case SS_DELAY:
                   if(TMR_ACTIVE(pc,T_RUN)){
                    delay=tkTmrUpdate(TMR_PTR(pc,T_RUN));
                    min_delay = ( (delay<min_delay) ? delay : min_delay);
                   }
                   if(TMR_ACTIVE(pc,T_DELAY)){
                    delay=tkTmrUpdate(TMR_PTR(pc,T_DELAY));
                    min_delay = ( (delay<min_delay) ? delay : min_delay);
                   }
                    break;
                default:
                    break;
            }
        }
        // point to next script
        ps++;
        retval=min_delay;
    }

    return retval;
}

return_code_t script_goto(script_hnd_t hnd, char *label)
{
    return_code_t retval=ERR_FAILURE;

    if(NULL!=label && SCR_HRANGE_VALID(hnd) && SCR_CTX_VALID(hnd)){
        script_ctx_t *pc=SCR_HND2PCTX(hnd);
        FILE_T *fp = pc->fp_src;
        char *line = (char *)palloc(SCR_LINE_BUFFER_BYTES);
        if(NULL!=fp && NULL!=line){
            // save line number
            // in case we need to restore it
            uint16_t line_save=pc->line;
            pc->line=0;
            iow_fseek(fp,0);
            int32_t test=0;
            int err=0;
            while((test=script_read_line(hnd,line, SCR_LINE_BUFFER_BYTES,&err))>=0){
                pc->line++;
                // stop if label found
                if(line[0]==':' && strstr(line,label)==(line+1)){
                    retval=ERR_SUCCESS;
                    break;
                }
                ZERO_MEM(line,SCR_LINE_BUFFER_BYTES);
            }
            if(retval!=ERR_SUCCESS){
                EPRINT("label not found [%s]",label);
                // label not found, restore context
                // resume at next line
                pc->fp_src=fp;
                pc->line=0;
                iow_fseek(pc->fp_src,0);

                while((test=script_read_line(hnd,line, SCR_LINE_BUFFER_BYTES,&err))>=0){
                    pc->line++;
                    if(pc->line>=line_save){
                        break;
                    }
                    ZERO_MEM(line,SCR_LINE_BUFFER_BYTES);
                }
            }
            pfree(line);
        }
    }
    return retval;
}
