/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "data_logging_test.h"

#include "system/logging.h"
#include "services/common/comm_session/session.h"
#include "services/normal/data_logging/data_logging_service.h"
#include "services/normal/data_logging/dls_private.h"
#include "applib/app.h"
#include "applib/data_logging.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/window.h"
#include "applib/ui/text_layer.h"
#include "applib/app_timer.h"
#include "applib/app_logging.h"

#include <stdio.h>
#include <string.h>

/*
 *  Incremental STM CRC32 implemented in software
 */

#define CRC_POLY 0x04C11DB7

static uint32_t crc_init(void) {
  return 0xffffffff;
}

static uint32_t crc_update(uint32_t crc, const uint8_t *data, uint32_t length) {
  const uint8_t num_remainder_bytes = length % 4;
  uint32_t num_whole_word_bytes = length / 4;

  while (num_whole_word_bytes--) {
    crc = crc ^ *((uint32_t*)data);
    for(int bit = 0; bit < 32; ++bit) {
      if ((crc & 0x80000000) != 0) {
        crc = (crc << 1) ^ CRC_POLY;
      } else {
        crc = (crc << 1);
      }
    }
    data += 4;
  }

  if (num_remainder_bytes) {
    uint32_t last_word = 0;
    for (unsigned int i = 0; i < num_remainder_bytes; ++i) {
      last_word = (last_word << 8) | data[i];
    }
    return crc_update(crc, (uint8_t*)&last_word, 4);
  }

  return crc & 0xffffffff;
}

/*
 *  Data Logging Test App
 */

struct DataLoggingInfo {
  TextLayer text_layer;
  char text[32];
  int counter;
  uint32_t crc;
  DataLoggingSessionRef logging_session;
  uint8_t item_size;
};

static struct {
  Window window;
  struct DataLoggingInfo info[3];
  TextLayer log_layer;
} s_data;

static const int s_chunk_size = 80;

static void log_moar_data(struct DataLoggingInfo *info) {
  uint8_t buf[s_chunk_size];

  for (int i = 0; i < s_chunk_size; i++) {
    buf[i] = (info->counter * s_chunk_size) + i;
  }

  info->crc = crc_update(info->crc, buf, s_chunk_size);
  data_logging_log(info->logging_session, buf, s_chunk_size / info->item_size);
  ++info->counter;
}

static void handle_timer(void *ck) {
  if (s_data.info[0].logging_session == NULL) {
    // Sessions closed.
    return;
  }
  struct DataLoggingInfo*info = ck;

  log_moar_data(info);

  const int num_chunks = 30;

  snprintf(info->text, sizeof(info->text), "%lu (%i) %i/%i", info->crc, info->counter * s_chunk_size, info->counter, num_chunks);
  text_layer_set_text(&info->text_layer, info->text);

  if (info->counter < num_chunks) {
    app_timer_register(1000 /* milliseconds */, handle_timer, info);
  } else {
    text_layer_set_text(&s_data.log_layer, "Done logging. Select to close.");
  }
}

static void close_sessions(void) {
  for (int i = 0; i < 3; ++i) {
    data_logging_finish(s_data.info[i].logging_session);
    s_data.info[i].logging_session = NULL;
  }
  text_layer_set_text(&s_data.log_layer, "Closed all logging sessions.");
}

static void start_logging(void) {
  const uint8_t item_size[] = {4, 2, 16};
  const DataLoggingItemType types[] = {DATA_LOGGING_INT, DATA_LOGGING_UINT, DATA_LOGGING_BYTE_ARRAY};

  for (int i = 0; i < 3; ++i) {
    text_layer_set_text(&s_data.info[i].text_layer, "Empty");
    s_data.info[i].item_size = item_size[i];
    s_data.info[i].logging_session = data_logging_create(i + 1, types[i], item_size[i], false);
  }

  // start timers
  app_timer_register(2000 /* milliseconds */, handle_timer, (void *) &s_data.info[0]);
  app_timer_register(1500 /* milliseconds */, handle_timer, (void *) &s_data.info[1]);
  app_timer_register(4500 /* milliseconds */, handle_timer, (void *) &s_data.info[2]);

  text_layer_set_text(&s_data.log_layer, "Logging...");
}

static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
  if (s_data.info[0].logging_session) {
    close_sessions();
  } else {
    start_logging();
  }
}

static void click_config_provider(void *context) {
  window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
}


static void handle_deinit(void) {
  comm_session_set_responsiveness(comm_session_get_system_session(), BtConsumerApp,
                                  ResponseTimeMax, MAX_PERIOD_RUN_FOREVER);
}

static void handle_init(void) {
  dls_clear();

  memset(&s_data, 0, sizeof(s_data));
  // Init window
  window_init(&s_data.window, "Logging Demo");
  app_window_stack_push(&s_data.window, true /* Animated */);
  window_set_click_config_provider_with_context(&s_data.window, click_config_provider,
                                                &s_data.window);

  const GRect *bounds = &s_data.window.layer.bounds;

  for (int i = 0; i < 3; ++i) {
    s_data.info[i].crc = crc_init();
    text_layer_init(&s_data.info[i].text_layer, &GRect(0, i * 20, bounds->size.w, 20));
    layer_add_child(&s_data.window.layer, &s_data.info[i].text_layer.layer);
  }

  text_layer_init(&s_data.log_layer, &GRect(0, bounds->size.w / 2, bounds->size.w,
                                            bounds->size.w / 2));
  layer_add_child(&s_data.window.layer, &s_data.log_layer.layer);

  start_logging();

  comm_session_set_responsiveness(comm_session_get_system_session(), BtConsumerApp,
                                  ResponseTimeMax, MAX_PERIOD_RUN_FOREVER);
}

////////////////////
// App boilerplate

static void s_main(void) {
  handle_init();

  app_event_loop();

  handle_deinit();
}

const PebbleProcessMd* data_logging_test_get_info() {
  static const PebbleProcessMdSystem s_app_info = {
    // UUID: 01020304-0506-0708-0910-111213141516
    .common.uuid = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16},
    .common.main_func = &s_main,
    .name = "Data Logging Test"
  };
  return (const PebbleProcessMd*) &s_app_info;
}
