/* Motenpoche 
 *
 * (c) 2023 Daniele Lacamera <root@danielinux.net>
 *
 *
 * Motenpoche 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 2 of the License, or
 * (at your option) any later version.
 *
 * Motenpoche 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 *
 */
#include <stdio.h>
#include <stdint.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "hardware/gpio.h"
#include "hardware/adc.h"
#include "hardware/i2c.h"
#include "hardware/spi.h"
#include "cryptoengine.h"

#include "bsp/board.h"

#include "tusb.h"
#include "usb_descriptors.h"
#include "ui.h"

static bool has_keyboard_key = false;

/* USB HID: capslock */
static int capslock_on = 0;

/* USB HID: tx buffer */
#define TX_BUFFER_SIZE 256
static char tx_buffer_str[TX_BUFFER_SIZE];
static volatile int tx_buffer_wr=0, tx_buffer_rd=0;

/* ASCII to HID conversion */
static const uint8_t const conv_table[128][2] =  { HID_ASCII_TO_KEYCODE };

// Invoked when device is mounted
void tud_mount_cb(void)
{
}

// Invoked when device is unmounted
void tud_umount_cb(void)
{
}

// Invoked when usb bus is suspended
// remote_wakeup_en : if host allow us  to perform remote wakeup
// Within 7ms, device must draw an average of current less than 2.5 mA from bus
void tud_suspend_cb(bool remote_wakeup_en)
{
  (void) remote_wakeup_en;
}

// Invoked when usb bus is resumed
void tud_resume_cb(void)
{
}

/* USB: tud complete cb */
void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint8_t len)
{
    (void) instance;
    (void) len;
    (void) report;
    if (has_keyboard_key) {
        tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL);
        has_keyboard_key = false;
    } else if (tx_buffer_rd > tx_buffer_wr) {
        uint8_t  modifier = 0;
        uint8_t keycode[6] = {0};
        char c = tx_buffer_str[tx_buffer_wr];
        if (c == '\t')
            keycode[0] = HID_KEY_TAB;
        else {
            modifier = conv_table[c][0];
            keycode[0] = conv_table[c][1];
        }
        if (capslock_on) {
            if (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))) {
                modifier = !modifier;
            }
        }
        if (modifier)
            modifier = KEYBOARD_MODIFIER_LEFTSHIFT;
        tud_hid_keyboard_report(REPORT_ID_KEYBOARD, modifier, keycode);
        has_keyboard_key = true;
        tx_buffer_wr++;
    }

}

/* USB HID: get report */
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
{
  // TODO not Implemented
  (void) instance;
  (void) report_id;
  (void) report_type;
  (void) buffer;
  (void) reqlen;

  return 0;
}

/* Invoked when SET_REPORT control request or 
 * data on OUT endpoint ( Report ID = 0, Type = 0 ) are received
 */
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
{
  (void) instance;

  if (report_type == HID_REPORT_TYPE_OUTPUT)
  {
    // Set keyboard LED e.g Capslock, Numlock etc...
    if (report_id == REPORT_ID_KEYBOARD)
    {
      // bufsize should be (at least) 1
      if ( bufsize < 1 ) return;

      uint8_t const kbd_leds = buffer[0];

      if (kbd_leds & KEYBOARD_LED_CAPSLOCK)
      {
          capslock_on = 1;
      }else
      {
          capslock_on = 0;
      }
    }
  }
}


//--------------------------------------------------------------------+
// USB HID
//--------------------------------------------------------------------+

static void send_hid_report(uint8_t report_id, uint32_t unused)
{
    (void)unused;

    if ( !tud_hid_ready() )
        return;

    switch(report_id)
    {
        case REPORT_ID_KEYBOARD:
            {
                // use to avoid send multiple consecutive zero report for keyboard
                uint8_t modifier = 0;

                if (tx_buffer_rd > tx_buffer_wr) {
                    uint8_t keycode[6] = { 0 };
                    char c = tx_buffer_str[tx_buffer_wr];
                    if (c == '\t')
                        keycode[0] = HID_KEY_TAB;
                    else {
                        modifier = conv_table[c][0];
                        keycode[0] = conv_table[c][1];
                    }
                    if (capslock_on) {
                        if (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))) {
                            modifier = !modifier;
                        }
                    }
                    if (modifier)
                        modifier = KEYBOARD_MODIFIER_LEFTSHIFT;
                    tud_hid_keyboard_report(REPORT_ID_KEYBOARD, modifier, keycode);
                    has_keyboard_key = true;
                    tx_buffer_wr++;
                } else {
                    if (has_keyboard_key)
                        tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL);
                    has_keyboard_key = false;
                }
            }
            break;


        default: break;
    }
}

void hid_keys_string_send(const char *str)
{
    int len = strlen(str);
    int i;
    if (tx_buffer_rd == tx_buffer_wr) {
        tx_buffer_rd = 0;
        ForceZero(tx_buffer_str, TX_BUFFER_SIZE);
        tx_buffer_wr = 0;
    }
    for(i = 0; i < len; i++) {
        if (tx_buffer_rd >= TX_BUFFER_SIZE)
            return;
        tx_buffer_str[tx_buffer_rd++] = str[i];
    }
    gpio_put(17, 0);
}

// Every 10ms, we will sent 1 report for each HID profile (keyboard, mouse etc ..)
// tud_hid_report_complete_cb() is used to send the next report after previous one is complete
void hid_task(void)
{
  // Poll every 10ms
  const uint32_t interval_ms = 10;
  static uint32_t start_ms = 0;
  if (!tud_inited())
      return;

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

  // Remote wakeup
  if ( tud_suspended() && (tx_buffer_rd > tx_buffer_wr))
  {
    // Wake up host if we are in suspend mode
    // and REMOTE_WAKEUP feature is enabled by host
    tud_remote_wakeup();
  }else
  {
    // Send the 1st of report chain, the rest will be sent by tud_hid_report_complete_cb()
    send_hid_report(REPORT_ID_KEYBOARD, 0);
    gpio_put(17, 1);
  }
}
