/**
 * Handle the case where the device unexpectedly fails.
 * @param device The device the failed.
 * @param ec The error code from GetLastError()
 * @return The device error code.
 */
int handleDeviceFailure(struct FreespaceDeviceStruct* device, int ec) {
    int rc = FREESPACE_ERROR_UNEXPECTED;
    int formatRc = 0;
    LPVOID lpMsgBuf;

    // Retrieve the system error message for the last-error code
    formatRc = FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        ec,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );
    if (formatRc == 0) {
        DEBUG_WPRINTF(L"handleDeviceFailure: %d\n", ec);
    } else {
        // Display the error message and exit the process
        DEBUG_WPRINTF(L"handleDeviceFailure: %d => %s\n", ec, lpMsgBuf);
        LocalFree(lpMsgBuf);
    }

    // Clean up this device.
    freespace_private_removeDevice(device);
    freespace_private_forceCloseDevice(device);
    freespace_private_requestDeviceRescan();

    if (ec == ERROR_DEVICE_NOT_CONNECTED) {
        rc = FREESPACE_ERROR_NOT_FOUND;
    }

    return rc;
}
/*
 * Remove the devices that are no longer present in the system.
 */
int checkDiscoveryRemoveDevices() {
    int i;
    struct FreespaceDeviceStruct* list[FREESPACE_MAXIMUM_DEVICE_COUNT];
    int listLength = 0;

    // Collect the removed devices.
    freespace_private_filterDevices(list, FREESPACE_MAXIMUM_DEVICE_COUNT, &listLength, filterSweep);

    // Remove them from the device list so that future API calls fail to this device
    // fail. See callbacks after this loop.
    for (i = 0; i < listLength; i++) {
        int idx;
        DEBUG_WPRINTF(L"device %d removed\n", list[i]->id_);
        for (idx = 0; idx < freespace_instance_->deviceCount_; idx++) {
            if (list[i] == freespace_instance_->devices_[idx]) {
                memmove(&freespace_instance_->devices_[idx], &freespace_instance_->devices_[idx + 1], (freespace_instance_->deviceCount_ - idx - 1) * sizeof(struct FreespaceDeviceStruct*));
                freespace_instance_->deviceCount_--;
            }
        }
    }

    // Call the removal callbacks.
    for (i = 0; i < listLength; i++) {
        freespace_private_removeDevice(list[i]);
    }

    // Free the device structure.
    for (i = 0; i < listLength; i++) {
        freespace_private_freeDevice(list[i]);
    }

    return listLength;
}
int checkDiscovery() {
    if (freespace_private_discoveryStatusChanged()) {
        int rc;
        int totalChanges = 0;

        // Wait for system to stabilize before scanning.
        if (CMP_WaitNoPendingInstallEvents(0) == WAIT_TIMEOUT) {
            DEBUG_PRINTF("Pending install events.  Wait for resolution.\n");
            freespace_private_requestDeviceRescan();
            return FREESPACE_ERROR_BUSY;
        }

        DEBUG_WPRINTF(L"Scanning devices\n");

        // Mark and sweep the device list.
        freespace_private_filterDevices(NULL, 0, NULL, filterInitialize);

        // Mark everything that still exists and add new devices
        rc = freespace_private_scanAndAddDevices();
        if (rc != FREESPACE_SUCCESS) {
            // Unexpected error.  Schedule a rescan.
            DEBUG_WPRINTF(L"Error %d while scanning devices.  Request a rescan.\n", rc);
            freespace_private_requestDeviceRescan();
            return rc;
        }

        // Handle all changes.
        totalChanges += checkDiscoveryRemoveDevices();
        totalChanges += checkDiscoveryPartiallyRemovedDevices();
        totalChanges += checkDiscoveryAddedDevices();

        /*
         * Continue to schedule a rescan until no changes are detected.
         * Although this should not be necessary, rescanning until a
         * stable state is reached should increase robustness.
         */
        if (totalChanges != 0) {
            DEBUG_WPRINTF(L"Detected %d changes.  Schedule rescan\n", totalChanges);
            freespace_private_requestDeviceRescan();
        }
    }

    return freespace_private_discoveryGetThreadStatus();
}
/*
 * Process devices with multiple handles that are not fully complete.
 */
int checkDiscoveryAddedDevices() {
    struct FreespaceDeviceStruct* list[FREESPACE_MAXIMUM_DEVICE_COUNT];
    int listLength = 0;
    int i;

    // Collect the added devices and call the insertion callbacks
    freespace_private_filterDevices(list, FREESPACE_MAXIMUM_DEVICE_COUNT, &listLength, filterReady);
    for (i = 0; i < listLength; i++) {
        DEBUG_WPRINTF(L"device %d added\n", list[i]->id_);
    }
    for (i = 0; i < listLength; i++) {
        freespace_private_insertDevice(list[i]);
    }
    return listLength;
}
/*
 * Process devices with multiple handles that are not fully complete.
 */
int checkDiscoveryPartiallyRemovedDevices() {
    struct FreespaceDeviceStruct* list[FREESPACE_MAXIMUM_DEVICE_COUNT];
    int listLength = 0;
    int i;

    // Collect the removed devices and call the removed callbacks.
    freespace_private_filterDevices(list, FREESPACE_MAXIMUM_DEVICE_COUNT, &listLength, filterPartiallyRemoved);
    for (i = 0; i < listLength; i++) {
        DEBUG_WPRINTF(L"device %d partially removed\n", list[i]->id_);
    }
    for (i = 0; i < listLength; i++) {
        freespace_private_removeDevice(list[i]);
    }

    // Close file handles of devices that are open.
    for (i = 0; i < listLength; i++) {
        freespace_closeDevice(list[i]->id_);
    }

    return listLength;
}
LIBFREESPACE_API int freespace_openDevice(FreespaceDeviceId id) {
    int idx;
    struct FreespaceDeviceStruct* device = freespace_private_getDeviceById(id);
    if (device == NULL) {
        return FREESPACE_ERROR_NO_DEVICE;
    }

    if (device->isOpened_) {
        // Each device can only be opened once.
        return FREESPACE_ERROR_BUSY;
    }


    for (idx = 0; idx < device->handleCount_; idx++) {
        struct FreespaceSubStruct* s = &device->handle_[idx];
        if (s->handle_ != NULL) {
            // Device was partially (incorrectly) opened.
            freespace_private_forceCloseDevice(device);
            return FREESPACE_ERROR_BUSY;
        }
        if (s->devicePath == NULL) {
            // Device was not fully enumerated.
            freespace_private_forceCloseDevice(device);
            return FREESPACE_ERROR_NO_DEVICE;
        }
        DEBUG_WPRINTF(L"Open %s\n", s->devicePath);
        s->handle_ = CreateFile(s->devicePath,
                                GENERIC_READ | GENERIC_WRITE,
                                FILE_SHARE_READ | FILE_SHARE_WRITE,
                                NULL,
                                OPEN_EXISTING,
                                FILE_FLAG_OVERLAPPED,
                                NULL);
        {
            DWORD d;
            if (!GetHandleInformation(s->handle_, &d)) {
                // We do not have the correct handle.
                DEBUG_PRINTF("freespace_openDevice failed with code %d\n", GetLastError());
            }
        }

        if (s->handle_ == INVALID_HANDLE_VALUE) {
            freespace_private_forceCloseDevice(device);
            return FREESPACE_ERROR_NO_DEVICE;
        }

        if (!BindIoCompletionCallback(s->handle_, freespace_private_overlappedCallback, 0)) {
            freespace_private_forceCloseDevice(device);
            return FREESPACE_ERROR_UNEXPECTED;
        }

        if (!HidD_SetNumInputBuffers(s->handle_, HID_NUM_INPUT_BUFFERS)) {
            freespace_private_forceCloseDevice(device);
            return FREESPACE_ERROR_NO_DEVICE;
        }

        // Create the read event.
        s->readOverlapped_.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (s->readOverlapped_.hEvent == NULL) {
            freespace_private_forceCloseDevice(device);
            return FREESPACE_ERROR_UNEXPECTED;
        }
        s->readOverlapped_.Offset = 0;
        s->readOverlapped_.OffsetHigh = 0;
        s->readStatus_ = FALSE;
    }

    device->isOpened_ = TRUE;

    // Enable send by initializing all send events.
    for (idx = 0; idx < FREESPACE_MAXIMUM_SEND_MESSAGE_COUNT; idx++) {
        device->send_[idx].overlapped_.hEvent = NULL;
        if (initializeSendStruct(&device->send_[idx]) != FREESPACE_SUCCESS) {
            freespace_private_forceCloseDevice(device);
            return FREESPACE_ERROR_UNEXPECTED;
        }
    }

    // If async mode has been enabled already, then start the receive
    // process going.
    if (freespace_instance_->fdAddedCallback_) {
        int rc;
        rc = initiateAsyncReceives(device);
        if (rc != FREESPACE_SUCCESS) {
            freespace_private_forceCloseDevice(device);
            return rc;
        }
    }

    return FREESPACE_SUCCESS;
}
LRESULT CALLBACK discoveryCallback(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam) {
    if (nMsg == WM_CLOSE) {
        DestroyWindow(hwnd);
        return DefWindowProc(hwnd,	nMsg, wParam, lParam);
    }

    if (nMsg == WM_DESTROY) {
        DEBUG_WPRINTF(L"discoveryCallback on WM_DESTROY\n");

        // Remove the device notification
        if (freespace_instance_->windowEvent_) {
            UnregisterDeviceNotification(freespace_instance_->windowEvent_);
            freespace_instance_->windowEvent_ = NULL;
        }

        CancelWaitableTimer(freespace_instance_->discoveryEvent_);
        CloseHandle(freespace_instance_->discoveryEvent_);
        freespace_instance_->discoveryEvent_ = NULL;

        PostQuitMessage(0);
        return DefWindowProc(hwnd,	nMsg, wParam, lParam);
    }

    if (nMsg == WM_DEVICECHANGE) {
        // Should start with DEV_BROADCAST_HDR and validate.
        DEV_BROADCAST_DEVICEINTERFACE* hdr;
        hdr = (DEV_BROADCAST_DEVICEINTERFACE*) lParam;

        // Schedule a device list rescan.
        freespace_private_requestDeviceRescan();

        // Only handle all devices arrived or all devices removed.
        if ((LOWORD(wParam) != DBT_DEVICEARRIVAL) &&
            (LOWORD(wParam) != DBT_DEVICEREMOVECOMPLETE)) {
            DEBUG_WPRINTF(L"WM_DEVICECHANGE => %d\n", LOWORD(wParam));
            return TRUE;
        }

        /*
         * NOTE: Device scan is only performed once changes have stabilized.
         * The scan routine must be able to handled a device being removed
         * and reinserted without scan calls between events.
         */

        if (hdr->dbcc_devicetype != DBT_DEVTYP_DEVICEINTERFACE) {
            return TRUE;
        }

        if (LOWORD(wParam) == DBT_DEVICEARRIVAL) {
            DEBUG_WPRINTF(L"DBT_DEVICEARRIVAL => %s\n", hdr->dbcc_name);
        } else if (LOWORD(wParam) == DBT_DEVICEREMOVECOMPLETE) {
            DEBUG_WPRINTF(L"DBT_DEVICEREMOVECOMPLETE => %s\n", hdr->dbcc_name);
        } else {
            DEBUG_WPRINTF(L"discoveryCallback on unexpected change (%d) => %s\n", 
                LOWORD(wParam), hdr->dbcc_name);
        }

        return TRUE;
    }

    return DefWindowProc(hwnd,	nMsg, wParam, lParam);
}