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

/// System code module

/// @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 "script.h"
#include "micron.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"
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include "cwgsys.h"
#include "config.h"
#include "ioserial.h"
#include "rtc.h"
#include "timers.h"
#include "iowfile.h"


#define POOL_MAX_LEN  1024
#define NBYTES(n) ((n/8)+(n%8==0?0:1))
#define SYS_LC_CHUNK_BYTES 1024

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

typedef struct pmeminf_s{
    uint16_t res_size;
    uint16_t res_count;
    byte **res_table;
    byte *res_mem;
}pmeminf_t;

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

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

uint16_t g_rcon=0;
serial_port_t g_ctty=SER_CONS;
bool g_sdc_ok=false;

// buffer pool storage
#if POOL_16_COUNT > 0
byte mem_16[POOL_16_COUNT][16];
byte *pmem_16[POOL_16_COUNT];
#endif

#if POOL_32_COUNT > 0
byte mem_32[POOL_32_COUNT][32];
byte *pmem_32[POOL_32_COUNT];
#endif
#if POOL_64_COUNT > 0
byte mem_64[POOL_64_COUNT][64];
byte *pmem_64[POOL_64_COUNT];
#endif
#if POOL_128_COUNT > 0
byte mem_128[POOL_128_COUNT][128];
byte *pmem_128[POOL_128_COUNT];
#endif
#if POOL_256_COUNT > 0
byte mem_256[POOL_256_COUNT][256];
byte *pmem_256[POOL_256_COUNT];
#endif
#if POOL_512_COUNT > 0
byte mem_512[POOL_512_COUNT][512];
byte *pmem_512[POOL_512_COUNT];
#endif
#if POOL_1024_COUNT > 0
byte mem_1024[POOL_1024_COUNT][1024];
byte *pmem_1024[POOL_1024_COUNT];
#endif
#if POOL_2048_COUNT > 0
byte mem_2048[POOL_2048_COUNT][2048];
byte *pmem_2048[POOL_2048_COUNT];
#endif

pmeminf_t pmeminf[]={
 #if POOL_16_COUNT > 0
   {16,POOL_16_COUNT,pmem_16,(void *)&mem_16[0]},
#endif
#if POOL_32_COUNT > 0
    {32,POOL_32_COUNT,pmem_32,(void *)&mem_32[0]},
#endif
#if POOL_64_COUNT > 0
    {64,POOL_64_COUNT,pmem_64,(void *)&mem_64[0]},
#endif
#if POOL_128_COUNT > 0
    {128,POOL_128_COUNT,pmem_128,(void *)&mem_128[0]},
#endif
#if POOL_256_COUNT > 0
    {256,POOL_256_COUNT,pmem_256,(void *)&mem_256[0]},
#endif
#if POOL_512_COUNT > 0
    {512,POOL_256_COUNT,pmem_512,(void *)&mem_512[0]},
#endif
#if POOL_1024_COUNT > 0
    {1024,POOL_1024_COUNT,pmem_1024,(void *)&mem_1024[0]},
#endif
#if POOL_2048_COUNT > 0
    {2048,POOL_2048_COUNT,pmem_2048,(void *)&mem_2048[0]},
#endif
    {0,0,NULL,NULL}
};

/////////////////////////
// Function Definitions
/////////////////////////
return_code_t sys_init()
{
    return_code_t retval=ERR_SUCCESS;
    int i=0;
#if POOL_16_COUNT > 0
    for(i=0;i<POOL_16_COUNT;i++){
        ZERO_MEM(mem_16[i],16);
        pmem_16[i]=NULL;
    }
#endif
#if POOL_32_COUNT > 0
    for(i=0;i<POOL_32_COUNT;i++){
        ZERO_MEM(mem_32[i],32);
        pmem_32[i]=NULL;
    }
#endif
#if POOL_64_COUNT > 0
    for(i=0;i<POOL_64_COUNT;i++){
        ZERO_MEM(mem_64[i],64);
        pmem_64[i]=NULL;
    }
#endif
#if POOL_128_COUNT > 0
    for(i=0;i<POOL_128_COUNT;i++){
        ZERO_MEM(mem_128[i],128);
        pmem_128[i]=NULL;
    }
#endif
#if POOL_256_COUNT > 0
    for(i=0;i<POOL_256_COUNT;i++){
        ZERO_MEM(mem_256[i],256);
        pmem_256[i]=NULL;
    }
#endif
#if POOL_512_COUNT > 0
    for(i=0;i<POOL_512_COUNT;i++){
        ZERO_MEM(mem_512[i],512);
        pmem_512[i]=NULL;
    }
#endif
#if POOL_1024_COUNT > 0
    for(i=0;i<POOL_1024_COUNT;i++){
        ZERO_MEM(mem_1024[i],1024);
        pmem_1024[i]=NULL;
    }
#endif
#if POOL_2048_COUNT > 0
    for(i=0;i<POOL_2048_COUNT;i++){
        ZERO_MEM(mem_2048[i],2048);
        pmem_2048[i]=NULL;
    }
#endif
    return retval;
}

return_code_t pshow_orig(char *arg)
{
   pmeminf_t *pinf = pmeminf;
    while(NULL!=pinf && pinf->res_size!=0){
        int i=0;
        int j=0;
        OUT_CTTY("res.size[%hu] @[%04p] res.count=[%hu] \r\n",pinf->res_size,&pinf->res_mem[0],pinf->res_count);
        for(i=0;i<pinf->res_count;i++){
            OUT_CTTY("  [%02d] pr[%04p]",i,&pinf->res_table[i][0]);
            if(NULL!=pinf && NULL!=pinf->res_table[i]){
                for(j=0;j<pinf->res_size;j++){
                        if(j%16==0)
                            OUT_CTTY("\r\n    %04d: ",j);
                        if(pinf->res_table[i]>=&pinf->res_mem[0] && pinf->res_table[i]<=&pinf->res_mem[i*pinf->res_size]){
                            OUT_CTTY(((pinf->res_table[i][j]>=0x20)?"%2c ":"%02X "),pinf->res_table[i][j]);
                        }else{
                            EPRINT("\r\nmemory pool pointer out of bounds [%04p]",pinf->res_table[i]);
                            EPRINT("          (did you serPrintf > %d bytes?)",SER_PRINTF_BUFFER_BYTES);
                            break;
                        }
                }
            }
            OUT_CTTY("\r\n");
        }
        pinf++;
    }
   return ERR_SUCCESS;
}

return_code_t pshow(int argc, char **argv){
    return pshow_orig(NULL);
}

void *palloc(size_t len)
{
    byte *retval=NULL;
//    DPRINT("%s(%zu)\r\n",__FUNCTION__,len);
    if(len>0 && len<=POOL_MAX_LEN){
        // iterate over pool sizes
        // find smallest available
        pmeminf_t *pinf = pmeminf;
        while(pinf->res_size!=0 && NULL==retval){
            if(pinf->res_size>=len){
                int i=0;
                for(i=0;i<pinf->res_count;i++){
                   if(NULL==pinf->res_table[i]){
                       // mark allocated
                       pinf->res_table[i]=(&pinf->res_mem[i*pinf->res_size]);
                       // zero-fill
                       ZERO_MEM(pinf->res_table[i], pinf->res_size);
                       retval=pinf->res_table[i];
//                       DPRINT(">>>>>>%s - allocating[%ld -> %ld][%04p]\r\n",len,pinf->res_size,retval);
                       break;
                    }
                }
            }
            pinf++;
        }
    }// else invalid
    return (void *)retval;
}

void pfree(void *ptr)
{
    if(NULL!=ptr)
    {
        byte *cptr = (byte *)ptr;
//        DPRINT("%s - cptr[%04p]\r\n",__FUNCTION__,cptr);
       pmeminf_t *pinf = pmeminf;
        bool quit=false;
        while(!quit && pinf->res_size!=0){
            // find the relevant table
            byte *tbl_first=(&pinf->res_mem[0]);
            byte *tbl_last = (&pinf->res_mem[(pinf->res_count-1)*pinf->res_size] );
//            DPRINT("%s - t_first[%04p] t_last[%04p]\r\n",__FUNCTION__,tbl_first,tbl_last);
            if( (cptr >= tbl_first) && (cptr <= tbl_last) ){

                // find the relevant entry
                int i=0;
                for(i=0;i<pinf->res_count;i++){
                    if(cptr==pinf->res_table[i]){
//                        DPRINT("%s - cptr[%04p] res_tbl[%d][%04p]\r\n",__FUNCTION__,cptr,i,pinf->res_table[i]);
                        // zero-fill
                        ZERO_MEM(cptr,pinf->res_size);
                        // mark available
                        pinf->res_table[i]=NULL;

                        quit=true;
                       break;
                    }
                }
            }
        pinf++;
        }
    }
    return;
}

char *ptstrdup(const char *src, bool trim_leading, bool trim_trailing)
{
    char *retval=NULL;

    if(NULL!=src){
       const char *ph=src;
       // point tail at NULL
       const char *pt=src+strlen(src);
       size_t len=0;

       // trim leading whitespace
       if(trim_leading){
           while(isspace(*ph) && *ph!='\0')ph++;
       }
       // trim trailing whitespace
       if(trim_trailing){
           while(pt>ph && isspace(*(pt-1)) ){
                pt--;
            }
            // don't touch src
            // *pt='\0';
        }
        // total (trimmed allocation) length, including NULL
        len=(pt-ph+1);
        if(len>1){
           // if not empty (1+NULL)
           // allocate and copy contents (w/o NULL)
           retval = (char *)palloc(len);
           if(NULL!=retval){
              memcpy(retval,ph,len);
              // NULL terminate copy
              retval[len-1]='\0';
           }
        }
     }
    return retval;
}

char *pstralloc(char *start, char *end)
{
    char *retval=NULL;
    if(NULL!=start && NULL!=end){
        int slen=(end-start+1);
        if( slen>0){
        retval=(char *)palloc(slen+1);
        if(NULL!=retval){
            memcpy(retval,start,slen);
            retval[slen]='\0';
        }
        }else{EMSG("end<start");}
    }
    return retval;
}

char *pargcat(int argc, char **argv, char *sep)
{
    char *retval=NULL;
    if(argc>0 && NULL!=argv){
        size_t slen=0;
        int i=0;
        for(i=0;i<argc;i++){
            slen=strlen(argv[i])+(NULL!=sep?strlen(sep):0);
        }

        if((retval=(char *)palloc(slen+1))!=NULL){
            for(i=0;i<argc;i++){
                strcat(retval,argv[i]);
//                DPRINT("pargcat [%s/%d/%d]\r\n",argv[i],strlen(argv[i]),strcmp(argv[i],"\""));
                // special case for single quote string
                // [don't append separator if next arg is single quote]
                // this will probably bite us later...
                if((NULL!=sep) && (  (i<(argc-1)) && (strcmp(argv[i+1],"\"" )!=0) ) )strcat(retval,sep);
            }
        }
    }
    return retval;
}

/// line count
uint32_t sys_count_lines(char* path, uint32_t *ms_out)
{
    uint32_t retval=0;

    #if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
    FILE_T file_s, *fp=&file_s;
#else
    FILE_T *fp;
#endif
    if(NULL!=path){
        if(iow_fopen(&fp,path,IOW_READ)==0){
            char *buf=(char *)palloc(SYS_LC_CHUNK_BYTES);
            char *pl=NULL;
            unsigned int bytes_read=0;
            uint32_t ticks[2]={0};

            // clear serial input flags
            // (will stop if set, i.e. on serial activity)
            SER_RX_CLR(SER_RX_ANY);

            // measurements showed that reading into memory and counting newlines
            // can count ~100 lines/s
            // ~3x faster than using fgets
            // >2x faster than reading char by char
            // (FatFS does not implement fgetc, must use f_read)

            ticks[0]=tkGetTicks();
            while( (bytes_read=iow_fread(fp,buf,SYS_LC_CHUNK_BYTES))>0 ){
                pl=buf;
                while(pl<(buf+bytes_read)){
                    if( *pl=='\n')
                        retval++;
                    pl++;
                }

                // stop on serial activity
                if(SER_RX(SER_RX_ANY)){
                    EPRINT(" (interrupted)",__FUNCTION__);
                    retval=0;
                    break;
                }

            }

            ticks[1]=tkGetTicks();

//            serPrintf(SER_CONS,"ticks[%d] %lu\r\n",0,ticks[0]);
//            serPrintf(SER_CONS,"ticks[%d] %lu\r\n",1,ticks[1]);
            if(NULL!=ms_out){
                *ms_out = (ticks[1]-ticks[0])*MS_PER_TICK;
            }

            iow_fclose(fp);
            pfree(buf);
        }
    }
    return retval;
}

void sys_show_tokens(const char *caller, int argc, char **argv)
{
    int i = 0;

    if(NULL!=caller)serPrintf(SER_CONS,"%s tokens[%d]:\r\n",caller,argc);
    for (i=0;i<argc;i++) {
        serPrintf(SER_CONS,"   [%d] [%s]\r\n",i,argv[i]);
    }
    return;
}

void sys_set_sdc_ok(bool is_ok)
{
  g_sdc_ok=is_ok;
}

bool sys_sdc_ok()
{
  return g_sdc_ok;
}

uint16_t sys_hw_serial_number()
{
  return SYS_HW_SERIAL_NUMBER;
}

const char *sys_sw_config()
{
  return SYS_SW_CONFIG_STR;
}

const char *sys_sw_version()
{
  return SYS_SW_VERSION_STR;
}

const char *sys_sw_tag()
{
  return SYS_SW_TAG_STR;
}

void sys_banner()
{
    uint16_t sn=0;
    config_get(CK_SERIAL_N, &sn);
    OUT_CTTY("\r\n MBARI CO2/DIC\r\n");
    OUT_CTTY(" firmware[ %s v%s tag %s]\r\n",sys_sw_config(),sys_sw_version(),sys_sw_tag());
    OUT_CTTY(" build   [ %s - %s ]\r\n", __DATE__, __TIME__);
    OUT_CTTY(" status  [ %04X ]\r\n", g_rcon);
    OUT_CTTY(" cfg_sn  [ %04X ] \r\n", sn);
    OUT_CTTY(" sys_sn  [ %04X ]\r\n", sys_hw_serial_number());
}

 uint32_t sys_next_event()
 {
    uint32_t retval=0;
    // get next scheduled event
    uint32_t next_scr_event = script_next_event();
    uint32_t next_cron_event = SYS_MAX_SLEEP_MS;
#ifdef WITH_MICRON
    if(micron_get_enable(CRON_HND_DFL)){
        next_cron_event=micron_next_event(CRON_HND_DFL, NULL);
    }
#endif
    // select shorter of : next script, next cron
    // if cron disabled, use SYS_MAX_SLEEP_MS for cron
    retval = (next_scr_event<next_cron_event ? next_scr_event : next_cron_event) ;
    MPRINT(MOD_SYS,"\r\n%s - next[%lu ms] scr[%lu ms] cron[%lu ms]\r\n", __func__, retval, next_scr_event,next_cron_event);
    return retval;
 }