#include "pxt.h"

#if CONFIG_ENABLED(DEVICE_USB)
#include "uf2format.h"

namespace pxt {
CodalUSB usb;

// share the buffer; we will crash anyway if someone talks to us over both at the same time
HF2_Buffer hf2buf;
HF2 hf2(hf2buf);
#ifdef HF2_HID
HF2 hf2hid(hf2buf);
#endif
DummyIface dummyIface;

#if CONFIG_ENABLED(DEVICE_MOUSE)
USBHIDMouse mouse;
#endif
#if CONFIG_ENABLED(DEVICE_KEYBOARD)
USBHIDKeyboard keyboard;
#endif
#if CONFIG_ENABLED(DEVICE_JOYSTICK)
USBHIDJoystick joystick;
#endif

static const DeviceDescriptor device_desc = {
    0x12,   // bLength
    0x01,   // bDescriptorType
    0x0210, // bcdUSBL

    // Class etc specified per-interface
    0x00, 0x00, 0x00,

    0x40, // bMaxPacketSize0
    USB_DEFAULT_VID, USB_DEFAULT_PID,
    0x4202, // bcdDevice - leave unchanged for the HF2 to work
    0x01,   // iManufacturer
    0x02,   // iProduct
    0x03,   // SerialNumber
    0x01    // bNumConfigs
};

static void start_usb() {
    // start USB with a delay, so that user code can add new interfaces if needed
    // (eg USB HID keyboard, or MSC)
    fiber_sleep(500);
    usb.start();
}

void platform_usb_init() __attribute__((weak));
void platform_usb_init() {}

void set_usb_strings(const char *uf2_info) {
    static const char *string_descriptors[3];
    static char serial[12];
    itoa(target_get_serial() & 0x7fffffff, serial);

    auto model = strstr(uf2_info, "Model: ");
    if (model) {
        model += 7;
        auto end = model;
        while (*end && *end != '\n' && *end != '\r')
            end++;
        auto len = end - model;
        auto dev = (char *)app_alloc(len + 10);
        memcpy(dev, model, len);
        strcpy(dev + len, " (app)");
        // try to split into manufacturer and
        auto sep = strstr(dev, " / ");
        if (sep) {
            *sep = '\0';
            string_descriptors[0] = dev;
            string_descriptors[1] = sep + 3;
        } else {
            string_descriptors[0] = dev;
            string_descriptors[1] = dev;
        }
    } else {
        string_descriptors[0] = "Unknown Corp.";
        string_descriptors[1] = "PXT Device (app)";
    }

    string_descriptors[2] = serial;
    usb.stringDescriptors = string_descriptors;
}

void usb_init() {
    usb.deviceDescriptor = &device_desc;
    set_usb_strings(UF2_INFO_TXT);

    platform_usb_init();

    usb.add(hf2);

#ifdef HF2_HID
    hf2hid.useHID = true;
    usb.add(hf2hid);
#else
    // the WINUSB descriptors don't seem to work if there's only one interface
    // so we add a dummy interface
    usb.add(dummyIface);
#endif

#if CONFIG_ENABLED(DEVICE_MOUSE)
    usb.add(mouse);
#endif
#if CONFIG_ENABLED(DEVICE_KEYBOARD)
    usb.add(keyboard);
#endif
#if CONFIG_ENABLED(DEVICE_JOYSTICK)
    usb.add(joystick);
#endif

    create_fiber(start_usb);
}

} // namespace pxt

#else
namespace pxt {
void usb_init() {}
} // namespace pxt
#endif

namespace control {
/**
 * Determines if the USB has been enumerated.
 */
//%
bool isUSBInitialized() {
#if CONFIG_ENABLED(DEVICE_USB)
    return pxt::usb.isInitialised();
#else
    return false;
#endif
}
} // namespace control

namespace pxt {
static void (*pSendToUART)(const char *data, int len) = NULL;
void setSendToUART(void (*f)(const char *, int)) {
    pSendToUART = f;
}

void sendSerial(const char *data, int len) {
#if CONFIG_ENABLED(DEVICE_USB)
    hf2.sendSerial(data, len);
#if HF2_HID
    hf2hid.sendSerial(data, len);
#endif
#endif
    if (pSendToUART)
        pSendToUART(data, len);
}

void dumpDmesg() {
    sendSerial("\nDMESG:\n", 8);
    sendSerial(codalLogStore.buffer, codalLogStore.ptr);
    sendSerial("\n\n", 2);
}

void (*logJDFrame)(const uint8_t *data);
void (*sendJDFrame)(const uint8_t *data);

} // namespace pxt
