#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "ff.h"
#include "private/lftpd_io.h"

#define DBG_TAG "[LFT_IO]"
#include "dbg_log.h"

/*
 * FatFS 在 FF_USE_LFN=0 时不接受 "/" 前缀路径。
 * 剥离前导 "/" 后用 FatFS 兼容路径，根目录用 "0:"（驱动号格式）替代。
 */
static const char *to_fatfs_path(const char *path)
{
    if (path == NULL || *path == '\0') {
        return "0:";
    }
    if (*path == '/') {
        path++;
        if (*path == '\0') {
            return "0:";
        }
    }
    return path;
}

static int s_file_open = 0;
static FIL s_ftp_file;

char *lftpd_io_canonicalize_path(const char *base, const char *name)
{
    if (base == NULL) {
        base = "";
    }
    if (name == NULL) {
        name = "";
    }

    char *path;
    if (name[0] == '/') {
        path = (char *)malloc(strlen(name) + 1);
        if (path == NULL) return NULL;
        strcpy(path, name);
    } else {
        size_t len = strlen(base) + 1 + strlen(name) + 1;
        path = (char *)malloc(len);
        if (path == NULL) return NULL;
        snprintf(path, len, "%s/%s", base, name);
    }

    char *abs_path = (char *)malloc(strlen(path) + 2);
    if (abs_path == NULL) {
        free(path);
        return NULL;
    }
    abs_path[0] = '\0';

    char *p = path;
    while (*p != '\0') {
        while (*p == '/') p++;
        if (*p == '\0') break;

        char *seg_start = p;
        while (*p != '\0' && *p != '/') p++;

        char saved = *p;
        *p = '\0';

        if (strcmp(seg_start, ".") == 0) {
            /* ignore */
        } else if (strcmp(seg_start, "..") == 0) {
            char *slash = strrchr(abs_path, '/');
            if (slash != NULL) {
                *slash = '\0';
            }
        } else {
            strcat(abs_path, "/");
            strcat(abs_path, seg_start);
        }

        *p = saved;
    }
    free(path);

    if (strlen(abs_path) == 0) {
        strcpy(abs_path, "/");
    }

    return abs_path;
}

int lftpd_io_open_read(const char *path)
{
    if (s_file_open) {
        DBG_ERROR("open_read '%s': already open", path);
        return -1;
    }
    const char *fpath = to_fatfs_path(path);
    FRESULT fr = f_open(&s_ftp_file, fpath, FA_READ);
    if (fr != FR_OK) {
        DBG_ERROR("open_read '%s': f_open err=%d", fpath, (int)fr);
        return -1;
    }
    s_file_open = 1;
    return 0;
}

int lftpd_io_open_write(const char *path)
{
    if (s_file_open) {
        DBG_ERROR("open_write '%s': already open", path);
        return -1;
    }
    const char *fpath = to_fatfs_path(path);
    FRESULT fr = f_open(&s_ftp_file, fpath, FA_WRITE | FA_CREATE_ALWAYS);
    if (fr != FR_OK) {
        DBG_ERROR("open_write '%s': f_open err=%d", fpath, (int)fr);
        return -1;
    }
    s_file_open = 1;
    return 0;
}

int lftpd_io_read(int handle, void *buf, int len)
{
    (void)handle;
    UINT br;
    if (f_read(&s_ftp_file, buf, (UINT)len, &br) != FR_OK) {
        return -1;
    }
    return (int)br;
}

int lftpd_io_write(int handle, const void *buf, int len)
{
    (void)handle;
    UINT bw;
    if (f_write(&s_ftp_file, buf, (UINT)len, &bw) != FR_OK) {
        return -1;
    }
    if ((int)bw != len) {
        return -1;
    }
    return (int)bw;
}

void lftpd_io_close(int handle)
{
    (void)handle;
    if (s_file_open) {
        f_close(&s_ftp_file);
        s_file_open = 0;
    }
}

int lftpd_io_stat(const char *path, uint32_t *p_size, int *p_is_dir)
{
    FILINFO fno;
    const char *fpath = to_fatfs_path(path);

    if (f_stat(fpath, &fno) != FR_OK) {
        return -1;
    }

    if (p_size != NULL) {
        *p_size = (uint32_t)fno.fsize;
    }
    if (p_is_dir != NULL) {
        *p_is_dir = (fno.fattrib & AM_DIR) ? 1 : 0;
    }

    return 0;
}

int lftpd_io_unlink(const char *path)
{
    const char *fpath = to_fatfs_path(path);
    if (f_unlink(fpath) == FR_OK) {
        return 0;
    }
    return -1;
}

void *lftpd_io_opendir(const char *path)
{
    DIR *dp = (DIR *)malloc(sizeof(DIR));
    if (dp == NULL) {
        return NULL;
    }
    const char *fpath = to_fatfs_path(path);
    if (f_opendir(dp, fpath) != FR_OK) {
        free(dp);
        return NULL;
    }
    return (void *)dp;
}

int lftpd_io_readdir(void *dp, char *name, int name_max, uint32_t *p_size, int *p_is_dir)
{
    FILINFO fno;
    FRESULT fr;

    while (1) {
        fr = f_readdir((DIR *)dp, &fno);
        if (fr != FR_OK || fno.fname[0] == 0) {
            return -1;
        }
        if (fno.fname[0] == '.') {
            continue;
        }
        break;
    }

    strncpy(name, fno.fname, (size_t)(name_max - 1));
    name[name_max - 1] = '\0';

    if (p_size != NULL) {
        *p_size = (uint32_t)fno.fsize;
    }
    if (p_is_dir != NULL) {
        *p_is_dir = (fno.fattrib & AM_DIR) ? 1 : 0;
    }

    return 0;
}

void lftpd_io_closedir(void *dp)
{
    if (dp != NULL) {
        f_closedir((DIR *)dp);
        free(dp);
    }
}
