#include <platform/file.h>
#include <SDL.h>
#include <sys/stat.h>
#include <fstream>
#include <sstream>

#ifdef _WIN32
#include <direct.h>
#include <windows.h>
#define mkdir_impl(path, mode) _mkdir(path)
#else
#include <dirent.h>
#include <unistd.h>
#define mkdir_impl(path, mode) mkdir(path, mode)
#endif

namespace extra2d {

FileSvc& FileSvc::inst() {
    static FileSvc instance;
    return instance;
}

bool FileSvc::init() {
    if (isInited()) return true;
    
    writableDir_ = SDL_GetPrefPath("Extra2D", "Extra2D");
    if (writableDir_.empty()) {
        writableDir_ = "./";
    }
    
    state_ = SvcState::Inited;
    return true;
}

void FileSvc::shutdown() {
    state_ = SvcState::Shutdown;
}

bool FileSvc::exists(const std::string& path) const {
    struct stat st;
    return stat(path.c_str(), &st) == 0;
}

bool FileSvc::isDir(const std::string& path) const {
    struct stat st;
    if (stat(path.c_str(), &st) != 0) return false;
    return (st.st_mode & S_IFDIR) != 0;
}

FileData FileSvc::read(const std::string& path) const {
    FileData result;
    
    std::ifstream file(path, std::ios::binary | std::ios::ate);
    if (!file.is_open()) {
        result.error = "Cannot open file: " + path;
        return result;
    }
    
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);
    
    result.data.resize(static_cast<size_t>(size));
    if (!file.read(reinterpret_cast<char*>(result.data.data()), size)) {
        result.error = "Failed to read file: " + path;
        return result;
    }
    
    result.ok = true;
    return result;
}

std::string FileSvc::readString(const std::string& path) const {
    std::ifstream file(path);
    if (!file.is_open()) return "";
    
    std::stringstream buffer;
    buffer << file.rdbuf();
    return buffer.str();
}

bool FileSvc::write(const std::string& path, const void* data, size_t size) const {
    std::ofstream file(path, std::ios::binary);
    if (!file.is_open()) return false;
    
    file.write(static_cast<const char*>(data), static_cast<std::streamsize>(size));
    return file.good();
}

bool FileSvc::writeString(const std::string& path, const std::string& content) const {
    return write(path, content.data(), content.size());
}

bool FileSvc::append(const std::string& path, const void* data, size_t size) const {
    std::ofstream file(path, std::ios::binary | std::ios::app);
    if (!file.is_open()) return false;
    
    file.write(static_cast<const char*>(data), static_cast<std::streamsize>(size));
    return file.good();
}

bool FileSvc::remove(const std::string& path) const {
    return std::remove(path.c_str()) == 0;
}

bool FileSvc::mkdir(const std::string& path) const {
#ifdef _WIN32
    return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST;
#else
    return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST;
#endif
}

std::vector<FileInfo> FileSvc::listDir(const std::string& path) const {
    std::vector<FileInfo> result;
    
#ifdef _WIN32
    WIN32_FIND_DATAA findData;
    std::string searchPath = path + "\\*";
    HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData);
    
    if (hFind == INVALID_HANDLE_VALUE) return result;
    
    do {
        std::string name = findData.cFileName;
        if (name == "." || name == "..") continue;
        
        FileInfo info;
        info.name = name;
        info.path = join(path, name);
        info.isDir = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
        if (!info.isDir) {
            info.size = static_cast<int64>(findData.nFileSizeLow) | 
                       (static_cast<int64>(findData.nFileSizeHigh) << 32);
        }
        result.push_back(info);
    } while (FindNextFileA(hFind, &findData));
    
    FindClose(hFind);
#else
    DIR* dir = opendir(path.c_str());
    if (!dir) return result;
    
    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        std::string name = entry->d_name;
        if (name == "." || name == "..") continue;
        
        FileInfo info;
        info.name = name;
        info.path = join(path, name);
        info.isDir = isDir(info.path);
        if (!info.isDir) {
            info.size = fileSize(info.path);
        }
        result.push_back(info);
    }
    
    closedir(dir);
#endif
    
    return result;
}

int64 FileSvc::fileSize(const std::string& path) const {
    struct stat st;
    if (stat(path.c_str(), &st) != 0) return -1;
    return static_cast<int64>(st.st_size);
}

std::string FileSvc::ext(const std::string& path) const {
    size_t pos = path.find_last_of('.');
    if (pos == std::string::npos || pos == 0) return "";
    
    size_t lastSep = path.find_last_of("/\\");
    if (lastSep != std::string::npos && pos < lastSep) return "";
    
    return path.substr(pos + 1);
}

std::string FileSvc::fileName(const std::string& path) const {
    size_t pos = path.find_last_of("/\\");
    if (pos == std::string::npos) return path;
    return path.substr(pos + 1);
}

std::string FileSvc::dirName(const std::string& path) const {
    size_t pos = path.find_last_of("/\\");
    if (pos == std::string::npos) return ".";
    if (pos == 0) return "/";
    return path.substr(0, pos);
}

std::string FileSvc::join(const std::string& a, const std::string& b) const {
    if (a.empty()) return b;
    if (b.empty()) return a;
    
    char last = a.back();
    if (last == '/' || last == '\\') {
        return a + b;
    }
    return a + "/" + b;
}

std::string FileSvc::writableDir() const {
    return writableDir_;
}

std::string FileSvc::assetPath(const std::string& relPath) const {
    if (assetRoot_.empty()) return relPath;
    return join(assetRoot_, relPath);
}

} // namespace extra2d
