/*      This file is part of Juggluco, an Android app to receive and display         */
/*      glucose values from Freestyle Libre 2, Libre 3, Dexcom G7/ONE+ and           */
/*      Sibionics GS1Sb sensors.                                                     */
/*                                                                                   */
/*      Copyright (C) 2021 Jaap Korthals Altes <jaapkorthalsaltes@gmail.com>         */
/*                                                                                   */
/*      Juggluco 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.                                          */
/*                                                                                   */
/*      Juggluco 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.                         */
/*                                                                                   */
/*      You should have received a copy of the GNU General Public License            */
/*      along with Juggluco. If not, see <https://www.gnu.org/licenses/>.            */
/*                                                                                   */
/*      Fri Jun 20 17:37:44 CEST 2025                                                 */
#ifndef WEAROS

#include <array>
#include <time.h>
#include <stdio.h>
#include "logs.hpp"
#include "glucose.hpp"    
#include "destruct.hpp"
#include "settings/settings.hpp"
#include "sensoren.hpp"
#include "nums/num.h"
#include "nums/numdata.hpp"
#include "calibrate/Calibrate.hpp"
using namespace std::literals;
extern Sensoren *sensors;
/*
Glucose Data,Generated on,30-05-2025 09:31 UTC,Generated by,Jaap Korthals Altes
Device,Serial Number,Device Timestamp,Record Type,Historic Glucose mmol/L,Scan Glucose mmol/L,Non-numeric Rapid-Acting Insulin,Rapid-Acting Insulin (units),Non-
numeric Food,Carbohydrates (grams),Carbohydrates (servings),Non-numeric Long-Acting Insulin,Long-Acting Insulin (units),Notes,Strip Glucose mmol/L,Ketone mmol/L
,Meal Insulin (units),Correction Insulin (units),User Change Insulin (units)
*/


template <typename Printer> 
bool printheader(time_t t,std::string_view name,bool mmolL,const Printer &printer) {
    struct tm tmbuf;
    gmtime_r(&t, &tmbuf);
    const char *unitstr =unitlabels[mmolL?1:2].data();
    return printer(R"(Glucose Data,Generated on,%02d-%02d-%04d %02d:%02d UTC,Generated by,%s)""\r\n",tmbuf.tm_mday,tmbuf.tm_mon+1,tmbuf.tm_year+1900,tmbuf.tm_hour,tmbuf.tm_min,name.data())&&
printer(R"(Device,Serial Number,Device Timestamp,Record Type,Historic Glucose %s,Scan Glucose %s,Non-numeric Rapid-Acting Insulin,Rapid-Acting Insulin (units),Non-numeric Food,Carbohydrates (grams),Carbohydrates (servings),Non-numeric Long-Acting Insulin,Long-Acting Insulin (units),Notes,Strip Glucose %s,Ketone mmol/L,Meal Insulin (units),Correction Insulin (units),User Change Insulin (units))""\r\n",unitstr,unitstr,unitstr);
   }

/*
FreeStyle Libre 2,<SN>,04-09-2021 12:40,0,76,,,,,,,,,,,,,,
FreeStyle Libre 2,<SN>,15-07-2021 10:13,1,,142,,,,,,,,,,,,,
FreeStyle Libre 2,<SN>,16-07-2021 00:11,1,,150,,,,,,,,,,,,,
FreeStyle Libre 2,<SN>,14-07-2021 13:33,6,,,,,,,,,,,,,,,
FreeStyle Libre 2,<SN>,14-07-2021 20:14,6,,,,,,,,,,,,,,,
FreeStyle Libre 2,<SN>,03-09-2021 19:28,2,,,,,,,,,,,97,,,,
FreeStyle Libre 2,<SN>,14-09-2021 08:31,3,,,,,,,,,,,,0.1,,,
*/
extern std::array<char,36> &getDeviceID(bool libre3);

/*template <typename Printer>
static bool printhistory(const char *libretype,const char *deviceID,time_t t,float value,const Printer &printer) {
    struct tm tmbuf;
    localtime_r(&t, &tmbuf);
    return printer(R"(FreeStyle Libre%s,%.36s,%02d-%02d-%04d %02d:%02d,0,%.*f,,,,,,,,,,,,,,)""\n",libretype,deviceID,tmbuf.tm_mday,tmbuf.tm_mon+1,tmbuf.tm_year+1900,tmbuf.tm_hour,tmbuf.tm_min, gludecimal,value);
    }; */

static const char *libresuffix[] {"",""," 2"," 3"};
template <typename Printer>
static bool    librestartline(int libreversion,const char *deviceID,time_t t,const Printer &printer) {
    const char *libretype=libresuffix[libreversion];
    struct tm tmbuf;
    localtime_r(&t, &tmbuf);
    return printer(R"(FreeStyle Libre%s,%.36s,%02d-%02d-%04d %02d:%02d,)",libretype,deviceID,tmbuf.tm_mday,tmbuf.tm_mon+1,tmbuf.tm_year+1900,tmbuf.tm_hour,tmbuf.tm_min);
    }
template <typename Printer>
static bool printhistory(const int libretype,const char *deviceID,time_t t,float value,int decimal,const Printer &printer) {
    return librestartline(libretype,deviceID,t,printer) &&
     printer(R"(0,%.*f,,,,,,,,,,,,,,)""\r\n",decimal,value);
    };


std::string_view getUserName() {
//    return "Otto Normalverbraucher"sv;
    return "Firstname Surname"sv;
    }
static int libreVersion=2;
template <typename Printer>
bool libreviewCGMexport(const uint32_t starttime,const uint32_t endtime,const bool header,const int unit,const bool overlap,const bool calibrate,const Printer &printer) {    

    if(header) {
        const auto name=getUserName();
        if(!printheader(time(nullptr), name,unit==1,printer) ) {
            return false;
            }
         }
    auto indices=sensors->sensorsInPeriod(    starttime,endtime);
    int totsen=indices.size();
    LOGGER("libreviewCGMexport start time=%u endtime=%u %d indices overlap=%d\n",starttime,endtime,totsen,overlap);
    const int decimal=unit==1;
    uint32_t nextstart=starttime;

    const bool CalibratePast=settings->data()->CalibratePast;
    for(int id=totsen-1;id>=0;--id) {
        const int index=indices[id];
        if(SensorGlucoseData *sens=sensors->getSensorData(index)) {
            const char *deviceID=getDeviceID(!sens->isLibre2()).data();
            if(!sens->hasRealHistory()) {
                CalibrateForward<ScanData> cali(sens, CalibratePast);
                const int librev=3;
                libreVersion=librev;
                const CurData  inper=sens->streamInperiod(nextstart, endtime); 
                const ScanData *en=inper.end();
                uint32_t prevtime=0;
                for(const ScanData *iter=inper.begin();iter<en;++iter) {
                    if(iter->valid()) {
                         uint32_t tim=iter->gettime();
                         if((tim-prevtime)<280)
                                continue;
                          prevtime=tim; 
                          int mgL;
                          if(double calibrated;calibrate&&!isnan(calibrated=cali.forwardvalue(*iter))) {
                                mgL=(int)round(calibrated*10.0);
                                 }
                          else
                                mgL=iter->getmgL();

                         float value=gconvert(mgL,unit);
                         if(!printhistory(librev,deviceID,tim,value,decimal,printer))
                                return false;
                          }
                       }
                  if(prevtime)
                          nextstart=prevtime;
                 }
            else {
                CalibrateForward<Glucose> cali(sens, CalibratePast);
                 bool isLibre3=(sens->getinfo()->interval==SensorGlucoseData::interval5);
                const int librev=isLibre3?3:(sens->shortsensorname()->data()[0]=='0'?1:2);
                libreVersion=librev;
                 const int startpos=sens->getfirstnotbeforetime(nextstart);
                 const int endpos=sens->getfirstnotbeforetime(endtime);
                 for(int pos=startpos;pos<endpos;pos++) {
                    const Glucose *gl = sens->getglucose(pos);

    int mgL;
    if(double calibrated;calibrate&&!isnan(calibrated=cali.forwardvalue(*gl))) {
        mgL=(int)round(calibrated*10.0);
         }
    else
        mgL=gl->getmgL();

                    const     float value=gconvert(mgL,unit);

                    const time_t tim= gl->gettime();
                    nextstart=tim;
                    if(gl->valid())
                        if(!printhistory(librev,deviceID,tim,value,decimal,printer))
                            return false;
                    }
                 }
            if(overlap)
                nextstart=starttime;
            }
    }    
   return true;
    }



template <typename Printer>
static bool printlong(float value,const Printer &printer) {
    return printer(R"(4,,,,,,,,,%.1f,,,,,,)""\r\n",value);
    }
template <typename Printer>
static bool printrapid(float value,const Printer &printer) {
    return printer(R"(4,,,,%.1f,,,,,,,,,,,)""\r\n",value);
    }

template <typename Printer>
static bool printcarbo(float value,const Printer &printer) {
    return printer(R"(5,,,,,,%.0f,,,,,,,,,)""\r\n",value);
    }
template <typename Printer>
static bool printnote(const Num *num,const Printer &printer) {
    const std::string_view typestr=settings->getlabel(num->type);
    return printer(R"(6,,,,,,,,,,%s %g,,,,,)""\r\n",typestr.data(),num->value);
    }
template <typename Printer>
bool writenum(int libreversion,const char *deviceid,const Num *num,const Printer &printer) {
    const uint32_t type=num->type; 
    if(type>=settings->varcount())
        return true;
    const int kind=settings->data()->librenums[type].kind;
    if(kind<1||kind>4)
        return true;
    if(kind!=4&&!isnormal(num->value))
        return true;
    if(!librestartline(libreversion,deviceid,num->time,printer))
        return false;
    switch(kind) {
       case 1: return printrapid(num->value,printer);
       case 2: return printlong(num->value,printer);
       case 3: return printcarbo(num->value*settings->data()->librenums[type].weight,printer);
       case 4: return printnote(num,printer);
        }
    return false;
    }
extern std::vector<Numdata*> numdatas;
template <typename Printer>
bool librenumexport(uint32_t starttime,uint32_t endtime,const Printer &printer) {    
    const int librev=libreVersion;
    const char *deviceID=getDeviceID(librev==3).data();
    for(const auto *numsptr:numdatas) {
        auto [beg,en]= numsptr->getInRange(starttime,endtime);
        for(const auto *iter=beg;iter<en;++iter) {
            if(numsptr->valid(iter)) {
                if(!writenum(librev,deviceID,iter,printer))
                    return false;
                }
            }
        }
     return true;
    }

template <typename Printer>
bool libreviewexport(uint32_t starttime,uint32_t endtime,bool header,int unit,bool overlap,const bool calibrate,const Printer &printer) {    
        return libreviewCGMexport(starttime,endtime,header,unit,overlap,calibrate,printer)&&
            librenumexport(starttime,endtime,printer) ;   
        }

bool libreviewexport(int handle,uint32_t starttime,uint32_t endtime,const bool calibrate) {    
        FILE *fp=fdopen(handle,"w");
        if(!fp) {
            LOGAR("libreviewexport: fdopen failed");
            close(handle);
            return false;
            }
        destruct _{[fp]{fclose(fp);}};
        const int unit=settings->data()->unit;
        return libreviewexport(starttime,endtime,true,unit,false,calibrate,[fp](auto ...args) {
                return fprintf(fp,args...)>0;
                });
         }

std::span<char> libreviewweb(int startpos, int startlen, uint32_t starttime, uint32_t endtime,bool header,int unit,bool overlap,int maxcount=INT_MAX,bool calibrate=false) {
    struct {
        char *buf;
        int max;
        int iter;
        } printdata{new(std::nothrow) char [startlen],startlen,startpos};
    if(!printdata.buf)
        return {(char *)nullptr,(size_t)0};
     if(!libreviewexport(starttime,endtime,header,unit,overlap,calibrate,[&printdata](auto ...args) {
            while(true) {
                int left=printdata.max-printdata.iter; 
                int wrote=snprintf(printdata.buf+printdata.iter,left,args...);
                if(wrote<=0)
                    return false;
                if(wrote>=left) {
                    printdata.max*=2;
                    char *tmp=new(std::nothrow) char [printdata.max];
                    if(!tmp) {
                        delete[] printdata.buf;
                        printdata.buf=nullptr;
                        return  false;
                        }
                    memcpy(tmp,printdata.buf,printdata.iter);
                    delete[] printdata.buf;
                    printdata.buf=tmp;
                    }
                else {
                    printdata.iter+=wrote;
                    return true;
                    }
                }
                }))  {
            return {printdata.buf,std::numeric_limits<size_t>::max()};
            }
    return {printdata.buf,(size_t)printdata.iter};
    }

#endif
