/*
  xdrv_52_3_tf_lite_micro.ino - Berry scripting language, High-Level Tensor Flow Lite for Microprocessors model deployer

  Copyright (C) 2022 Christian Baars & Stephan Hadinger, Berry language by Guan Wenliang https://github.com/Skiars/berry

  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.

  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.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#ifdef USE_BERRY

#include <berry.h>

#ifdef USE_BERRY_TF_LITE

#include <TensorFlowLite.h>
#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/micro/system_setup.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "freertos/ringbuf.h"
#include "tensorflow/lite/c/common.h"

#ifdef USE_I2S
#include "mfcc.h"
#endif //USE_I2S

/*********************************************************************************************\
 * Internal helper classes and constants
\*********************************************************************************************/
#ifdef USE_I2S

#define kObservationWindow    1000    //milliseconds
#define kAudioSampleFrequency 16000   
#define kAudioSampleBits      16

struct TFL_mic_descriptor_t{
  uint8_t channel_fmt;      // UNUSED NOW !!!
  uint8_t preamp;           // UNUSED NOW !!!
  uint8_t slice_dur;        // milliseconds
  uint8_t slice_stride;     // milliseconds
  uint8_t num_filter;       // mfe bins
  uint8_t num_coeff;        // mfcc coefficients, if 0 -> compute MFE only
  uint8_t fft_bins;         // 2^fft_bins
  uint8_t max_invocations;  // max. invocations per second
  uint8_t db_floor;        // filter out noise below decibel threshold, treated as negative value
  uint8_t preemphasis;     //  as <float>preemphasis/100.0f , 0 - no preemphasis
};

struct TFL_mic_ctx_t{
  TaskHandle_t audio_capture_task = nullptr;
  SemaphoreHandle_t feature_buffer_mutex = nullptr;
  MFCC * mfcc = nullptr;
  int8_t* model_input_buffer = nullptr;

  union{
    struct {
      uint32_t is_audio_initialized:1;
      uint32_t is_first_time:1;
      uint32_t new_feature_data:1;
      uint32_t continue_audio_capture:1;
      uint32_t stop_audio_capture:1;
      uint32_t audio_capture_ended:1;
      uint32_t use_mfcc:1;
      uint32_t use_gain_filter:1;
    } flag;
    uint32_t flags;
  };
  int feature_buffer_idx = 0;
  int8_t *feature_buffer;
  // user input
  // int32_t channel_fmt;  // UNUSED
  int32_t preamp;       // setup by I2S driver
  int32_t slice_dur;    // milliseconds
  int32_t slice_stride; // milliseconds
  uint8_t num_filter;   // mfe filter bins
  uint8_t num_coeff;    // mfcc coefficients, if 0 -> compute MFE only
  int32_t fft_bins;     // 2^fft_bins
  // calculated
  int32_t slice_size;   // bytes
  int32_t slice_count;
  uint16_t i2s_samples_to_get;
  int16_t db_floor;    // filter out noise below decibel threshold, this is now a negative value
  float preemphasis;
};

#endif //USE_I2S

struct TFL_stats_t{
  uint32_t model_size = 0;
  uint32_t used_arena_bytes = 0;
  uint32_t invokations = 0;
  uint32_t loop_task_free_stack_bytes = 0;
  uint32_t mic_task_free_stack_bytes = 0;
};

struct TFL_ctx_t{
const tflite::Model* model = nullptr;
TfLiteTensor* input = nullptr;
TfLiteTensor* output = nullptr;
int8_t *berry_output_buf = nullptr;
size_t berry_output_bufsize;
int TensorArenaSize = 2000;
uint8_t max_invocations;    // max. invocations per second

TaskHandle_t loop_task = nullptr;
// QueueHandle_t loop_queue = nullptr;
union{
    struct {
    uint32_t init_done:1;
    uint32_t delay_next_invocation:1;
    uint32_t running_invocation:1;
    uint32_t running_loop:1;
    // uint32_t stop_loop:1;
    uint32_t loop_ended:1;
    uint32_t unread_output:1;
    uint32_t use_cam:1;
    uint32_t use_mic:1;
    uint32_t mode_inference:1;
    } option;
    uint32_t options;
};
#ifdef USE_I2S
TFL_mic_ctx_t *mic = nullptr;
#endif // USE_I2S
TFL_stats_t *stats = nullptr;
};

TFL_ctx_t *TFL = nullptr;
RingbufHandle_t TFL_log_buffer = nullptr;


/*********************************************************************************************\
 * Internal driver functions
\*********************************************************************************************/

/**
 * @brief This function is called from Microprint() from the Tensorflow framework
 *        Used to log from Tensorflow and from this (Tasmota) driver
 * 
 * @param s - message as c-string
 */
void TFL_Log(char *s){
  size_t len = strlen(s);
  if(len<5) return; // we assume this is for the trash

  xRingbufferSend(TFL_log_buffer, s, len+1 , pdMS_TO_TICKS(3));
}

bool TFL_create_task(){
    if (TFL->option.running_loop) return bfalse;
    if(TFL->loop_task!=nullptr) vTaskDelete(TFL->loop_task);
    
    xTaskCreatePinnedToCore(
    TFL_task_loop,                /* Function to implement the task */
    "tfl_loop",                   /* Name of the task */
    8000 + (TFL->TensorArenaSize),/* Stack size in words */
    NULL,                         /* Task input parameter */
    1,                            /* Priority of the task */
    &TFL->loop_task,              /* Task handle. */
    1);                           /* Core where the task should run */

    return btrue;
}

bool TFL_init_CAM(){
    AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: mode webcam not implemented yet"));
    delete TFL;
    TFL = nullptr;
    return bfalse;
}

#ifdef USE_I2S
/**
 * @brief Set up some buffers and tables for feature extraction of audio samples. Must run once before starting audio capturing.
 * 
 * @return int - not used ATM
 */
int TFL_InitializeFeatures() {
  uint32_t samples_to_process = (TFL->mic->i2s_samples_to_get * TFL->mic->slice_dur)/TFL->mic->slice_stride;
  TFL->mic->mfcc = new MFCC(TFL->mic->num_coeff, samples_to_process, TFL->mic->num_filter, kAudioSampleFrequency, 300, 8000);
  TFL->mic->mfcc->set_preamp(TFL->mic->preamp);
  TFL->mic->mfcc->set_preemphasis(TFL->mic->preemphasis);
  MicroPrintf( PSTR( "MFCC %u initialized for %u samples, preamp: %u, preemphasis: %f"),TFL->mic->num_coeff,samples_to_process, TFL->mic->preamp, TFL->mic->preemphasis);
  return kTfLiteOk;
}

/**
 * @brief Computes features from every audio slice immediately after capturing it.
 * 
 * @param input - audio buffer
 * @param input_size - length auf audio input in samples (16-bit)
 * @param output_size - length of feature buffer in bytes (we use int8_t quantization)
 * @param output - feature buffer for one slice of audio
 * @param num_samples_read - not used anymore, to be removed
 * @return int 
 */
int TFL_GenerateFeatures(const int16_t* input, int input_size,
                                   int output_size, int8_t* output,
                                   size_t* num_samples_read) {

  float out_buf[output_size];

  TFL->mic->mfcc->mfcc_compute(input, out_buf);
  if(TFL->mic->num_coeff == 0){ // mfe only
    TFL->mic->mfcc->log10_normalize(out_buf, output_size, TFL->mic->db_floor);
  }
   
  float scale = TFL->input->params.scale;
  int32_t zero_point =  TFL->input->params.zero_point;
  float min_f = 1;
  float max_f = 0;

  for (size_t i = 0; i < output_size; ++i) {
    int32_t value = ((out_buf[i]/ scale) + zero_point);
    if(value <  -128){
      value = -128;
    }
    else if(value > 127){
      value = 127;
    }
    output[i] = value;
    // if(min_f>out_buf[i]) min_f = out_buf[i];
    // if(max_f<out_buf[i]) max_f = out_buf[i];
  }
  // MicroPrintf("%f %f %f %f %f %f %f %f %f %f %f %f %f",out_buf[0],out_buf[1] ,out_buf[2] ,out_buf[3] ,out_buf[4] ,out_buf[5] ,out_buf[6] ,out_buf[7] ,out_buf[8] ,out_buf[9] ,out_buf[10] ,out_buf[11] ,out_buf[12]);
  // MicroPrintf("min: %f max: %f",min_f,max_f);

  return kTfLiteOk;
}

/**
 * @brief Init I2S microphone. Pins must be configured in the "usual" Tasmota way. Some properties are variables stored in the descriptor.
 * 
 * @param descriptor - byte array passed from Berry. Arbitrary format - might change in the future!!
 * @return true - success
 * @return false - failure
 */
bool TFL_init_MIC(const uint8_t* descriptor){
   if (audio_i2s.in) {
      if(audio_i2s.in->getRxRate() != kAudioSampleFrequency || audio_i2s.in->getRxBitsPerSample() != kAudioSampleBits){
        AddLog(LOG_LEVEL_ERROR, PSTR("TFL: please configure microphone to 16 bits per sample at 16000 Hz"));
        return bfalse;
      }
      audio_i2s.in->startRx();
      AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: init mic"));
   }
   else{
      AddLog(LOG_LEVEL_ERROR, PSTR("TFL: could not connect to I2S driver"));
      return bfalse;
   }
  
  TFL->mic = new TFL_mic_ctx_t;
  TFL->mic->flags = 0;
  TFL_set_mic_config(descriptor);

  TFL->mic->feature_buffer_mutex = xSemaphoreCreateMutex();

  AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: MIC ctx created"));
  return btrue;
}

/**
 * @brief Function spawned as a task for capturing audio. Used for recording or inference.
 * 
 * @param arg - not used
 */
void TFL_capture_samples(void* arg) {
  MicroPrintf( PSTR( "Capture task started"));
  const int i2s_bytes_to_read = TFL->mic->i2s_samples_to_get * 2;  // according to slice duration

  const int buffer_size = (i2s_bytes_to_read * TFL->mic->slice_dur)/TFL->mic->slice_stride; // in bytes, current slice duration plus (potential) history data

  size_t samples_to_read;
  size_t bytes_read;
  int tf_status = 0;

  int16_t i2s_sample_buffer[buffer_size/2] = {0}; // in shorts, add the size to hold history data
  uint8_t  *i2s_byte_buffer = (uint8_t*)i2s_sample_buffer;
  uint32_t *i2s_long_buffer = (uint32_t*)i2s_sample_buffer;
  uint8_t  *i2s_read_buffer = i2s_byte_buffer + (buffer_size - i2s_bytes_to_read); // behind the history data, if slice duration != slice stride

  TFL_InitializeFeatures(); // TODO: check or not for success

  TFL->mic->flag.continue_audio_capture = 1;
  MicroPrintf( PSTR( "Enter capture samples loop"));

  while (TFL->mic->flag.continue_audio_capture == 1) {
    TFL->stats->mic_task_free_stack_bytes = uxTaskGetStackHighWaterMark(NULL);
    TickType_t xLastWakeTime = xTaskGetTickCount();

    /* read slice data at once from i2s */
    // i2s_read(I2S_NUM, i2s_read_buffer, i2s_bytes_to_read, &bytes_read, pdMS_TO_TICKS(TFL->mic->slice_stride));
    i2s_channel_read(audio_i2s.in->getRxHandle(), (void*)i2s_read_buffer, i2s_bytes_to_read, &bytes_read, pdMS_TO_TICKS(TFL->mic->slice_stride));

    if (bytes_read <= 0) {
      MicroPrintf( PSTR( "Error in I2S read : %d"), bytes_read);
    }
    else {
      if (bytes_read < i2s_bytes_to_read) {
       MicroPrintf(PSTR("Partial I2S read: %d"), bytes_read);
      }

      xSemaphoreTake(TFL->mic->feature_buffer_mutex, pdMS_TO_TICKS(TFL->mic->slice_stride) );

      tf_status = TFL_GenerateFeatures((const int16_t*)i2s_sample_buffer, buffer_size/2 , TFL->mic->slice_size,
                                            TFL->mic->feature_buffer + (TFL->mic->feature_buffer_idx * TFL->mic->slice_size),
                                            &samples_to_read);

      for(int i=0;i<(buffer_size - i2s_bytes_to_read)/4;i++){
        i2s_long_buffer[i] = i2s_long_buffer[i + ((buffer_size - i2s_bytes_to_read)/4)]; //move history to the front
      }

      TFL->mic->feature_buffer_idx += 1;
      if(TFL->mic->feature_buffer_idx == TFL->mic->slice_count){
        TFL->mic->feature_buffer_idx = 0;
      }
      TFL->mic->flag.new_feature_data = 1;
      xSemaphoreGive(TFL->mic->feature_buffer_mutex);

    }
    // MicroPrintf( PSTR("t: %u"),xTaskGetTickCount()-xLastWakeTime);

    if(TFL->mic->flag.continue_audio_capture == 1) vTaskDelayUntil( &xLastWakeTime, pdMS_TO_TICKS(TFL->mic->slice_stride) );
  }

  audio_i2s.in->stopRx();
  if(TFL->mic->mfcc != nullptr){
    delete TFL->mic->mfcc;
    TFL->mic->mfcc = nullptr;
  }
  MicroPrintf( PSTR("end capture task"));
  TFL->mic->flag.audio_capture_ended = 1;
  vTaskDelete(NULL);
}

/**
 * @brief Pass descriptor variables from Berry to the MIC context. Will also calculate some vars.
 * 
 * @param descriptor_buffer - byte array from Berry
 */
void TFL_set_mic_config(const uint8_t *descriptor_buffer){
  TFL_mic_descriptor_t *mic_descriptor = (TFL_mic_descriptor_t*)descriptor_buffer;
  // TFL->mic->channel_fmt = mic_descriptor->channel_fmt; // UNUSED!! - setup by I2S driver
  // TFL->mic->preamp = mic_descriptor->preamp; // UNUSED !!
  TFL->mic->preamp = audio_i2s.Settings->rx.gain / 16;  // setup by I2S driver
  TFL->mic->slice_dur = mic_descriptor->slice_dur;
  TFL->mic->slice_stride = mic_descriptor->slice_stride;
  TFL->mic->num_filter =  mic_descriptor->num_filter;
  TFL->mic->num_coeff =  mic_descriptor->num_coeff;
  TFL->mic->fft_bins = mic_descriptor->fft_bins;
  TFL->max_invocations = mic_descriptor->max_invocations;
  // now calculate the other settings
  TFL->mic->slice_size = mic_descriptor->num_coeff == 0 ? mic_descriptor->num_filter : mic_descriptor->num_coeff;
  TFL->mic->slice_count = (int32_t)((kObservationWindow/(float)TFL->mic->slice_stride) - 0.01); // floor(x) to int
  TFL->mic->preemphasis = (float)(mic_descriptor-> preemphasis)/100.0f;
  TFL->mic->i2s_samples_to_get = (TFL->mic->slice_stride * (kAudioSampleFrequency / 1000));
  TFL->mic->db_floor =  mic_descriptor->db_floor * -1;
  AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: filter: %u, coefficients: %u"), TFL->mic->num_filter, TFL->mic->num_coeff);
  AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: slice stride: %u ms -> slice count: %u, samples to read: %u"), TFL->mic->slice_stride, TFL->mic->slice_count, TFL->mic->i2s_samples_to_get);
}

/**
 * @brief Updates the input tensor with the data from the featuree buffer, which works as a ring buffer and is a shared resorce.
 * 
 */
void TFL_mic_feature_buf_to_input(){
  xSemaphoreTake(TFL->mic->feature_buffer_mutex, pdMS_TO_TICKS(TFL->mic->slice_stride) );
  // Copy feature buffer to input tensor
  int idx = TFL->mic->feature_buffer_idx + 1;  //oldest slice right after the newest slice
  if(idx == TFL->mic->slice_count) idx = 0;
  int slices_upperstack = TFL->mic->slice_count - idx;
  int slices_lowerstack = TFL->mic->slice_count - slices_upperstack;
  memcpy(TFL->mic->model_input_buffer,TFL->mic->feature_buffer + (idx * TFL->mic->slice_size), TFL->mic->slice_size * slices_upperstack);
  memcpy(TFL->mic->model_input_buffer +  (TFL->mic->slice_size * slices_upperstack),TFL->mic->feature_buffer, TFL->mic->slice_size * slices_lowerstack);
  xSemaphoreGive(TFL->mic->feature_buffer_mutex);
  return;
}

/**
 * @brief Helper function to stop audio capture task
 * 
 */
void TFL_stop_audio_capture(){
    MicroPrintf( PSTR("shall stop_capture_task"));
    if(TFL->mic->flag.continue_audio_capture == 0) return;
    TFL->mic->flag.continue_audio_capture = 0;
    uint32_t timeout = 0;
    while(TFL->mic->flag.audio_capture_ended == 0){
      if(timeout>3) break;
      vTaskDelay(pdMS_TO_TICKS(TFL->mic->slice_stride) );
      timeout++;
    }
    vSemaphoreDelete(TFL->mic->feature_buffer_mutex);
    delete[] TFL->mic->feature_buffer;
}
#endif //USE_I2S

/**
 * @brief Helper function to stop all runnning tasks
 * 
 */
void TFL_delete_tasks(){
  if(TFL == nullptr) return;
  TFL->option.running_loop = 0;
  while(TFL->option.loop_ended == 0){
    vTaskDelay(pdMS_TO_TICKS(10));
  }
  AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: task loop did stop"));
#ifdef USE_I2S
  if(TFL->mic != nullptr) {delete TFL->mic;} 
#endif //USE_I2S
  delete TFL;
  TFL = nullptr;
}

/**
 * @brief Starts inference task and the run loop. Should be terminated by signal from helper function.
 * 
 * @param pvParameters - not used
 */
void TFL_task_loop(void *pvParameters){
  uint8_t tensor_arena[TFL->TensorArenaSize];
  TFL->stats = new TFL_stats_t;
  tflite::AllOpsResolver resolver; //TODO: infer needed Ops from model??
  tflite::MicroInterpreter interpreter(
      TFL->model, resolver, tensor_arena, TFL->TensorArenaSize);
  int allocate_status = interpreter.AllocateTensors();
  if (allocate_status != kTfLiteOk) {
    MicroPrintf( PSTR("AllocateTensors() failed"));
    goto loop_task_exit;
  }
  else{
    TFL->stats->used_arena_bytes = interpreter.arena_used_bytes();
  }
  // Obtain pointers to the model's input/output, we can use it externally
  TFL->input = interpreter.input(0);
  TFL->output = interpreter.output(0);

#ifdef USE_I2S
  if(TFL->option.use_mic == 1){
    TFL->mic->feature_buffer = new int8_t[TFL->mic->slice_size * TFL->mic->slice_count]();
    xTaskCreatePinnedToCore(TFL_capture_samples, "tfl_mic", 1024 * 5, NULL, 15, &TFL->mic->audio_capture_task, 0);
    if(TFL->mic->audio_capture_task == nullptr){
      MicroPrintf( PSTR("Creating capture task failed"));
      goto loop_task_exit;
    }
     MicroPrintf( PSTR("Created capture task"));
    TFL->mic->model_input_buffer = TFL->input->data.int8;
    vTaskDelay(pdMS_TO_TICKS(2000)); // wait for at least the time of the the microphone warm up
  }
#endif

  TFL->option.running_loop = 1;

// loop section
 MicroPrintf(PSTR("Enter task loop"));
  while (TFL->option.running_loop == 1)
  {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    TFL->stats->loop_task_free_stack_bytes = uxTaskGetStackHighWaterMark(NULL);

    bool do_invokation = true;
    while(TFL->option.delay_next_invocation == 1 && TFL->option.running_loop == 1){
      MicroPrintf(PSTR("delay_next_invocation"));
      vTaskDelay(10/ portTICK_PERIOD_MS);
    }
    TFL->option.delay_next_invocation = 1;

  #ifdef USE_I2S
    if(TFL->option.use_mic == 1){
      TFL->option.delay_next_invocation = 0; // Clean up later
      if (TFL->mic->flag.new_feature_data == 0){
        do_invokation = false;
      } 
      if(TFL->mic->flag.continue_audio_capture == 1){
        TFL_mic_feature_buf_to_input();
      }
    }
  #endif
    if(do_invokation){
      // MicroPrintf(PSTR("Invokation requested"));
      TFL->option.running_invocation = 1;
      int invoke_status = interpreter.Invoke();
      if (invoke_status != kTfLiteOk) {
        MicroPrintf(PSTR("Invoke failed"));
        TFL->option.running_loop = 0;
      }
      if(TFL->berry_output_buf != nullptr){
        memcpy(TFL->berry_output_buf,(int8_t*)TFL->output->data.data,TFL->berry_output_bufsize);
      }
      TFL->stats->invokations++;

      TFL->option.unread_output = 1;
    #ifdef USE_I2S
      if(TFL->option.use_mic == 1) {
        TFL->mic->flag.new_feature_data = 0;
      }
       TFL->option.running_invocation = 0;
    #endif //USE_I2S
    }
    if(TFL->option.running_loop == 1) vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000 / TFL->max_invocations)); //maybe we already want to exit
  }

// end of loop section
loop_task_exit:
  delete TFL->stats;
#ifdef USE_I2S
  if(TFL->option.use_mic == 1) {TFL_stop_audio_capture();}
#endif //USE_I2S
  MicroPrintf(PSTR("end loop task"));
  TFL->option.loop_ended = 1;
  vTaskDelete( NULL );
}

/*********************************************************************************************\
 * Native functions mapped to Berry functions
\*********************************************************************************************/
extern "C" {

/**
 * @brief Create a context for a tensor flow session, that will later run in a task
 * 
 * @param vm 
 * @param type        BUF - generic byte buffer, CAM - webcam input, MIC - microphone input
 * @return btrue 
 * @return bfalse 
 */
  bbool be_TFL_begin(struct bvm *vm, const char* type, const uint8_t *descriptor, size_t len) {
    if (TFL_log_buffer == nullptr){
      TFL_log_buffer = xRingbufferCreate(1028, RINGBUF_TYPE_NOSPLIT);
      AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: init log buffer"));
    }
    TFL_delete_tasks();
    if(strlen(type) == 0){
      AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: context deleted"));
      return btrue;
    }
    TFL = new TFL_ctx_t;
    TFL->options = 0;

    if(*(uint32_t*)type == 0x00465542){ //BUF
      AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: mode generic buffer"));
    }
    else if(*(uint32_t*)type == 0x004D4143){ //CAM - not yet implemented
      AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: mode webcam"));
      if(TFL_init_CAM()){
        TFL->option.use_cam = 1;
      }
      else{
        return bfalse;
      }
    }
    else if(*(uint32_t*)type == 0x0043494D){ //MIC
#ifdef USE_I2S
      if(descriptor && len==sizeof(TFL_mic_descriptor_t)){
        if(TFL_init_MIC(descriptor)){
          TFL->option.use_mic = 1;
        }
        else{
          return bfalse;
        }
      }
      else{
        AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: expected descriptor of size: %u"), sizeof(TFL_mic_descriptor_t));
        return bfalse;
      }
#else
      AddLog(LOG_LEVEL_ERROR, PSTR("TFL: firmware with I2S audio required !!"));
#endif //USE_I2S
    }
    else{
      AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: unknown mode"));
      return bfalse;
    }
    if(TFL!=nullptr){
      AddLog(LOG_LEVEL_INFO, PSTR("TFL: start TFL context with type: %s"), type);
      TFL->option.init_done = 1;
      return btrue;
    }
    return bfalse;
  }

/**
 * @brief Load tensor flow lite model and then start the tensor flow session in a task
 * 
 * @param vm 
 * @param buf     Model in a byte buffer
 * @param size    Size of buffer, must be 8-byte-aligned (auto-calculated by Berry)
 * @param arena   Size of the Tensor Arena in the stack of the TFL task
 * @return btrue 
 * @return bfalse 
 */
  bbool be_TFL_load(struct bvm *vm, const uint8_t *model_buf, size_t model_size, const uint8_t *output_buf, size_t output_size,int arena) {
    if(TFL){
      if(TFL->option.init_done){
        TFL->model = tflite::GetModel(model_buf);
        if ( TFL->model->version() != TFLITE_SCHEMA_VERSION) {
          AddLog(LOG_LEVEL_INFO, PSTR("TFL: Model schema version %d not supported "
                      "version %d."),  TFL->model->version(), TFLITE_SCHEMA_VERSION);
          return bfalse;
        }
        if(model_size%8 != 0){
          AddLog(LOG_LEVEL_INFO, PSTR("TFL: model not 8-byte aligned"));
          return bfalse;
        }
        if(arena){
          TFL->TensorArenaSize = arena;
        }
        TFL->option.mode_inference = 1;
        TFL->berry_output_buf = (int8_t*)output_buf;
        TFL->berry_output_bufsize = output_size;
        TFL_create_task();
        AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: starting TFL task, model sz: %u, allocated arena sz: %u"),model_size,TFL->TensorArenaSize);
        AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: Berry output buffer of size: %u"),output_size);
        return btrue;
      }
    }
    return bfalse;
  }

  /**
   * @brief Send new input data to the tensor flow session
   * 
   * @param vm 
   * @param buf       Arbitrary data in a byte buffer, must fit to the TF model
   * @param size      Size of buffer (auto-calculated by Berry)
   * @return btrue 
   * @return bfalse 
   */

  bbool be_TFL_input(struct bvm *vm, const uint8_t *buf, size_t size){
    if(!TFL) return bfalse;
    if(TFL->option.running_loop == 1){
      uint32_t timeout = 0;
      while(!TFL->option.delay_next_invocation == 1) {
        if(timeout>10) break;
        delay(2);
        timeout++;
      }
      AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: imput new data and invoke"));
      memcpy((uint8_t*)TFL->input->data.data,(uint8_t*)buf,size);
      TFL->option.delay_next_invocation = 0;
      return btrue;
    }
    return bfalse;
  }

  /**
   * @brief Get copy of the output sensor of the tensor flow session
   * 
   * @param vm 
   * @param buf       Arbitrary data in a byte buffer, must fit in size to the TF model
   * @param size      Size of buffer (auto-calculated by Berry)
   * @return btrue    - new data
   * @return bfalse   - old data
   */
  bbool be_TFL_output(struct bvm *vm, const uint8_t *buf, size_t size){
    if(!TFL) return bfalse;
    if(TFL->option.running_loop == 0) return bfalse;
    if(TFL->option.unread_output == 1){
      AddLog(LOG_LEVEL_DEBUG, PSTR("TFL: read output  data"));
      if(TFL->output != nullptr){
        TFL->option.unread_output = 0;
        return btrue; //new data
      }
    }
    return bfalse; // old data
  }

/**
 * @brief Read from the logging buffer from the TFL tasks
 * 
 * @param vm 
 * @return const char* 
 */
  const char * be_TFL_log(struct bvm *vm){
      size_t size;
      char * item =  (char *)xRingbufferReceive(TFL_log_buffer, &size, pdMS_TO_TICKS(5));
      if(item != NULL){
        // item[size] = 0; // 0-terminate string
        vRingbufferReturnItem(TFL_log_buffer, (void *)item);
      }
      return (const char *)item;
  }


/**
 * @brief Shows statistics about the model and the running TFL session
 * 
 * @param vm 
 * @return json string
 */
  const char * be_TFL_stats(struct bvm *vm){

    const size_t size = 512;
    char * s = (char*)calloc(size,1);
    uint32_t pos = 0;
    uint32_t inc = 0;
    inc = snprintf_P(s + pos, size, PSTR("{\"model\":{\"input_shape\":["));
    pos += inc;
    uint32_t dims = TFL->input->dims->size;
    for(int i=0;i<dims;i++){
      inc = snprintf_P(s + pos, size-pos, PSTR("%u"),TFL->input->dims->data[i]);
      pos += inc;
      if (i != dims-1){
        inc = snprintf_P(s + pos, size-pos,",");
        pos += inc;
      } 
    }
    inc = snprintf_P(s + pos, size-pos, PSTR("],\"input_type\":%u,\"output_shape\":["),TFL->input->type);
    pos += inc;
    dims = TFL->output->dims->size;
    for(int i=0;i<dims;i++){
      inc = snprintf_P(s + pos, size-pos, PSTR("%u"),TFL->output->dims->data[i]);
      pos += inc;
      if (i != dims-1){
        inc = snprintf_P(s + pos, size-pos,",");
        pos += inc;
      } 
    }
    inc = snprintf_P(s + pos, size-pos, PSTR("],\"output_type\":%u}"),TFL->output->type);
    pos += inc;
    inc = snprintf_P(s + pos, size-pos, PSTR(",\"sessiom\":{\"used_arena\":%u"),TFL->stats->used_arena_bytes);
    pos += inc;
    inc = snprintf_P(s + pos, size-pos, PSTR(",\"loop_stack\":%u"),TFL->stats->loop_task_free_stack_bytes);
    pos += inc;
    if(TFL->option.use_mic == 1){
      inc = snprintf_P(s + pos, size-pos, PSTR(",\"audio_stack\":%u"),TFL->stats->mic_task_free_stack_bytes);
      pos += inc;
    }
    inc = snprintf_P(s + pos, size-pos, PSTR(",\"invokations\":%u}}"),TFL->stats->invokations);
    be_pushstring(vm, s);
    free(s);
    return s;
  }

} //extern "C"

#endif // USE_BERRY_TF_LITE
#endif  // USE_BERRY