/*
 * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "esp_err.h"
#include "esp_rom_sys.h"
#include "esp_vfs_cdcacm.h"
#include "esp_vfs_private.h"
#include "esp_private/usb_console.h"
#include "esp_vfs_console.h"
#include "esp_private/esp_vfs_console.h"
#include "sdkconfig.h"
#include "esp_private/startup_internal.h"

#define STRINGIFY(s) STRINGIFY2(s)
#define STRINGIFY2(s) #s

/**
 * This file is to concentrate all the vfs(UART, USB_SERIAL_JTAG, CDCACM) console into one single file.
 * Get the vfs information from their component (i.e. uart_vfs.c),
 * which can help us to output some string to two different ports(i.e both through uart and usb_serial_jtag).
 * Usually, we set a port as primary and another as secondary. For primary, it is used for all the features supported by each vfs implementation,
 * while the secondary is only used for output.
 */

typedef struct {
    int fd_primary;
    int fd_secondary;
} vfs_console_context_t;

#if CONFIG_VFS_SUPPORT_IO
// Primary register part.
#ifdef CONFIG_ESP_CONSOLE_UART
const static char *primary_path = "/dev/uart";
#elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
const static char *primary_path = "/dev/usbserjtag";
#elif CONFIG_ESP_CONSOLE_USB_CDC
const static char *primary_path = "/dev/cdcacm";
#endif

// Secondary register part.
#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG
const static char *secondary_path = "/dev/secondary";
static int secondary_vfs_index;
const static esp_vfs_t *secondary_vfs = NULL;
#endif // Secondary part

static int primary_vfs_index;
const static esp_vfs_t *primary_vfs = NULL;

static vfs_console_context_t vfs_console= {0};

int console_open(const char * path, int flags, int mode)
{
// Primary port open
#if CONFIG_ESP_CONSOLE_UART
    vfs_console.fd_primary = get_vfs_for_path(primary_path)->vfs.open("/"STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM), flags, mode);
#elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
    vfs_console.fd_primary = get_vfs_for_path(primary_path)->vfs.open("/", flags, mode);
#elif CONFIG_ESP_CONSOLE_USB_CDC
    vfs_console.fd_primary = esp_vfs_cdcacm_get_vfs()->open("/", flags, mode);
#endif

// Secondary port open
#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG
    vfs_console.fd_secondary = get_vfs_for_path(secondary_path)->vfs.open("/", flags, mode);
#endif
    return 0;
}

ssize_t console_write(int fd, const void *data, size_t size)
{
    // All function calls are to primary, except from write and close, which will be forwarded to both primary and secondary.
    get_vfs_for_index(primary_vfs_index)->vfs.write(vfs_console.fd_primary, data, size);
#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG
    get_vfs_for_index(secondary_vfs_index)->vfs.write(vfs_console.fd_secondary, data, size);
#endif
    return size;
}

int console_fstat(int fd, struct stat * st)
{
    return get_vfs_for_index(primary_vfs_index)->vfs.fstat(fd, st);
}

int console_close(int fd)
{
    // All function calls are to primary, except from write and close, which will be forwarded to both primary and secondary.
    get_vfs_for_index(primary_vfs_index)->vfs.close(vfs_console.fd_primary);
#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG
    get_vfs_for_index(secondary_vfs_index)->vfs.close(vfs_console.fd_secondary);
#endif
    return 0;
}

ssize_t console_read(int fd, void * dst, size_t size)
{
    return get_vfs_for_index(primary_vfs_index)->vfs.read(vfs_console.fd_primary, dst, size);
}

int console_fcntl(int fd, int cmd, int arg)
{
    return get_vfs_for_index(primary_vfs_index)->vfs.fcntl(vfs_console.fd_primary, cmd, arg);
}

int console_fsync(int fd)
{
    return get_vfs_for_index(primary_vfs_index)->vfs.fsync(vfs_console.fd_primary);
}

#ifdef CONFIG_VFS_SUPPORT_DIR
int console_access(const char *path, int amode)
{
    // currently only UART support DIR.
    return get_vfs_for_index(primary_vfs_index)->vfs.access("/"STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM), amode);
}
#endif // CONFIG_VFS_SUPPORT_DIR

#ifdef CONFIG_VFS_SUPPORT_SELECT
static esp_err_t console_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
        esp_vfs_select_sem_t select_sem, void **end_select_args)
{
    const vfs_entry_t* vfs_entry = get_vfs_for_index(primary_vfs_index);
    // start_select is not guaranteed be implemented even though CONFIG_VFS_SUPPORT_SELECT is enabled in sdkconfig
    if (vfs_entry && vfs_entry->vfs.start_select) {
        return vfs_entry->vfs.start_select(nfds, readfds, writefds, exceptfds, select_sem, end_select_args);
    }
    return ESP_ERR_NOT_SUPPORTED;
}

esp_err_t console_end_select(void *end_select_args)
{
    const vfs_entry_t* vfs_entry = get_vfs_for_index(primary_vfs_index);
    // end_select is not guaranteed be implemented even though CONFIG_VFS_SUPPORT_SELECT is enabled in sdkconfig
    if (vfs_entry && vfs_entry->vfs.end_select) {
        return vfs_entry->vfs.end_select(end_select_args);
    }
    return ESP_ERR_NOT_SUPPORTED;
}

#endif // CONFIG_VFS_SUPPORT_SELECT

#ifdef CONFIG_VFS_SUPPORT_TERMIOS

int console_tcsetattr(int fd, int optional_actions, const struct termios *p)
{
    return get_vfs_for_index(primary_vfs_index)->vfs.tcsetattr(vfs_console.fd_primary, optional_actions, p);
}

int console_tcgetattr(int fd, struct termios *p)
{
    return get_vfs_for_index(primary_vfs_index)->vfs.tcgetattr(vfs_console.fd_primary, p);
}

int console_tcdrain(int fd)
{
    return get_vfs_for_index(primary_vfs_index)->vfs.tcdrain(vfs_console.fd_primary);
}

int console_tcflush(int fd, int select)
{
    return get_vfs_for_index(primary_vfs_index)->vfs.tcflush(vfs_console.fd_primary, select);
}
#endif // CONFIG_VFS_SUPPORT_TERMIOS

static const esp_vfs_t vfs = {
    .flags = ESP_VFS_FLAG_DEFAULT,
    .write = &console_write,
    .open = &console_open,
    .fstat = &console_fstat,
    .close = &console_close,
    .read = &console_read,
    .fcntl = &console_fcntl,
    .fsync = &console_fsync,
#ifdef CONFIG_VFS_SUPPORT_DIR
    .access = &console_access,
#endif // CONFIG_VFS_SUPPORT_DIR
#ifdef CONFIG_VFS_SUPPORT_SELECT
    .start_select = &console_start_select,
    .end_select = &console_end_select,
#endif // CONFIG_VFS_SUPPORT_SELECT
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
    .tcsetattr = &console_tcsetattr,
    .tcgetattr = &console_tcgetattr,
    .tcdrain = &console_tcdrain,
    .tcflush = &console_tcflush,
#endif // CONFIG_VFS_SUPPORT_TERMIOS
};

static esp_err_t esp_vfs_dev_console_register(void)
{
    return esp_vfs_register(ESP_VFS_DEV_CONSOLE, &vfs, NULL);
}

esp_err_t esp_vfs_console_register(void)
{
    esp_err_t err = ESP_OK;
// Primary register part.
#if CONFIG_ESP_CONSOLE_UART || CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
    assert(primary_vfs);
#elif CONFIG_ESP_CONSOLE_USB_CDC
    primary_vfs = esp_vfs_cdcacm_get_vfs();
    err = esp_usb_console_init();
    if (err != ESP_OK) {
        return err;
    }
#endif
    err = esp_vfs_register_common(primary_path, strlen(primary_path), primary_vfs, NULL, &primary_vfs_index);
    if (err != ESP_OK) {
        return err;
    }

// Secondary register part.
#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG
    assert(secondary_vfs);
    err = esp_vfs_register_common(secondary_path, strlen(secondary_path), secondary_vfs, NULL, &secondary_vfs_index);
    if(err != ESP_OK) {
        return err;
    }
#endif
    err = esp_vfs_dev_console_register();
    return err;
}

void esp_vfs_set_primary_dev_vfs_def_struct(const esp_vfs_t *vfs)
{
    primary_vfs = vfs;
}

#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG
void esp_vfs_set_secondary_dev_vfs_def_struct(const esp_vfs_t *vfs)
{
    secondary_vfs = vfs;
}
#endif

ESP_SYSTEM_INIT_FN(init_vfs_console, CORE, BIT(0), 114)
{
    return esp_vfs_console_register();
}

#endif // CONFIG_VFS_SUPPORT_IO

void esp_vfs_include_console_register(void)
{
    // Linker hook function, exists to make the linker examine this file
}