/*
 * 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"
#include "log.h"
#include "com_usb.h"
#include "pico/multicore.h"
#include "ws2812.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;
    }
}

void sd_card_gestion(){
  if(gpio_get(1) == 1){
    ws2812_set_buffer_rgb(OFF_24BITS, 0);
    // Unmount FS + de-init log SD
  }else{
    ws2812_set_buffer_rgb(BLEU_24BITS, 0);
  }

  // Init SD Card for log
  FATFS fs;
  char drive_path[3] = "0:";
  drive_path[0] += DEV_SDIO_MIN;
  FRESULT fr;
  do{
    fr = f_mount(&fs, drive_path, 1);
    if(FR_OK != fr){
      printf("f_mount error: %s (%d)\n", FRESULT_str(fr), fr);
      printf(">f_mount:0\n");
      sleep_ms(50);
    }else{
      printf(">f_mount:1\n");
      f_unmount(drive_path);
      sleep_ms(50);
    }
  }while(1);
}

void core1_functions(){
  while(1){
    ws2812_gestion();
    ws2812_affiche_buffer();
    sleep_ms(2);
  }
}


/*------------- MAIN -------------*/
int main(void) {
  gpio_set_function(12, UART_FUNCSEL_NUM(uart0, 12));
  gpio_set_function(13, UART_FUNCSEL_NUM(uart0, 13));
  printf("TinyUSB Host CDC MSC HID - Logs\r\n");

  multicore_launch_core1(core1_functions);
  board_init();
  ws2812_init();

  ws2812_set_buffer_rgb(2,2,2,0);
  ws2812_set_buffer_rgb(2,2,2,3);

  // Init detection carte SD
  gpio_init(1);
  gpio_set_dir(1, GPIO_IN);
  gpio_pull_up(1);

  float f;
  absolute_time_t current_time, start_time;
  start_time = get_absolute_time() / 1000.;

  uint32_t couleur[13];

  couleur[0]=0x000800;
  couleur[1]=0x040800;
  couleur[2]=0x080800;
  couleur[3]=0x080400;
  couleur[4]=0x080000;
  couleur[5]=0x080004;
  couleur[6]=0x080008;
  couleur[7]=0x040008;
  couleur[8]=0x000008;
  couleur[9]=0x000408;
  couleur[10]=0x000808;
  couleur[11]=0x000804;
  couleur[12]=0x000800;
  uint8_t index_couleur=0;

  ws2812_set_mode(LED_ID_SD_CARD, LED_CLI_RAPIDE, 0x000004);
  ws2812_set_mode(LED_ID_USB, LED_CLI_NORMAL, 0x000004);
  ws2812_set_mode(LED_ID_ASSERV, LED_CLI_PANIQUE_1, 0x000004);
  ws2812_set_mode(LED_ID_DETECTION, LED_PULSE_RAPIDE, 0x000004);

  
  while(1){
    f = get_absolute_time() / 1000. - start_time;
    f = f / 1000.;
    if(f > 1){
      f=1;
      start_time = get_absolute_time() / 1000.;
      f = 0;
      index_couleur++;
      if(index_couleur > 11){
        index_couleur = 0;
      }
    }
    //ws2812_set_buffer(ws2812_mix_color(couleur[index_couleur], couleur[index_couleur+1],f), 4);
    ws2812_set_mode(LED_ID_SD_CARD, LED_CLI_RAPIDE, 0x000004);
    ws2812_set_mode(LED_ID_USB, LED_CLI_NORMAL, 0x000004);
    ws2812_set_mode(LED_ID_ASSERV, LED_CLI_PANIQUE_1, 0x000004);
    ws2812_set_mode(LED_ID_DETECTION, LED_PULSE_RAPIDE, 0x000004);
    ws2812_set_mode(LED_ID_DIVERS, LED_STABLE, 0x000004);

    sleep_ms(3000);

    ws2812_set_mode(LED_ID_SD_CARD, LED_CLI_RAPIDE, 0x000404);
    ws2812_set_mode(LED_ID_USB, LED_CLI_NORMAL, 0x000404);
    ws2812_set_mode(LED_ID_ASSERV, LED_CLI_LENT, 0x000404);
    ws2812_set_mode(LED_ID_DETECTION, LED_CLI_PANIQUE_1, 0x000404);
    ws2812_set_mode(LED_ID_DIVERS, LED_CLI_PANIQUE_2, 0x000404);

    sleep_ms(3000);

    ws2812_set_mode(LED_ID_SD_CARD, LED_CLI_PANIQUE_3, 0x040000);
    ws2812_set_mode(LED_ID_USB, LED_PULSE_RAPIDE, 0x040000);
    ws2812_set_mode(LED_ID_ASSERV, LED_PULSE_NORMAL, 0x040000);
    ws2812_set_mode(LED_ID_DETECTION, LED_PULSE_LENT, 0x040000);
    ws2812_set_mode(LED_ID_DIVERS, LED_STABLE, 0x040000);

    sleep_ms(3000);

    sleep_ms(1);

  }


  
  // init host stack on configured roothub port
  // à mettre avant tuh_init
  com_init();
  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;
  }

  log_init(drive_path);

  //multicore_launch_core1(routine_log_sd);


  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));
}