/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2026 Saulo Verissimo
 *
 * 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 <string.h>
#include "bsp/board_api.h"
#include "tusb.h"
#include "class/audio/audio.h"
#include "class/midi/midi.h"

//--------------------------------------------------------------------+
// Device Descriptors
//--------------------------------------------------------------------+

static tusb_desc_device_t const desc_device = {
  .bLength = sizeof(tusb_desc_device_t),
  .bDescriptorType = TUSB_DESC_DEVICE,
  .bcdUSB = 0x0200,
  .bDeviceClass = 0x00,
  .bDeviceSubClass = 0x00,
  .bDeviceProtocol = 0x00,
  .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,

  .idVendor = 0xcafe,
  .idProduct = 0x4062,  // MIDI 2.0 Device
  .bcdDevice = 0x0100,

  .iManufacturer = 0x01,
  .iProduct = 0x02,
  .iSerialNumber = 0x03,

  .bNumConfigurations = 0x01
};

uint8_t const * tud_descriptor_device_cb(void) {
  return (uint8_t const *) &desc_device;
}

//--------------------------------------------------------------------+
// Configuration Descriptor - MIDI 2.0
//--------------------------------------------------------------------+

enum {
  ITF_NUM_MIDI2 = 0,       // Audio Control interface
  ITF_NUM_MIDI2_STREAMING,  // MIDI Streaming interface
  ITF_NUM_TOTAL
};

#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MIDI2_DESC_LEN)

// Endpoint addresses
#define EPNUM_MIDI2_OUT 0x01
#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
#define EPNUM_MIDI2_IN 0x82
#else
#define EPNUM_MIDI2_IN 0x81
#endif

static uint8_t const desc_fs_configuration[] = {
  // Config number, interface count, string index, total length, attribute, power in mA
  TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),

  // MIDI 2.0 Interface, two Group Terminal Blocks associated per direction:
  //   bulk OUT (host to device) carries the input block (ID 2)
  //   bulk IN  (device to host) carries the output block (ID 1)
  // Alt Setting 0 (MIDI 1.0)
  TUD_MIDI_DESC_HEAD(ITF_NUM_MIDI2, 0, 1),
  TUD_MIDI_DESC_JACK_DESC(1, 0),
  TUD_MIDI_DESC_EP(EPNUM_MIDI2_OUT, 64, 1),
  TUD_MIDI_JACKID_IN_EMB(1),
  TUD_MIDI_DESC_EP(EPNUM_MIDI2_IN, 64, 1),
  TUD_MIDI_JACKID_OUT_EMB(1),
  // Alt Setting 1 (UMP)
  TUD_MIDI2_DESC_ALT1_HEAD(ITF_NUM_MIDI2, 0),
  TUD_MIDI2_DESC_ALT1_EP(EPNUM_MIDI2_OUT, 64, 1, 2),
  TUD_MIDI2_DESC_ALT1_EP(EPNUM_MIDI2_IN, 64, 1, 1)
};

uint8_t const * tud_descriptor_configuration_cb(uint8_t index) {
  (void) index;
  return desc_fs_configuration;
}

//--------------------------------------------------------------------+
// String Descriptors
//--------------------------------------------------------------------+

enum {
  STRID_LANGID = 0,
  STRID_MANUFACTURER = 1,
  STRID_PRODUCT = 2,
  STRID_SERIAL = 3,
};

static char const *string_desc_arr[] = {
  (const char[]) { 0x09, 0x04 },  // 0: Language
  "TinyUSB",                      // 1: Manufacturer
  "TinyUSB MIDI 2.0",             // 2: Product
  NULL,                           // 3: Serial
};

static uint16_t _desc_str[32 + 1];

uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
  (void) langid;
  size_t chr_count;

  switch ( index ) {
    case STRID_LANGID:
      memcpy(&_desc_str[1], string_desc_arr[0], 2);
      chr_count = 1;
      break;

    case STRID_SERIAL:
      chr_count = board_usb_get_serial(_desc_str + 1, 32);
      break;

    default:
      if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) {
        return NULL;
      }

      const char *str = string_desc_arr[index];
      chr_count = strlen(str);
      const size_t max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1;
      if ( chr_count > max_count ) {
        chr_count = max_count;
      }

      for ( size_t i = 0; i < chr_count; i++ ) {
        _desc_str[1 + i] = str[i];
      }
      break;
  }

  _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
  return _desc_str;
}
