#include "pch.h"
#include "log.h"
#include "asset.h"
#include "platform_api/network_tls_policy.h"

#ifdef __APPLE__
#include <Foundation/Foundation.h>
#include "objc_utils.h"
#endif

#ifdef __ANDROID__
#include <android/asset_manager.h>
#include <dirent.h>
void* Asset::m_android_asset_manager;
bool android_create_dir(const std::string& path);

namespace {

[[nodiscard]] AAsset* android_asset_handle(void* asset)
{
    return static_cast<AAsset*>(asset);
}

}

void Asset::set_android_asset_manager(void* asset_manager)
{
    m_android_asset_manager = asset_manager;
}
#endif

bool Asset::delete_file(const std::string& path)
{
    LOG("delete file: %s", path.c_str());
    std::remove(path.c_str());
    return true;
}

bool Asset::exist(std::string path)
{
    if (Asset::is_asset(path))
    {
        Asset asset;
        if (asset.open(path.c_str()))
        {
            asset.close();
            return true;
        }
    }
    else
    {
        FILE* fp = fopen(path.c_str(), "rb");
        if (!fp)
            return false;
        fclose(fp);
        return true;
    }
    return false; // useless return for the stupid xcode 
}

std::vector<std::string> Asset::list_files(std::string folder, const std::string& filter_regex)
{
    bool is_asset = Asset::is_asset(folder);
    std::vector<std::string> names;
#ifdef _WIN32
    WIN32_FIND_DATAA fd;
    HANDLE hFind = ::FindFirstFileA((folder + "\\*").c_str(), &fd);
    if (hFind != INVALID_HANDLE_VALUE) 
    {
        do 
        {
            // read all (real) files in current folder
            // , delete '!' read other 2 default folder . and ..
            if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) 
            {
                names.push_back(fd.cFileName);
            }
        } while (::FindNextFileA(hFind, &fd));
        ::FindClose(hFind);
    }
#elif __ANDROID__
    if (is_asset)
    {
        AAssetDir* dir = AAssetManager_openDir(static_cast<AAssetManager*>(Asset::m_android_asset_manager), folder.c_str());
        while (const char* name = AAssetDir_getNextFileName(dir))
        {
            //LOG("asset: %s", name);
            names.push_back(name);
        }
        AAssetDir_close(dir);
    }
    else
    {
        DIR *dp;
        struct dirent *ep;
        dp = opendir(folder.c_str());

        if (dp != NULL)
        {
            while ((ep = readdir(dp)))
                if (ep->d_type != DT_DIR)
                    names.push_back(ep->d_name);
            closedir(dp);
        }
        else
        LOG("Couldn't open the directory: %s", folder.c_str());
    }
#else
    std::string abs_path = folder;
    if (is_asset)
    {
        abs_path = absolute(folder);
    }

    DIR *dp;
    struct dirent *ep;
    dp = opendir(abs_path.c_str());

    if (dp != NULL)
    {
        while ((ep = readdir(dp)))
            if (ep->d_type != DT_DIR)
                names.push_back(ep->d_name);
        closedir(dp);
    }
    else
        LOG("Couldn't open the directory: %s", folder.c_str());
#endif

    if (!filter_regex.empty())
    {
        std::regex r(filter_regex);
        names.erase(std::remove_if(names.begin(), names.end(),
            [&r](const std::string& s) { return !std::regex_match(s, r); }), names.end());
    }

    return names;
}

std::string Asset::absolute(const std::string& path)
{
#ifdef __APPLE__
    NSString* bundle_path = [[NSBundle mainBundle] resourcePath];
    std::string base = [bundle_path cStringUsingEncoding:1];
    return base + "/" + path;
#else
    return path;
#endif
}

bool Asset::is_asset(const std::string & path)
{
    return path.substr(0, 5) == "data/";
}

bool Asset::create_dir(const std::string& path)
{
#if __WIN__
    return PathFileExistsA(path.c_str()) ? true : CreateDirectoryA(path.c_str(), NULL);
#elif __ANDROID__
    return android_create_dir(path);
#elif __IOS__ || __OSX__
    return apple_create_dir(path);
#else
    // TODO: implement for linux and web
    return false;
#endif
}

static size_t curl_data_handler_asset(void* contents, size_t size, size_t nmemb, void* userp)
{
    auto buffer = reinterpret_cast<std::vector<uint8_t>*>(userp);
    buffer->insert(buffer->end(), (char*)contents, (char*)contents + (size * nmemb));
    return size * nmemb;
}

#if WITH_CURL
static int progress_callback_asset_download(void* clientp, curl_off_t dltotal,
    curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
    Asset* ass = static_cast<Asset*>(clientp);
    if (ass->m_stop_async)
    {
        LOG("interrupt download of %s", ass->m_current_url.c_str());
        return 1;
    }
    float p = dltotal > 0 ? (float)dlnow / (float)dltotal : 0.f;
    bool cont = ass->on_progress(p);
    return cont ? 0 : 1;
}
#endif //CURL

bool Asset::open_url(const std::string& url, std::function<bool(float)> progress /*= nullptr*/)
{
#if WITH_CURL
    CURL* curl = curl_easy_init();
    if (curl)
    {
        close();
        LOG("download %s", url.c_str());
        m_current_url = url;
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &tmp_data);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler_asset);
        curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
        if (pp::platform::default_disables_network_tls_verification())
            curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        if (progress)
        {
            on_progress = progress;
            curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback_asset_download);
            curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this);
            curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
        }
        auto err = curl_easy_perform(curl);
        curl_easy_cleanup(curl);
        
        if (err == CURLE_OK)
        {
            if (m_data)
                delete m_data;
            m_data = new uint8_t[tmp_data.size()];
            std::copy(tmp_data.begin(), tmp_data.end(), m_data);
            m_len = tmp_data.size();
            tmp_data.clear();
            return true;
        }
    }
#endif // CURL
    return false;
}

std::future<bool>& Asset::open_url_async(const std::string& url, 
    std::function<bool(float)> progress /*= nullptr*/, std::function<void(bool)> complete /*= nullptr*/)
{
    close_remote();
    m_stop_async = false;
    remote_future = std::async([this, url, progress, complete] {
        bool ok = open_url(url, progress);
        if (complete)
            complete(ok);
        return ok;
    });
    return remote_future;
}

bool Asset::open(const char* path)
{
    //LOG("Asset::open %s", path);
    m_current_path = path;
    std::string file_path = path;
#ifdef __ANDROID__
    if (is_asset(path))
    {
        if (!(m_android_asset = AAssetManager_open(
            static_cast<AAssetManager*>(m_android_asset_manager),
            path,
            AASSET_MODE_RANDOM)))
        {
            LOG("AAssetManager_open failed %s", path);
            return false;
        }
    }
    else
    {
        if (!(m_fp = fopen(file_path.c_str(), "rb")))
        {
            LOG("asset open errno = %d, %s", errno, path);
            return false;
        }
        fseek(m_fp, 0, SEEK_END);
        m_len = (int)ftell(m_fp);
        fseek(m_fp, 0, SEEK_SET);
    }
#else
#ifdef __APPLE__
    if (is_asset(path))
    {
        NSString* bundle_path = [[NSBundle mainBundle] resourcePath];
        std::string base = [bundle_path cStringUsingEncoding:1];
        file_path = base + "/" + path;
    }
#endif
    //LOG("asset file: %s", file_path.c_str());
    if (!(m_fp = fopen(file_path.c_str(), "rb")))
    {
        LOG("asset open errno = %d, %s", errno, path);
        return false;
    }
    fseek(m_fp, 0, SEEK_END);
    m_len = (int)ftell(m_fp);
    fseek(m_fp, 0, SEEK_SET);
#endif
    return true;
}

glm::uint8_t* Asset::read_all()
{
#ifdef __ANDROID__
    if (!m_data)
    {
        if (is_asset(m_current_path))
        {
            m_len = (int)AAsset_getLength(android_asset_handle(m_android_asset));
            m_data = (uint8_t*)AAsset_getBuffer(android_asset_handle(m_android_asset));
        }
        else
        {
            m_data = new uint8_t[m_len];
            if (m_len != fread(m_data, 1, m_len, m_fp))
            {
                LOG("ASSET READ FAILED for %s", m_current_path.c_str());
                delete[] m_data;
                m_data = nullptr;
            }
        }
    }
    return m_data;
#else
    if (!m_data)
    {
        m_data = new uint8_t[m_len];
        if (m_len != fread(m_data, 1, m_len, m_fp))
        {
            LOG("ASSET READ FAILED for %s", m_current_path.c_str());
            delete[] m_data;
            m_data = nullptr;
        }
    }
    return m_data;
#endif
}

void Asset::close()
{
#ifdef __ANDROID__
    if (m_android_asset)
        AAsset_close(android_asset_handle(m_android_asset));
    m_android_asset = nullptr;
#else
    if (m_fp)
        fclose(m_fp);
    if (m_data)
        delete m_data;
    m_data = nullptr;
    m_fp = nullptr;
#endif
}

void Asset::close_remote()
{
    if (remote_future.valid())
    {
        m_stop_async = true;
        remote_future.get();
    }
}

Asset::~Asset()
{
    close_remote();
    close();
}
