#include <Arduino.h>

#include "usbflasher.h"

#ifdef HAS_USB
// flashing via 2nd USB port
#include "USB.h"
USBCDC USBSerial;
#define cmdSerial USBSerial
#else
#define cmdSerial Serial
#endif

// #include "esp32-hal-tinyusb.h"
#include "flasher.h"
#include "leds.h"
#include "powermgt.h"
#include "settings.h"
#include "swd.h"
#include "web.h"
#include "webflasher.h"
#include "zbs_interface.h"
#include "cc_interface.h"

QueueHandle_t flasherCmdQueue;

uint32_t usbConnectedStartTime = 0;
extern bool serialPassthroughState;

#define FLASHER_WAIT_A 0
#define FLASHER_WAIT_T 1
#define FLASHER_WAIT_CMD 2
#define FLASHER_WAIT_LEN 3
#define FLASHER_WAIT_DATA 4
#define FLASHER_WAIT_CRCH 5
#define FLASHER_WAIT_CRCL 6
#define FLASHER_RESET 7

struct flasherCommand {
    uint8_t command = 0;
    uint32_t len = 0;
    uint8_t* data = nullptr;
};

int8_t powerPins[] = FLASHER_AP_POWER;
#ifdef HAS_EXT_FLASHER
int8_t powerPins2[] = FLASHER_EXT_POWER;
int8_t powerPins3[] = FLASHER_ALT_POWER;
#endif

bool autoFlash(flasher* f) {
    f->getFirmwareMD5();

    if (f->findTagByMD5()) {
        // this tag currently contains original firmware, found its fingerprint
        cmdSerial.printf("Found original firmware tag, recognized its fingerprint (%s)\r\n", f->md5char);
        f->readInfoBlock();
        f->getFirmwareMac();
        f->prepareInfoBlock();
        f->writeInfoBlock();
        cmdSerial.printf("Attempting to perform a flash...\r\n");
        if (f->writeFlashFromPack("/Tag_FW_Pack.bin", f->tagtype)) {
            cmdSerial.printf("Successfully flashed the tag!\r\n");
            return true;
        } else {
            cmdSerial.printf("Couldn't flash the tag, for some reason...\r\n");
        }
    } else if (f->getInfoBlockMD5()) {
        // did find an infoblock MD5 that looks valid
        if (f->findTagByMD5()) {
            // did find the md5 in the database
            cmdSerial.printf("Found an already-flashed tag, recognized its fingerprint (%s)\r\n", f->md5char);
            f->getInfoBlockMac();
            f->getInfoBlockType();
            f->readInfoBlock();
            cmdSerial.printf("Attempting to perform a flash...\r\n");
            if (f->writeFlashFromPack("/Tag_FW_Pack.bin", f->tagtype)) {
                cmdSerial.printf("Successfully flashed the tag!\r\n");
                return true;
            } else {
                cmdSerial.printf("Couldn't flash the tag, for some reason...\r\n");
            }
        } else {
            // couldn't find the md5 from the infoblock
            cmdSerial.printf("Found an already-flashed tag, but we couldn't find its fingerprint (%s) in the database\r\n", f->md5char);
            return false;
        }
    } else {
        // We couldn't recognize the tag from it's fingerprint...
        cmdSerial.printf("Found a tag but didn't recognize its fingerprint\r\n", f->md5char);
        f->backupFlash();
        cmdSerial.printf("Saved this MD5 binary to filesystem\r\n");
    }
    return false;
}

void sendFlasherAnswer(uint8_t answer_cmd, uint8_t* ans_buff, uint32_t len, uint8_t transportType) {
    uint8_t* answer_buffer = (uint8_t*)calloc(3 + 2 + 2 + len + 2 + 13, 1);
    if (answer_buffer == nullptr) return;
    uint32_t CRC_value = 0xAB34;
    answer_buffer[0] = 'A';
    answer_buffer[1] = 'T';
    answer_buffer[2] = answer_cmd;
    CRC_value += answer_cmd;

    for (uint8_t c = 0; c < 4; c++) {
        answer_buffer[3 + c] = (uint8_t)(len >> (24 - (c * 8)));
        CRC_value += answer_buffer[3 + c];
    }

    for (int i = 0; i < len; i++) {
        answer_buffer[7 + i] = ans_buff[i];
        CRC_value += ans_buff[i];
    }

    answer_buffer[3 + 2 + 2 + len] = CRC_value >> 8;
    answer_buffer[3 + 2 + 2 + len + 1] = CRC_value;
    if (transportType == TRANSPORT_USB) {
        cmdSerial.write(answer_buffer, 3 + 2 + 2 + len + 2);
    } else {
        sendDataToClient(answer_buffer, 3 + 2 + 2 + len + 2);
    }
    // for(uint16_t c = 0; c< 3+2+2+len+2; c++){

    //}
    free(answer_buffer);
}

void flasherDataHandler(uint8_t* data, size_t len, uint8_t transportType) {
    static struct flasherCommand* cmd = nullptr;
    static uint8_t flasherSerialState = FLASHER_WAIT_A;
    static uint32_t flasherCmdDataIndex = 0;
    static uint16_t flasherCRC = 0xAB34;
    static uint32_t flasherLastCmd = 0;
    static uint8_t curLenIndex = 0;

    if ((flasherSerialState != FLASHER_WAIT_A) && (millis() - flasherLastCmd >= 3000)) {
        flasherSerialState = FLASHER_RESET;
    }

    while (len--) {
        uint8_t usbbyte = *(data++);
        switch (flasherSerialState) {
            case FLASHER_RESET:
                if (transportType == TRANSPORT_TCP && cmd != nullptr) {
                    if (cmd->data != nullptr) {
                        free(cmd->data);
                        cmd->data = nullptr;
                    }
                    delete cmd;
                    cmd = nullptr;
                }
                flasherSerialState = FLASHER_WAIT_A;
            case FLASHER_WAIT_A:
                if (usbbyte == 'A') {
                    flasherSerialState = FLASHER_WAIT_T;
                    flasherLastCmd = millis();
                } else {
                    // enterConsoleMode();
                }
                break;
            case FLASHER_WAIT_T:
                if (usbbyte == 'T') {
                    flasherSerialState = FLASHER_WAIT_CMD;
                    cmd = new flasherCommand;
                    flasherCRC = 0xAB34;
                    flasherCmdDataIndex = 0;
                } else {
                    flasherSerialState = FLASHER_RESET;
                }
                break;
            case FLASHER_WAIT_CMD:
                cmd->command = usbbyte;
                flasherCRC += usbbyte;
                flasherSerialState = FLASHER_WAIT_LEN;
                curLenIndex = 0;
                break;
            case FLASHER_WAIT_LEN:
                flasherCRC += usbbyte;
                cmd->len |= ((uint32_t)usbbyte) << (24 - (8 * curLenIndex));
                curLenIndex++;
                if (curLenIndex == sizeof(cmd->len)) {
                    if (cmd->len) {
                        // not 0
                        cmd->data = (uint8_t*)calloc(cmd->len, 1);
                        if (cmd->data == nullptr) {
                            delete cmd;
                            cmd = nullptr;
                            flasherSerialState = FLASHER_WAIT_A;
                        } else {
                            flasherSerialState = FLASHER_WAIT_DATA;
                        }
                    } else {
                        // 0 len, so skip to CRC immediately
                        flasherSerialState = FLASHER_WAIT_CRCH;
                    }
                }
                break;
            case FLASHER_WAIT_DATA:
                flasherCRC += usbbyte;
                cmd->data[flasherCmdDataIndex++] = usbbyte;
                if (flasherCmdDataIndex == cmd->len) {
                    flasherSerialState = FLASHER_WAIT_CRCH;
                }
                break;
            case FLASHER_WAIT_CRCH:
                flasherCRC -= ((uint16_t)usbbyte << 8);
                flasherSerialState = FLASHER_WAIT_CRCL;
                break;
            case FLASHER_WAIT_CRCL:
                flasherCRC -= ((uint16_t)usbbyte);
                if (flasherCRC) {
                    flasherSerialState = FLASHER_RESET;
                    wsSerial("failed CRC");
                } else {
                    if (transportType == TRANSPORT_USB) {
                        BaseType_t queuestatus = xQueueSend(flasherCmdQueue, &cmd, 0);
                    } else {
                        processFlasherCommand(cmd, TRANSPORT_TCP);
                    }
                    flasherSerialState = FLASHER_RESET;
                }
                break;
        }
    }
}

void resetFlasherState() {
    if (serialPassthroughState) {
        Serial2.end();
    }
    serialPassthroughState = false;
}

#ifdef HAS_USB
static void usbEventCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
    if (event_base == ARDUINO_USB_EVENTS) {
        arduino_usb_event_data_t* data = (arduino_usb_event_data_t*)event_data;
        switch (event_id) {
            case ARDUINO_USB_STARTED_EVENT:
                Serial.println("USB PLUGGED");
                resetFlasherState();
                break;
            case ARDUINO_USB_STOPPED_EVENT:
                Serial.println("USB UNPLUGGED");
                resetFlasherState();
                break;
            case ARDUINO_USB_SUSPEND_EVENT:
                Serial.printf("USB SUSPENDED: remote_wakeup_en: %u\r\n", data->suspend.remote_wakeup_en);
                break;
            case ARDUINO_USB_RESUME_EVENT:
                Serial.println("USB RESUMED");
                break;
            default:
                ets_printf("other USB event %d\n", event_id);
                break;
        }
    } else if (event_base == ARDUINO_USB_CDC_EVENTS) {
        arduino_usb_cdc_event_data_t* data = (arduino_usb_cdc_event_data_t*)event_data;
        switch (event_id) {
            case ARDUINO_USB_CDC_CONNECTED_EVENT:
                ets_printf("CDC CONNECTED\n");
                resetFlasherState();
                usbConnectedStartTime = millis();
                break;
            case ARDUINO_USB_CDC_DISCONNECTED_EVENT:
                ets_printf("CDC DISCONNECTED\n");
                resetFlasherState();
                break;
            case ARDUINO_USB_CDC_LINE_STATE_EVENT:
                ets_printf("CDC LINE STATE: dtr: %u, rts: %u\n", data->line_state.dtr, data->line_state.rts);
                if (data->line_state.dtr == 0) resetFlasherState();
                break;
            case ARDUINO_USB_CDC_LINE_CODING_EVENT:
                ets_printf("CDC LINE CODING: bit_rate: %u, data_bits: %u, stop_bits: %u, parity: %u\n", data->line_coding.bit_rate, data->line_coding.data_bits, data->line_coding.stop_bits, data->line_coding.parity);
                resetFlasherState();
                break;
            case ARDUINO_USB_CDC_RX_EVENT:
                // Serial.printf("CDC RX [%u]:", data->rx.len);
                {
                    uint8_t buf[data->rx.len];
                    size_t len = cmdSerial.read(buf, data->rx.len);
                    if (serialPassthroughState) {
                        Serial2.write(buf, len);
                    } else {
                        flasherDataHandler(buf, len, TRANSPORT_USB);
                    }
                }
                break;
            case ARDUINO_USB_CDC_RX_OVERFLOW_EVENT:
                Serial.printf("CDC RX Overflow of %d bytes", data->rx_overflow.dropped_bytes);
                break;

            default:
                break;
        }
    }
}
#endif

typedef enum {
    CMD_GET_VERSION = 1,
    CMD_RESET_ESP = 2,
    CMD_RESET = 11,
    CMD_SET_POWER = 13,

    CMD_ERASE_FLASH = 26,
    CMD_ERASE_INFOPAGE = 27,
    CMD_SAVE_MAC_FROM_FW = 40,
    CMD_PASS_THROUGH = 50,

    CMD_SELECT_ZBS243 = 60,
    CMD_SELECT_NRF82511 = 61,
    CMD_SELECT_CC = 62,

    CMD_SELECT_PORT = 70,

    CMD_READ_INFOPAGE = 80,
    CMD_READ_FLASH = 81,
    CMD_WRITE_INFOPAGE = 82,
    CMD_WRITE_FLASH = 83,
    CMD_AUTOFLASH = 87,
    CMD_COMPLETE = 88,

    CMD_WRITE_ERROR = 99,

} ZBS_UART_PROTO;
uint32_t FLASHER_VERSION = 0x00000032;

#define CONTROLLER_ZBS243 0
#define CONTROLLER_NRF82511 1
#define CONTROLLER_CC      2
uint8_t selectedController = 0;
uint8_t selectedFlasherPort;
uint32_t currentFlasherOffset;
flasher* zbsflasherp = nullptr;
nrfswd* nrfflasherp = nullptr;
CC_interface *ccflasherp = nullptr;

void processFlasherCommand(struct flasherCommand* cmd, uint8_t transportType) {
    uint8_t* tempbuffer;
    uint8_t temp_buff[16];
    uint32_t spi_speed = 0;
    uint8_t powerPinCount = 1;
    static uint32_t curspeed = 0;
    uint8_t numPowerPins;

#ifdef HAS_RGB_LED
    shortBlink(CRGB::White);
#else
    quickBlink(2);
#endif

    switch (cmd->command) {
        case CMD_GET_VERSION:
            wsSerial("> get version");
            temp_buff[0] = FLASHER_VERSION >> 24;
            temp_buff[1] = FLASHER_VERSION >> 16;
            temp_buff[2] = FLASHER_VERSION >> 8;
            temp_buff[3] = FLASHER_VERSION;
            sendFlasherAnswer(cmd->command, temp_buff, 4, transportType);
            break;
        case CMD_RESET_ESP:
            wsSerial("reset");
            sendFlasherAnswer(cmd->command, NULL, 0, transportType);
            delay(100);
            ESP.restart();
            break;
        case CMD_SET_POWER:
            wsSerial("> power");
            switch (selectedFlasherPort) {
                case 0:
                    numPowerPins = sizeof(powerPins);
                    powerControl(cmd->data[0], (uint8_t*)powerPins, numPowerPins);
                    break;
                case 1:
                    numPowerPins = sizeof(powerPins2);
                    powerControl(cmd->data[0], (uint8_t*)powerPins2, numPowerPins);
                    break;
                case 2:
                    numPowerPins = sizeof(powerPins3);
                    powerControl(cmd->data[0], (uint8_t*)powerPins3, numPowerPins);
                    break;
            }
            sendFlasherAnswer(CMD_SET_POWER, NULL, 0, transportType);
            break;
        case CMD_RESET:
            wsSerial("> reset tag");
            if (zbsflasherp != nullptr) {
                zbsflasherp->zbs->reset();
                delete zbsflasherp;
                zbsflasherp = nullptr;
            }
            if (nrfflasherp != nullptr) {
                nrfflasherp->nrf_soft_reset();
                delete nrfflasherp;
                nrfflasherp = nullptr;
            }
            sendFlasherAnswer(CMD_RESET, NULL, 0, transportType);
            break;
        case CMD_ERASE_FLASH:
            wsSerial("> erase flash");
            if (selectedController == CONTROLLER_NRF82511) {
            } else if (selectedController == CONTROLLER_ZBS243) {
                if (zbsflasherp == nullptr) return;
                zbsflasherp->zbs->erase_flash();
            } else if (selectedController == CONTROLLER_CC) {
               if (ccflasherp == nullptr) return;
                ccflasherp->erase_chip();
            }
            sendFlasherAnswer(CMD_ERASE_FLASH, NULL, 0, transportType);
            break;
        case CMD_ERASE_INFOPAGE:
            wsSerial("> erase infopage");
            if (selectedController == CONTROLLER_NRF82511) {
                nrfflasherp->erase_uicr();
            } else if (selectedController == CONTROLLER_ZBS243) {
                if (zbsflasherp == nullptr) return;
                zbsflasherp->zbs->erase_infoblock();
            }
            sendFlasherAnswer(CMD_ERASE_INFOPAGE, NULL, 0, transportType);
            break;
        case CMD_SELECT_PORT:
            wsSerial("> select port");
            selectedFlasherPort = cmd->data[0];
            Serial.printf("Port selected = %d\r\n", cmd->data[0]);
            break;
        case CMD_SELECT_ZBS243:
            wsSerial("> connect zbs");
            zbsflasherp = new flasher;
            temp_buff[0] = zbsflasherp->connectTag(selectedFlasherPort);
            sendFlasherAnswer(CMD_SELECT_ZBS243, temp_buff, 1, transportType);
            currentFlasherOffset = 0;
            selectedController = CONTROLLER_ZBS243;
            break;
        case CMD_SELECT_NRF82511:
            wsSerial("> connect nrf");
            switch (selectedFlasherPort) {
                case 0:
                    numPowerPins = sizeof(powerPins);
                    powerControl(true, (uint8_t*)powerPins, numPowerPins);
                    nrfflasherp = new nrfswd(FLASHER_AP_MISO, FLASHER_AP_CLK);
                    break;
                case 1:
                    numPowerPins = sizeof(powerPins2);
                    powerControl(true, (uint8_t*)powerPins2, numPowerPins);
                    nrfflasherp = new nrfswd(FLASHER_EXT_MISO, FLASHER_EXT_CLK);
                    break;
                case 2:
                    numPowerPins = sizeof(powerPins3);
                    powerControl(true, (uint8_t*)powerPins3, numPowerPins);
                    nrfflasherp = new nrfswd(FLASHER_ALT_MISO, FLASHER_ALT_CLK);
                    break;
            }
            nrfflasherp->init();
            temp_buff[0] = (nrfflasherp->isConnected && !nrfflasherp->isLocked);
            sendFlasherAnswer(CMD_SELECT_NRF82511, temp_buff, 1, transportType);
            currentFlasherOffset = 0;
            selectedController = CONTROLLER_NRF82511;
            break;
       case CMD_SELECT_CC:
            ccflasherp = new CC_interface;
            ccflasherp->begin(FLASHER_EXT_CLK,FLASHER_EXT_MISO,FLASHER_EXT_RESET);
            temp_buff[0] = 1;
            sendFlasherAnswer(CMD_SELECT_CC, temp_buff, 1,transportType);
            currentFlasherOffset = 0;
            selectedController = CONTROLLER_CC;
            break;
        case CMD_READ_FLASH:
            wsSerial("> read flash");
            uint8_t* bufferp;
            uint32_t cur_len;
            if (selectedController == CONTROLLER_NRF82511) {
                if (nrfflasherp == nullptr) return;
                if (currentFlasherOffset >= nrfflasherp->nrf_info.flash_size) {
                    sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
                } else {
                    bufferp = (uint8_t*)malloc(1024);
                    if (bufferp == nullptr) return;
                    cur_len = (nrfflasherp->nrf_info.flash_size - currentFlasherOffset >= 1024) ? 1024 : nrfflasherp->nrf_info.flash_size - currentFlasherOffset;
                    nrfflasherp->nrf_read_bank(currentFlasherOffset, (uint32_t*)bufferp, cur_len);
                    currentFlasherOffset += cur_len;
                    sendFlasherAnswer(CMD_READ_FLASH, bufferp, cur_len, transportType);
                    if (bufferp != nullptr) free(bufferp);
                }
            } else if (selectedController == CONTROLLER_ZBS243) {
                if (zbsflasherp == nullptr) return;
                if (currentFlasherOffset >= 65536) {
                    sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
                } else {
                    bufferp = (uint8_t*)malloc(1024);
                    if (bufferp == nullptr) return;
                    cur_len = (65536 - currentFlasherOffset >= 1024) ? 1024 : 65536 - currentFlasherOffset;
                    zbsflasherp->readBlock(currentFlasherOffset, bufferp, cur_len, false);
                    currentFlasherOffset += cur_len;
                    sendFlasherAnswer(CMD_READ_FLASH, bufferp, cur_len, transportType);
                    if (bufferp != nullptr) free(bufferp);
                }
            } else if (selectedController == CONTROLLER_CC) {
                if (ccflasherp == nullptr) return;
                if (currentFlasherOffset >= 32768) {
                    sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1,transportType);
                } else {
                   bufferp = (uint8_t*)malloc(1024);
                   if (bufferp == nullptr) return;
                   cur_len = (32768 - currentFlasherOffset >= 1024) ? 1024 : 32768 - currentFlasherOffset;
                   ccflasherp->read_code_memory(currentFlasherOffset,cur_len,bufferp);
                   currentFlasherOffset += cur_len;
                   sendFlasherAnswer(CMD_READ_FLASH, bufferp, cur_len,transportType);
                   free(bufferp);
                }
            }
            break;
        case CMD_READ_INFOPAGE:
            wsSerial("> read infopage");
            uint8_t* ibufferp;
            uint32_t icur_len;
            if (selectedController == CONTROLLER_NRF82511) {
                if (nrfflasherp == nullptr) return;
                if (currentFlasherOffset >= 4096) {
                    sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
                } else {
                    ibufferp = (uint8_t*)malloc(1024);
                    if (ibufferp == nullptr) return;
                    icur_len = (4096 - currentFlasherOffset >= 256) ? 256 : 4096 - currentFlasherOffset;
                    nrfflasherp->nrf_read_bank(0x10001000 + currentFlasherOffset, (uint32_t*)ibufferp, icur_len);
                    currentFlasherOffset += icur_len;
                    sendFlasherAnswer(CMD_READ_INFOPAGE, ibufferp, icur_len, transportType);
                    if (ibufferp != nullptr) free(ibufferp);
                }
            } else if (selectedController == CONTROLLER_ZBS243) {
                if (zbsflasherp == nullptr) return;
                if (currentFlasherOffset >= 1024) {
                    sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
                } else {
                    ibufferp = (uint8_t*)malloc(1024);
                    if (ibufferp == nullptr) return;
                    icur_len = (1024 - currentFlasherOffset >= 256) ? 256 : 1024 - currentFlasherOffset;
                    zbsflasherp->readBlock(currentFlasherOffset, ibufferp, icur_len, true);
                    currentFlasherOffset += icur_len;
                    sendFlasherAnswer(CMD_READ_INFOPAGE, ibufferp, icur_len, transportType);
                    if (ibufferp != nullptr) free(ibufferp);
                }
            }
            break;
        case CMD_WRITE_FLASH:
            Serial.println("> write flash");
            if (selectedController == CONTROLLER_NRF82511) {
                if (nrfflasherp == nullptr) return;
                if (currentFlasherOffset >= nrfflasherp->nrf_info.flash_size) {
                    sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
                } else {
                    for (uint32_t c = currentFlasherOffset; c < (currentFlasherOffset + cmd->len);) {
                        // very ugly and naive way to find out what page we're in, and erase all relevant pages before writing
                        if (c % nrfflasherp->nrf_info.codepage_size == 0) {
                            nrfflasherp->erase_page(c);
                            Serial.printf("Erasing page %lu\r\n", c);
                            c += nrfflasherp->nrf_info.codepage_size;
                        } else {
                            c++;
                        }
                    }
                    uint8_t result = nrfflasherp->nrf_write_bank(currentFlasherOffset, (uint32_t*)cmd->data, cmd->len);
                    Serial.printf("wrote page offset %lu to nrf\r\n", currentFlasherOffset);
                    currentFlasherOffset += cmd->len;
                    if (result == 3) {
                        sendFlasherAnswer(CMD_WRITE_ERROR, NULL, 0, transportType);
                        return;
                    }
                    sendFlasherAnswer(CMD_WRITE_FLASH, NULL, 0, transportType);
                }
            } else if (selectedController == CONTROLLER_ZBS243) {
                if (zbsflasherp == nullptr) return;
                if (currentFlasherOffset >= 65536) {
                    sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
                } else {
                    zbsflasherp->writeBlock(currentFlasherOffset, cmd->data, cmd->len, false);
                    currentFlasherOffset += cmd->len;
                    sendFlasherAnswer(CMD_WRITE_FLASH, NULL, 0, transportType);
                }
            } else if (selectedController == CONTROLLER_CC) {
               if (currentFlasherOffset >= 32768) {
                  sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
               } else {
                  if (ccflasherp == nullptr) return;
                   ccflasherp->write_code_memory(currentFlasherOffset, cmd->data, cmd->len);
                   currentFlasherOffset += cmd->len;
                   sendFlasherAnswer(CMD_WRITE_FLASH, NULL, 0, transportType);
               }
            }
            break;
        case CMD_WRITE_INFOPAGE:
            wsSerial("> write infopage");
            if (selectedController == CONTROLLER_NRF82511) {
                if (nrfflasherp == nullptr) return;
                if (currentFlasherOffset >= 4096) {
                    sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
                } else {
                    uint8_t result =  nrfflasherp->nrf_write_bank(0x10001000 + currentFlasherOffset, (uint32_t*)cmd->data, cmd->len);
                    Serial.printf("wrote infopage to nrf\r\n");
                    currentFlasherOffset += cmd->len;
                    if (result == 3) {
                        sendFlasherAnswer(CMD_WRITE_ERROR, NULL, 0, transportType);
                        return;
                    }
                    sendFlasherAnswer(CMD_WRITE_INFOPAGE, NULL, 0, transportType);
                }
            } else if (selectedController == CONTROLLER_ZBS243) {
                if (zbsflasherp == nullptr) return;
                if (currentFlasherOffset >= 1024) {
                    sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
                } else {
                    zbsflasherp->writeBlock(currentFlasherOffset, cmd->data, cmd->len, true);
                    currentFlasherOffset += cmd->len;
                    sendFlasherAnswer(CMD_WRITE_INFOPAGE, NULL, 0, transportType);
                }
            }
            break;
        case CMD_PASS_THROUGH:
            wsSerial("> pass through");

            extern bool rxSerialStopTask2;
            rxSerialStopTask2 = true;
            vTaskDelay(500 / portTICK_PERIOD_MS);

            if (Serial2) Serial2.end();
            Serial2.begin(115200, SERIAL_8N1, FLASHER_EXT_RXD, FLASHER_EXT_TXD);
            cmdSerial.println(">>>");
            serialPassthroughState = true;
            break;
        case CMD_AUTOFLASH:
            wsSerial("> autoflash");
            if (selectedController == CONTROLLER_ZBS243) {
                autoFlash(zbsflasherp);
                zbsflasherp->zbs->reset();
                delete zbsflasherp;
                zbsflasherp = nullptr;
                cmdSerial.write(0x04);
            } else {
                cmdSerial.println("Not yet implemented!");
            }
            break;

       default:
          cmdSerial.printf("Ignored flasher command 0x%x.\r\n",cmd->command);
          break;
    }
}

uint32_t lastCmdTimeStamp = 0;
#define USBFLASHER_CONNECTION_TIMEOUT 5000
void flasherCommandTimeout() {
    // this is triggered if no command has been received for a couple of seconds; this makes sure everything is cleaned up when the USB connection is lost
    if (zbsflasherp != nullptr) {
        delete zbsflasherp;
        zbsflasherp = nullptr;
    }
    if (nrfflasherp != nullptr) {
        delete nrfflasherp;
        nrfflasherp = nullptr;
    }
    if (ccflasherp != nullptr) {
        delete ccflasherp;
        ccflasherp = nullptr;
    }
    lastCmdTimeStamp = 0;
}

void tagDebugPassthrough() {
    // static String accumulatedData = "";
    // static unsigned long flushTimer = 0;
    uint16_t len = Serial2.available();

    if (len > 0) {
        uint8_t* buf = (uint8_t*)malloc(len);
        Serial2.readBytes(buf, len);
        cmdSerial.printf("%d bytes: ", len);
        cmdSerial.write(buf, len);
        cmdSerial.print("\r\n");
        //String dataString((char*)buf, len);
        //wsSerial(dataString, "cyan");
        //  accumulatedData += dataString;
        free(buf);
    }
    /*
    if (millis() - flushTimer > 500 && accumulatedData.length() > 0) {
        // wsSerial("*", "cyan");
        flushTimer = millis();
        accumulatedData = "";
    }
    */
}

#ifdef HAS_USB
void usbFlasherTask(void* parameter) {
    flasherCmdQueue = xQueueCreate(10, sizeof(struct flasherCommand*));

#ifndef ARDUINO_USB_MODE
#error This ESP32 SoC has no Native USB interface
#elif ARDUINO_USB_MODE == 1
#warning This sketch should be used when USB is in OTG mode. Wrong USB mode is in use, check settings in platformio.ini
#endif
    USB.onEvent(usbEventCallback);
    cmdSerial.onEvent(usbEventCallback);
    cmdSerial.setTimeout(1000);
    USB.productName("OpenEpaperLink-flasher");
    USB.begin();
    cmdSerial.begin();
    Serial.println("Task started");
    uint32_t notificationValue;
    struct flasherCommand* cmd = nullptr;
    while (true) {
        while (serialPassthroughState) {
            tagDebugPassthrough();
            vTaskDelay(1 / portTICK_PERIOD_MS);
        }

        BaseType_t queuereceive = xQueueReceive(flasherCmdQueue, &cmd, 1000 / portTICK_PERIOD_MS);  // timeout every second to make sure the timeout gets triggered after a while
        if (queuereceive == pdTRUE) {
            processFlasherCommand(cmd, TRANSPORT_USB);
            lastCmdTimeStamp = millis();
            if (cmd->data != nullptr) {
                free(cmd->data);
                cmd->data = nullptr;
            }
            delete cmd;
            cmd = nullptr;
        } else {
            if (lastCmdTimeStamp) {
                if (millis() - lastCmdTimeStamp > USBFLASHER_CONNECTION_TIMEOUT)
                flasherCommandTimeout();
            }
        }

        vTaskDelay(50 / portTICK_PERIOD_MS);
    }
}
#endif

