/* Copyright (C) 2024 Kevin Koster, OmberTech. All rights reserved.

 This software may be distributed and modified under the terms of the GNU
 General Public License version 2 (GPL2) as published by the Free Software
 Foundation and appearing in the file GPL2.TXT included in the packaging of
 this file. Please note that GPL2 Section 2[b] requires that all works based
 on this software must also be made publicly available under the terms of
 the GPL2 ("Copyleft").
*/

#ifndef _hidjoyusb_h_
#define _hidjoyusb_h_

#include "hiduniversal.h"
#include "HIDJoyParser.h"

//#define PRINTREPORTDESC

/**
 * This class implements support for the joystick controller via USB.
 * It uses the HIDUniversal class for all the USB communication.
 */
class HIDJoyUSB : public HIDUniversal, public HIDJoyParser {
public:
        /**
         * Constructor for the HIDJoyUSB class.
         * @param  p   Pointer to the USB class instance.
         */
        HIDJoyUSB(USB *p) :
        HIDUniversal(p) {
                HIDJoyParser::Reset();
        };

        /**
         * Used to check if a joystick controller is connected.
         * @return Returns true if it is connected.
         */
        bool connected() {
		//printf ("ready=%d\n", HIDUniversal::isReady());
                return HIDUniversal::isReady();
        };

        /**
         * Used to call your own function when the device is successfully initialized.
         * @param funcOnInit Function to call.
         */
        void attachOnInit(void (*funcOnInit)(void)) {
                pFuncOnInit = funcOnInit;
        };

protected:
        /** @name HIDUniversal implementation */
        /**
         * Used to parse USB HID data.
         * @param hid       Pointer to the HID class.
         * @param is_rpt_id Only used for Hubs.
         * @param len       The length of the incoming data.
         * @param buf       Pointer to the data buffer.
         */
        virtual void ParseHIDData(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
                HIDJoyParser::Parse(len, buf);
        };

        /**
         * Called when a device is successfully initialized.
         */
        virtual uint8_t OnInitSuccessful() {
                puts ("HIDJoyUSB OnInitSuccessful");
                HIDUniversal::bNumIface = 1; // Only use first HID interface
                HIDJoyParser::Init();
                if (HIDJoyParser::initDone)
                  return 0;
                else
                  return 1;
        };
        /**@}*/

        /** @name HIDJoyParser implementation */
        /*   TODO: Try multiple interfaces if first one fails? */
        virtual uint8_t* getHIDReportDescriptor(uint16_t *desc_len) {
            /* See hid1_11.pdf pg. 21 (PDF pg. 31) and mouse examples on pg. 70 (PDF pg. 80) */
            /* Reads from the first HID-class interface, which was detected in HIDUniversal::Init*/
            uint8_t rcode;
            uint8_t desc_hid[8]; // HID Descriptor - Contains length of HID Descriptor/s (first one should be a HID Report Descriptor)
            USB_HID_DESCRIPTOR *uhd = reinterpret_cast<USB_HID_DESCRIPTOR *>(desc_hid);
            uint8_t *desc_report = NULL; // HID Report Descriptor - Describes format of interface's HID Reports
            DWORD bytesIn;

            if (HIDUniversal::VID != 0x046d) { // With Logitech Extreme 3D Pro joystick request fails, causing 34sec wait for NAK time-out
              rcode = pUsb->ctrlReq(bAddress, 0x00, bmREQ_GET_DESCR, USB_REQUEST_GET_DESCRIPTOR, 0x00, HID_DESCRIPTOR_HID, 0, 9,
                     9, desc_hid, NULL); // Get the HID Descriptor
              if (rcode) {
#ifdef DEBUG_USB_HOST
                printf ("(HIDJoyUSB) HID Descriptor Request Failed, using default values, ret: 0x%X\n", rcode);
#endif
               *desc_len = FALLBACK_HID_REPORT_DESCRIPTOR_SIZE;
              }
              else {
                *desc_len = uhd->wDescriptorLength;
                if (*desc_len == 0) {
#ifdef DEBUG_USB_HOST
                  puts("(HIDJoyUSB) Zero-length HID Report Descriptor");
#endif
                  return NULL;
                }
              }
#ifdef DEBUG_USB_HOST
            if (!rcode && uhd->bDescrType != 0x22)
              printf ("(HIDJoyUSB) bDescriptorType is 0x%X, not 0x22\n", uhd->bDescrType);
#endif
            }
            else {
#ifdef DEBUG_USB_HOST
                puts ("(HIDJoyUSB) Logitech detected, using FALLBACK_HID_REPORT_DESCRIPTOR_SIZE");
#endif
                *desc_len = FALLBACK_HID_REPORT_DESCRIPTOR_SIZE;
            }
            desc_report = (uint8_t*)malloc(*desc_len);
            if (desc_report == NULL) {
#ifdef DEBUG_USB_HOST
              printf("(HIDJoyUSB) Cannot allocate %dB for HID Report Descriptor\n", *desc_len);
#endif
              return desc_report;
            }

            rcode = pUsb->ctrlReq(bAddress, 0x00, bmREQ_HID_REPORT, USB_REQUEST_GET_DESCRIPTOR, 0x00,
                     HID_DESCRIPTOR_REPORT, 0, *desc_len, *desc_len, desc_report, NULL); // Get the HID Report Descriptor for interface 0
#ifdef DEBUG_USB_HOST
            if (rcode)
              printf ("(HIDJoyUSB) HID Report Descriptor Request Failed, ret: 0x%X\n", rcode);
#endif
            USBHostTransferIsComplete(bAddress, 0x00, &rcode, (DWORD*)&bytesIn); // Get number of bytes actually read
            *desc_len = (uint16_t)bytesIn;

#ifdef PRINTREPORTDESC
            for (uint8_t i = 0; i < *desc_len; i++) {
                printf("0x%X ", desc_report[i]);
            }
            puts("HIDJoyUSB::getHIDReportDescriptor");
#endif

            return desc_report;
        };
        /**@}*/

private:
        void (*pFuncOnInit)(void); // Pointer to function called in onInit()
};
#endif /*_hidjoyusb_h_*/
