// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// 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 "USBHID.h"
#if SOC_USB_OTG_SUPPORTED

#if CONFIG_TINYUSB_HID_ENABLED

#include "esp32-hal-log.h"
#include "USBHIDVendor.h"

ESP_EVENT_DEFINE_BASE(ARDUINO_USB_HID_VENDOR_EVENTS);
esp_err_t arduino_usb_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, TickType_t ticks_to_wait);
esp_err_t arduino_usb_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg);

// HID Generic Input, Output & Feature
// - 1st parameter is report size (mandatory)
// - 2nd parameter is report id HID_REPORT_ID(n) (optional)
#define TUD_HID_REPORT_DESC_GENERIC_INOUT_FEATURE(report_size, ...) \
    HID_USAGE_PAGE_N ( HID_USAGE_PAGE_VENDOR, 2   ),\
    HID_USAGE        ( 0x01                       ),\
    HID_COLLECTION   ( HID_COLLECTION_APPLICATION ),\
      /* Report ID if any */\
      __VA_ARGS__ \
      /* Input */ \
      HID_USAGE       ( 0x02                                   ),\
      HID_LOGICAL_MIN ( 0x00                                   ),\
      HID_LOGICAL_MAX ( 0xff                                   ),\
      HID_REPORT_SIZE ( 8                                      ),\
      HID_REPORT_COUNT( report_size                            ),\
      HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\
      /* Output */ \
      HID_USAGE       ( 0x03                                    ),\
      HID_LOGICAL_MIN ( 0x00                                    ),\
      HID_LOGICAL_MAX ( 0xff                                    ),\
      HID_REPORT_SIZE ( 8                                       ),\
      HID_REPORT_COUNT( report_size                             ),\
      HID_OUTPUT      ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE  ),\
      /* Feature */ \
      HID_USAGE       ( 0x04                                    ),\
      HID_LOGICAL_MIN ( 0x00                                    ),\
      HID_LOGICAL_MAX ( 0xff                                    ),\
      HID_REPORT_SIZE ( 8                                       ),\
      HID_REPORT_COUNT( report_size                             ),\
      HID_FEATURE     ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE  ),\
    HID_COLLECTION_END \

#define TUD_HID_REPORT_DESC_GENERIC_INOUT_FEATURE_LEN 46

// max size is 64 and we need one byte for the report ID
static uint8_t HID_VENDOR_REPORT_SIZE = 63;
static uint8_t feature[64];
static QueueHandle_t rx_queue = NULL;
static bool prepend_size = false;

USBHIDVendor::USBHIDVendor(uint8_t report_size, bool prepend): hid(){
    static bool initialized = false;
    if(!initialized){
        initialized = true;
        hid.addDevice(this, TUD_HID_REPORT_DESC_GENERIC_INOUT_FEATURE_LEN);
        memset(feature, 0, 64);
        if(report_size < 64){
            HID_VENDOR_REPORT_SIZE = report_size;
        }
        prepend_size = prepend;
    }
}

uint16_t USBHIDVendor::_onGetDescriptor(uint8_t* dst){
    uint8_t report_descriptor[] = {
        TUD_HID_REPORT_DESC_GENERIC_INOUT_FEATURE(HID_VENDOR_REPORT_SIZE, HID_REPORT_ID(HID_REPORT_ID_VENDOR))
    };
    memcpy(dst, report_descriptor, sizeof(report_descriptor));
    return sizeof(report_descriptor);
}

void USBHIDVendor::prependInputPacketsWithSize(bool enable){
    prepend_size = enable;
}

size_t USBHIDVendor::setRxBufferSize(size_t rx_queue_len){
    if(rx_queue){
        if(!rx_queue_len){
            vQueueDelete(rx_queue);
            rx_queue = NULL;
        }
        return 0;
    }
    rx_queue = xQueueCreate(rx_queue_len, sizeof(uint8_t));
    if(!rx_queue){
        return 0;
    }
    return rx_queue_len;
}

void USBHIDVendor::begin(){
    hid.begin();
    setRxBufferSize(256);//default if not preset
}

void USBHIDVendor::end(){
    setRxBufferSize(0);
}

void USBHIDVendor::onEvent(esp_event_handler_t callback){
    onEvent(ARDUINO_USB_HID_VENDOR_ANY_EVENT, callback);
}

void USBHIDVendor::onEvent(arduino_usb_hid_vendor_event_t event, esp_event_handler_t callback){
    arduino_usb_event_handler_register_with(ARDUINO_USB_HID_VENDOR_EVENTS, event, callback, this);
}

uint16_t USBHIDVendor::_onGetFeature(uint8_t report_id, uint8_t* buffer, uint16_t len){
    if(report_id != HID_REPORT_ID_VENDOR){
        return 0;
    }
    memcpy(buffer, feature, len);
    arduino_usb_hid_vendor_event_data_t p;
    p.buffer = feature;
    p.len = len;
    arduino_usb_event_post(ARDUINO_USB_HID_VENDOR_EVENTS, ARDUINO_USB_HID_VENDOR_GET_FEATURE_EVENT, &p, sizeof(arduino_usb_hid_vendor_event_data_t), portMAX_DELAY);
    return len;
}

void USBHIDVendor::_onSetFeature(uint8_t report_id, const uint8_t* buffer, uint16_t len){
    if(report_id != HID_REPORT_ID_VENDOR){
        return;
    }
    memcpy(feature, buffer, len);
    arduino_usb_hid_vendor_event_data_t p;
    p.buffer = feature;
    p.len = len;
    arduino_usb_event_post(ARDUINO_USB_HID_VENDOR_EVENTS, ARDUINO_USB_HID_VENDOR_SET_FEATURE_EVENT, &p, sizeof(arduino_usb_hid_vendor_event_data_t), portMAX_DELAY);
}

void USBHIDVendor::_onOutput(uint8_t report_id, const uint8_t* buffer, uint16_t len){
    if(report_id != HID_REPORT_ID_VENDOR){
        return;
    }
    for(uint32_t i=0; i<len; i++){
        if(rx_queue == NULL || !xQueueSend(rx_queue, buffer+i, 0)){
            len = i+1;
            log_e("RX Queue Overflow");
            break;
        }
    }
    arduino_usb_hid_vendor_event_data_t p;
    p.buffer = buffer;
    p.len = len;
    arduino_usb_event_post(ARDUINO_USB_HID_VENDOR_EVENTS, ARDUINO_USB_HID_VENDOR_OUTPUT_EVENT, &p, sizeof(arduino_usb_hid_vendor_event_data_t), portMAX_DELAY);
}

size_t USBHIDVendor::write(const uint8_t* buffer, size_t len){
    uint8_t hid_in[HID_VENDOR_REPORT_SIZE];
    const uint8_t * data = (const uint8_t *)buffer;
    uint8_t size_offset = prepend_size?1:0;
    size_t to_send = len, max_send=HID_VENDOR_REPORT_SIZE - size_offset, will_send=0;
    while(to_send){
        will_send = to_send;
        if(will_send > max_send){
            will_send = max_send;
        }
        if(prepend_size){
            hid_in[0] = will_send;
        }
        // We can get INPUT only when data length equals the input report size
        memcpy(hid_in + size_offset, data, will_send);
        // pad with zeroes
        memset(hid_in + size_offset + will_send, 0, max_send - will_send);
        if(!hid.SendReport(HID_REPORT_ID_VENDOR, hid_in, HID_VENDOR_REPORT_SIZE)){
            return len - to_send;
        }
        to_send -= will_send;
        data += will_send;
    }
    return len;
}

size_t USBHIDVendor::write(uint8_t c){
    return write(&c, 1);
}

int USBHIDVendor::available(void){
    if(rx_queue == NULL){
        return -1;
    }
    return uxQueueMessagesWaiting(rx_queue);
}

int USBHIDVendor::peek(void){
    if(rx_queue == NULL){
        return -1;
    }
    uint8_t c;
    if(xQueuePeek(rx_queue, &c, 0)) {
        return c;
    }
    return -1;
}

int USBHIDVendor::read(void){
    if(rx_queue == NULL){
        return -1;
    }
    uint8_t c = 0;
    if(xQueueReceive(rx_queue, &c, 0)) {
        return c;
    }
    return -1;
}

size_t USBHIDVendor::read(uint8_t *buffer, size_t size){
    if(rx_queue == NULL){
        return -1;
    }
    uint8_t c = 0;
    size_t count = 0;
    while(count < size && xQueueReceive(rx_queue, &c, 0)){
        buffer[count++] = c;
    }
    return count;
}

void USBHIDVendor::flush(void){}


#endif /* CONFIG_TINYUSB_HID_ENABLED */
#endif /* SOC_USB_OTG_SUPPORTED */
