#include "file_utils.h"

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

#include "SDL3/SDL_log.h"
#include "commons.h"
#include "errors_common.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb/stb_image.h"

#ifdef __linux__
#include <limits.h>
#include <unistd.h>
#endif /* ifndef __linux */

static rse_err_t get_curr_path(char* path)
{
    char* last_sep = NULL;
    char* tmp_path = NULL;
    size_t path_length = 0U;
    ssize_t binary_path_length = 0U;
    size_t directory_path_length = 0U;

    if (NULL == path) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to get path. Provided pointer is NULL");

        return RSE_ERROR_NULL_POINTER;
    }

    path_length = strlen(path);
    if (path_length > MAX_PATH_LENGTH) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Max path length cannot be 0");

        return RSE_ERROR_INVALID_PARAM;
    }

    rse_memset(path, '\0', path_length);
    rse_malloc(tmp_path, MAX_PATH_LENGTH);

#ifdef __linux__
    binary_path_length = readlink("/proc/self/exe", tmp_path, MAX_PATH_LENGTH - 1);
    if (binary_path_length == -1) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to read current binary path");

        return RSE_ERROR_NULL_POINTER;
    }

    tmp_path[binary_path_length] = '\0';
#endif /* ifdef __linux__ */

    last_sep = strrchr(tmp_path, '/') + 1;

    if (NULL == last_sep) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to get last separator in path");

        return RSE_ERROR_INTERNAL_ERROR;
    }

    directory_path_length = last_sep - tmp_path;

    if (directory_path_length > MAX_PATH_LENGTH) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Provided path buffer is too small");

        return RSE_ERROR_INTERNAL_ERROR;
    }

    strncpy(path, tmp_path, directory_path_length);

    free(tmp_path);

    return RSE_ERROR_NO_ERROR;
}

rse_err_t file_append_full_path(char* file_path, size_t max_buffer_size)
{
    rse_err_t ret = RSE_ERROR_NO_ERROR;
    size_t curr_path_length = 0U;
    size_t file_path_length = 0U;
    char curr_path[MAX_PATH_LENGTH] = {0};
    char temp_path[MAX_PATH_LENGTH] = {0};

    file_path_length = strlen(file_path);

    if (file_path_length > max_buffer_size) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Provided max_buffer_size is smaller than the file path itself");

        return RSE_ERROR_INVALID_PARAM;
    }

    if ((ret = get_curr_path(curr_path)) != RSE_ERROR_NO_ERROR) {
        return ret;
    }

    curr_path_length = strlen(curr_path);

    if ((curr_path_length + file_path_length) > max_buffer_size) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
                        "Provided max_buffer_size is to small to hold concatenated file path. %ld vs %ld",
                        (curr_path_length + file_path_length),
                        max_buffer_size);
    }

    strncpy(temp_path, file_path, MAX_PATH_LENGTH);

    memset(file_path, 0, max_buffer_size);

    strncat(file_path, curr_path, max_buffer_size);
    strncat(file_path, temp_path, max_buffer_size);

    return ret;
}

rse_err_t file_load_pixels(const char* file_name, unsigned char* buffer, size_t* buffer_size, int* width, int* height)
{
    int channels = 0;
    stbi_uc* pixel_buffer = NULL;
    size_t image_buffer_size = 0U;
    rse_err_t ret = RSE_ERROR_NO_ERROR;
    char real_path[MAX_PATH_LENGTH] = {0};

    if (buffer_size == NULL) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Provided NULL buffer_size");

        return RSE_ERROR_INVALID_PARAM;
    }

    if (width == NULL) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Provided NULL width");

        return RSE_ERROR_INVALID_PARAM;
    }

    if (height == NULL) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Provided NULL height");

        return RSE_ERROR_INVALID_PARAM;
    }

    strncpy(real_path, file_name, MAX_PATH_LENGTH);
    if ((ret = file_append_full_path(real_path, MAX_PATH_LENGTH)) != RSE_ERROR_NO_ERROR) {
        return ret;
    }

    pixel_buffer = stbi_load(real_path, width, height, &channels, STBI_rgb_alpha);

    if (pixel_buffer == NULL) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to load texture from file: %s", file_name);
        return RSE_ERROR_INTERNAL_ERROR;
    }

    image_buffer_size = *width * *height * 4; // TODO: "4" is very hardcoded value. Change this

    if ((buffer != NULL) && (image_buffer_size > *buffer_size)) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
                        "Provided buffer is to small to hold pixel data. %ld vs %ld",
                        *buffer_size,
                        image_buffer_size);
        ret = RSE_ERROR_INTERNAL_ERROR;

        goto image_free;
    }

    *buffer_size = image_buffer_size;

    if (buffer != NULL) {
        memset(buffer, 0, *buffer_size);
        memcpy(buffer, pixel_buffer, *buffer_size);
    }

image_free:
    stbi_image_free(pixel_buffer);

    return ret;
}

rse_err_t file_read(const char* file_name, size_t* bytes_count, char* buffer)
{
    rse_err_t ret = RSE_ERROR_NO_ERROR;
    FILE* file = NULL;
    char real_path[MAX_PATH_LENGTH] = {0};

    strncpy(real_path, file_name, MAX_PATH_LENGTH);
    if ((ret = file_append_full_path(real_path, MAX_PATH_LENGTH)) != RSE_ERROR_NO_ERROR) {
        return ret;
    }

    file = fopen(real_path, "rb");

    if (file == NULL) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Failed to open file for read: %s", real_path);
        ret = RSE_ERROR_INTERNAL_ERROR;

        goto file_close;
    }

    fseek(file, 0, SEEK_END);
    *bytes_count = ftell(file);
    fseek(file, 0, SEEK_SET);

    if (buffer == NULL) {
        goto file_close;
    }

    fread(buffer, *bytes_count, 1, file);
    if (ferror(file) != 0) {
        perror("Error reading file");

        ret = RSE_ERROR_INTERNAL_ERROR;
    }

file_close:
    fclose(file);

    return ret;
}

rse_err_t file_write(const char* file_name, size_t bytes_count, char* buffer)
{
    FILE* file = NULL;
    char real_path[MAX_PATH_LENGTH] = {0};
    rse_err_t ret = RSE_ERROR_NO_ERROR;

    strncpy(real_path, file_name, MAX_PATH_LENGTH);
    if ((ret = file_append_full_path(real_path, MAX_PATH_LENGTH)) != RSE_ERROR_NO_ERROR) {
        return ret;
    }

    file = fopen(real_path, "wb");

    if (file == NULL) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to open file for write: %s", file_name);
        return RSE_ERROR_INTERNAL_ERROR;
    }

    fwrite(buffer, bytes_count, 1, file);
    fclose(file);

    return RSE_ERROR_NO_ERROR;
}
