/*
 * This file is part of DeskHop (https://github.com/hrvach/deskhop).
 * Copyright (c) 2024 Hrvoje Cavrak
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "main.h"

void task_scheduler(device_t *state, task_t *task) {
    uint64_t current_time = time_us_64();

    if (current_time < task->next_run)
        return;

    task->next_run = current_time + task->frequency;
    task->exec(state);
}

/**================================================== *
 * ==============  Watchdog Functions  ============== *
 * ================================================== */

void kick_watchdog_task(device_t *state) {
    /* Read the timer AFTER duplicating the core1 timestamp,
       so it doesn't get updated in the meantime. */
    uint64_t core1_last_loop_pass = state->core1_last_loop_pass;
    uint64_t current_time         = time_us_64();

    /* If a reboot is requested, we'll stop updating watchdog */
    if (state->reboot_requested)
        return;

    /* If core1 stops updating the timestamp, we'll stop kicking the watchog and reboot */
    if (current_time - core1_last_loop_pass < CORE1_HANG_TIMEOUT_US)
        watchdog_update();
}

/**================================================== *
 * ===============  USB Device / Host  ============== *
 * ================================================== */

void usb_device_task(device_t *state) {
    tud_task();
}

void usb_host_task(device_t *state) {
    if (tuh_inited())
        tuh_task();
}

// mouse_report_t *screensaver_pong(device_t *state) {
//     static mouse_report_t report = {0};
//     static int dx = 20, dy = 25;

//     /* Check if we are bouncing off the walls and reverse direction in that case. */
//     if (report.x + dx < MIN_SCREEN_COORD || report.x + dx > MAX_SCREEN_COORD)
//         dx = -dx;

//     if (report.y + dy < MIN_SCREEN_COORD || report.y + dy > MAX_SCREEN_COORD)
//         dy = -dy;

//     report.x += dx;
//     report.y += dy;

//     return &report;
// }

// mouse_report_t *screensaver_jitter(device_t *state) {
//     const int16_t jitter_distance = 2;
//     static mouse_report_t report = {
//         .y = jitter_distance,
//         .mode = RELATIVE,
//     };
//     report.y = -report.y;

//     return &report;
// }

// /* Have something fun and entertaining when idle. */
// void screensaver_task(device_t *state) {
//     const uint32_t delays[] = {
//         0,        /* DISABLED, unused index 0 */
//         5000,     /* PONG, move mouse every 5 ms for a high framerate */
//         10000000, /* JITTER, once every 10 sec is more than enough */
//     };
//     static int last_pointer_move = 0;
//     screensaver_t *screensaver = &state->config.output[BOARD_ROLE].screensaver;
//     uint64_t inactivity_period = time_us_64() - state->last_activity[BOARD_ROLE];

//     /* If we're not enabled, nothing to do here. */
//     if (screensaver->mode == DISABLED)
//         return;

//     /* System is still not idle for long enough to activate or screensaver mode is not supported */
//     if (inactivity_period < screensaver->idle_time_us || screensaver->mode > MAX_SS_VAL)
//         return;

//     /* We exceeded the maximum permitted screensaver runtime */
//     if (screensaver->max_time_us
//         && inactivity_period > (screensaver->max_time_us + screensaver->idle_time_us))
//         return;

//     /* If we're the selected output and we can only run on inactive output, nothing to do here. */
//     if (screensaver->only_if_inactive && CURRENT_BOARD_IS_ACTIVE_OUTPUT)
//         return;

//     /* We're active! Now check if it's time to move the cursor yet. */
//     if (time_us_32() - last_pointer_move < delays[screensaver->mode])
//         return;

//     mouse_report_t *report;
//     switch (screensaver->mode) {
//         case PONG:
//             report = screensaver_pong(state);
//             break;

//         case JITTER:
//             report = screensaver_jitter(state);
//             break;

//         default:
//             return;
//     }

//     /* Move mouse pointer */
//     queue_mouse_report(report, state);

//     /* Update timer of the last pointer move */
//     last_pointer_move = time_us_32();
// }

/* Periodically emit heartbeat packets */
void heartbeat_output_task(device_t *state) {
    /* If firmware upgrade is in progress, don't touch flash_cs */
    if (state->fw.upgrade_in_progress)
        return;

    if (state->config_mode_active) {
        /* Leave config mode if timeout expired and user didn't click exit */
        if (time_us_64() > state->config_mode_timer)
            reboot();

        /* Keep notifying the user we're still in config mode */
        blink_led(state);
    }

#ifdef DH_DEBUG
    /* Holding the button invokes bootsel firmware upgrade */
    if (is_bootsel_pressed())
        reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
#endif

    uart_packet_t packet = {
        .type = HEARTBEAT_MSG,
        .data16 = {
            [0] = state->_running_fw.version,
            [2] = state->active_output,
        },
    };

    queue_try_add(&global_state.uart_tx_queue, &packet);
}


/* Process other outgoing hid report messages. */
void process_hid_queue_task(device_t *state) {
    hid_generic_pkt_t packet;

    if (!queue_try_peek(&state->hid_queue_out, &packet))
        return;

    if (!tud_hid_n_ready(packet.instance))
        return;

    /* ... try sending it to the host, if it's successful */
    bool succeeded = tud_hid_n_report(packet.instance, packet.report_id, packet.data, packet.len);

    /* ... then we can remove it from the queue. Race conditions shouldn't happen [tm] */
    if (succeeded)
	queue_try_remove(&state->hid_queue_out, &packet);
}

// /* Task that handles copying firmware from the other device to ours */
// void firmware_upgrade_task(device_t *state) {
//     if (!state->fw.upgrade_in_progress || !state->fw.byte_done)
//         return;

//     if (queue_is_full(&state->uart_tx_queue))
//         return;

//     /* End condition, when reached the process is completed. */
//     if (state->fw.address > STAGING_IMAGE_SIZE) {
//         state->fw.upgrade_in_progress = 0;
//         state->fw.checksum = ~state->fw.checksum;

//         /* Checksum mismatch, we wipe the stage 2 bootloader and rely on ROM recovery */
//         if(calculate_firmware_crc32() != state->fw.checksum) {
//             flash_range_erase((uint32_t)ADDR_FW_RUNNING - XIP_BASE, FLASH_SECTOR_SIZE);
//             reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
//         }

//         else {
//             state->_running_fw = _firmware_metadata;
//             global_state.reboot_requested = true;
//         }
//     }

//     /* If we're on the last element of the current page, page is done - write it. */
//     if (TU_U32_BYTE0(state->fw.address) == 0x00) {

//         uint32_t page_start_addr = (state->fw.address - 1) & 0xFFFFFF00;
//         write_flash_page((uint32_t)ADDR_FW_RUNNING + page_start_addr - XIP_BASE, state->page_buffer);
//     }

//     request_byte(state, state->fw.address);
// }

void packet_receiver_task(device_t *state) {
    uint32_t current_pointer
        = (uint32_t)DMA_RX_BUFFER_SIZE - dma_channel_hw_addr(state->dma_rx_channel)->transfer_count;
    uint32_t delta = get_ptr_delta(current_pointer, state);

    /* If we don't have enough characters for a packet, skip loop and return immediately */
    while (delta >= RAW_PACKET_LENGTH) {
        if (is_start_of_packet(state)) {
            fetch_packet(state);
            process_packet(&state->in_packet, state);
            return;
        }

        /* No packet found, advance to next position and decrement delta */
        state->dma_ptr = NEXT_RING_IDX(state->dma_ptr);
        delta--;
    }
}
