/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ha Thach (tinyusb.org)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

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

#include "bsp/board_api.h"
#include "lib/sd_driver/sd_card.h"
#include "tusb.h"
#include "ff.h"
#include "f_util.h"

//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF PROTYPES
//--------------------------------------------------------------------+
void led_blinking_task(void);
extern void cdc_app_task(void);
extern void hid_app_task(void);

#if CFG_TUH_ENABLED && CFG_TUH_MAX3421
// API to read/rite MAX3421's register. Implemented by TinyUSB
extern uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, bool in_isr);
extern bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, bool in_isr);
#endif

// USB Log
void log_to_usb(const char * message);

//--------------------------------------------------------------------+
// SD CARD 
//--------------------------------------------------------------------+
void log_to_sd(const char * message);
FIL fil_sdcard; // File to log on the SD Card

/* SDIO Interface */
static sd_sdio_if_t sdio_if = {
    /*
    Pins CLK_gpio, D1_gpio, D2_gpio, and D3_gpio are at offsets from pin D0_gpio.
    The offsets are determined by sd_driver\SDIO\rp2040_sdio.pio.
        CLK_gpio = (D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32;
        As of this writing, SDIO_CLK_PIN_D0_OFFSET is 30,
            which is -2 in mod32 arithmetic, so:
        CLK_gpio = D0_gpio -2.
        D1_gpio = D0_gpio + 1;
        D2_gpio = D0_gpio + 2;
        D3_gpio = D0_gpio + 3;
    */
    .CMD_gpio = 3,
    .D0_gpio = 4,
    .baud_rate = 125 * 1000 * 1000 / 6  // 20833333 Hz
};

/* Hardware Configuration of the SD Card socket "object" */
static sd_card_t sd_card = {.type = SD_IF_SDIO, .sdio_if_p = &sdio_if};

/**
 * @brief Get the number of SD cards.
 *
 * @return The number of SD cards, which is 1 in this case.
 */
size_t sd_get_num() { return 1; }

/**
 * @brief Get a pointer to an SD card object by its number.
 *
 * @param[in] num The number of the SD card to get.
 *
 * @return A pointer to the SD card object, or @c NULL if the number is invalid.
 */
sd_card_t* sd_get_by_num(size_t num) {
    if (0 == num) {
        // The number 0 is a valid SD card number.
        // Return a pointer to the sd_card object.
        return &sd_card;
    } else {
        // The number is invalid. Return @c NULL.
        return NULL;
    }
}


/*------------- MAIN -------------*/
int main(void) {
  board_init();

  printf("TinyUSB Host CDC MSC HID Example - test comparatif\r\n");

  // init host stack on configured roothub port
  tuh_init(BOARD_TUH_RHPORT);

  if (board_init_after_tusb) {
    board_init_after_tusb();
  }

#if CFG_TUH_ENABLED && CFG_TUH_MAX3421
  // FeatherWing MAX3421E use MAX3421E's GPIO0 for VBUS enable
  enum { IOPINS1_ADDR  = 20u << 3, /* 0xA0 */ };
  tuh_max3421_reg_write(BOARD_TUH_RHPORT, IOPINS1_ADDR, 0x01, false);
#endif

  // Init SD Card for log
  FATFS fs;
  char drive_path[3] = "0:";
  drive_path[0] += DEV_SDIO_MIN;
  FRESULT fr = f_mount(&fs, drive_path, 1);
  if (FR_OK != fr) {
      panic("f_mount error: %s (%d)\n", FRESULT_str(fr), fr);
      return -1;
  }else{
    printf("SD Card mounted on %s\n", drive_path);
  }

  char* const filepath = "0:/log.txt";
  filepath[0] = drive_path[0];
  fr = f_open(&fil_sdcard, filepath, FA_OPEN_ALWAYS | FA_WRITE);
  if (FR_OK != fr && FR_EXIST != fr) {
      panic("f_open(%s) error: %s (%d)\n", filepath, FRESULT_str(fr), fr);
      return -1;
  }




  const char buffer[]= "Ceci est le test de Poivron Robotique\n";
  char big_big_buffer[0x10000]; // 16 ko

  int size = 0;
  big_big_buffer[0]= '\0';
  while(size + strlen(buffer) < 0x10000){
    strcat(big_big_buffer, buffer);
    size += strlen(buffer);
  }

  while (1) {
    // tinyusb host task
    tuh_task();

    led_blinking_task();
    cdc_app_task();
    hid_app_task();
    //log_to_sd(big_big_buffer);
    //log_to_usb(big_big_buffer); // à commenter pour obtenir un test juste sur la carte SD.(ou simplement ne pas brancher de clé USB)
    
  }
}

//--------------------------------------------------------------------+
// TinyUSB Callbacks
//--------------------------------------------------------------------+

void tuh_mount_cb(uint8_t dev_addr) {
  // application set-up
  char message[100];
  printf("A device with address %d is mounted\r\n", dev_addr);
  sprintf(message, "A device with address %d is mounted\r\n", dev_addr);
  log_to_sd(message);
}

void tuh_umount_cb(uint8_t dev_addr) {
  // application tear-down
  char message[100];
  printf("A device with address %d is unmounted \r\n", dev_addr);
  sprintf(message, "A device with address %d is unmounted\r\n", dev_addr);
  log_to_sd(message);
}


//--------------------------------------------------------------------+
// Blinking Task
//--------------------------------------------------------------------+
void led_blinking_task(void) {
  const uint32_t interval_ms = 500;
  static uint32_t start_ms = 0;

  static bool led_state = false;

  // Blink every interval ms
  if (board_millis() - start_ms < interval_ms) return; // not enough time
  start_ms += interval_ms;

  board_led_write(led_state);
  led_state = 1 - led_state; // toggle
}


void log_to_sd(const char * chaine){
    static int i=0;
    unsigned int nb_char_written, strlen_chaine;
    absolute_time_t current_time, start_time;
    FRESULT error;
    strlen_chaine = strlen(chaine);
    start_time = get_absolute_time();
    error = f_write(&fil_sdcard, chaine, strlen_chaine, &nb_char_written);
    if (error != FR_OK) {
        printf("f_write failed, error: %d\n", error);
    }
    f_sync(&fil_sdcard);
    current_time = get_absolute_time();
    printf("Ecrit: %d octets en  %llu us\n", nb_char_written, current_time - start_time);
    printf(">sd(Mo/s):%d:%.3f\n", (int)(current_time/1000), (float)(nb_char_written) / (float)(current_time - start_time));
}