// Copyright 2021 Jean Pierre Cimalando
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0
//

#pragma once
#include "ysfx.h"
#include <string>
#include <vector>
#include <mutex>
#include <cstdio>
#include <cstdint>
#include <clocale>
#if defined(__APPLE__)
#   include <xlocale.h>
#endif
#if !defined(_WIN32) && !defined(__FreeBSD__)
#   include <alloca.h>
#else
#   include <malloc.h>
#endif

#if defined(YSFX_NO_STANDARD_MUTEX)
#   include "WDL/mutex.h"
#endif

namespace ysfx {

YSFX_DEFINE_AUTO_PTR(FILE_u, FILE, fclose);

FILE *fopen_utf8(const char *path, const char *mode);
int64_t fseek_lfs(FILE *stream, int64_t off, int whence);
int64_t ftell_lfs(FILE *stream);

//------------------------------------------------------------------------------

#if !defined(_WIN32)
using c_locale_t = locale_t;
#else
using c_locale_t = _locale_t;
#endif

c_locale_t c_numeric_locale();

//------------------------------------------------------------------------------

#if !defined(_WIN32)
#   define ysfx_alloca(n) alloca((n))
#else
#   define ysfx_alloca(n) _malloca((n))
#endif

//------------------------------------------------------------------------------

#if !defined(YSFX_NO_STANDARD_MUTEX)
using mutex = std::mutex;
#else
class mutex
{
public:
    void lock() { m_mutex.Enter(); }
    void unlock() { m_mutex.Leave(); }

private:
    WDL_Mutex m_mutex;
};
#endif

//------------------------------------------------------------------------------

using string_list = std::vector<std::string>;

double c_atof(const char *text, c_locale_t loc);
double c_strtod(const char *text, char **endp, c_locale_t loc);
double dot_atof(const char *text);
double dot_strtod(const char *text, char **endp);
bool ascii_isspace(char c);
bool ascii_isalpha(char c);
char ascii_tolower(char c);
char ascii_toupper(char c);
int ascii_casecmp(const char *a, const char *b);
uint32_t latin1_toupper(uint32_t c);
uint32_t latin1_tolower(uint32_t c);
char *strdup_using_new(const char *src);
string_list split_strings_noempty(const char *input, bool(*pred)(char));
std::string trim(const char *input, bool(*pred)(char));

//------------------------------------------------------------------------------

void pack_u32le(uint32_t value, uint8_t data[4]);
void pack_f32le(float value, uint8_t data[4]);
uint32_t unpack_u32le(const uint8_t data[4]);
float unpack_f32le(const uint8_t data[4]);

//------------------------------------------------------------------------------

std::vector<uint8_t> decode_base64(const char *text, size_t len = ~(size_t)0);
std::string encode_base64(const uint8_t *data, size_t len);

//------------------------------------------------------------------------------

using file_uid = std::pair<uint64_t, uint64_t>;
bool get_file_uid(const char *path, file_uid &uid);
bool get_stream_file_uid(FILE *stream, file_uid &uid);
bool get_descriptor_file_uid(int fd, file_uid &uid);
#if defined(_WIN32)
bool get_handle_file_uid(void *handle, file_uid &uid);
#endif

//------------------------------------------------------------------------------

struct split_path_t {
    std::string drive;
    std::string dir;
    std::string file;
};

// check if the character is a path separator
bool is_path_separator(char ch);
// break down a path into individual components
split_path_t split_path(const char *path);
// get the file name part (all after the final '/' separator)
std::string path_file_name(const char *path);
// get the directory part (all up to the '/' separator, inclusive)
std::string path_directory(const char *path);
// add the final '/' separator if absent; if empty, does nothing
std::string path_ensure_final_separator(const char *path);
// compare the tail of the path with the suffix, case-insensitively
bool path_has_suffix(const char *path, const char *suffix);
// check whether the path is relative
bool path_is_relative(const char *path);

//------------------------------------------------------------------------------

// check whether a file exists on disk
bool exists(const char *path);
// list the elements of a directory; directories are distinguished with a final '/'
string_list list_directory(const char *path);
// visit the root and subdirectories in depth-first order
void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data);
// resolve a path which matches root/fragment, where fragment is case-insensitive (0=failed, 1=exact, 2=inexact)
int case_resolve(const char *root, const char *fragment, std::string &result);

//------------------------------------------------------------------------------

#if defined(_WIN32)
std::wstring widen(const std::string &u8str);
std::wstring widen(const char *u8data, size_t u8len = ~(size_t)0);
std::string narrow(const std::wstring &wstr);
std::string narrow(const wchar_t *wdata, size_t wlen = ~(size_t)0);
#endif

//------------------------------------------------------------------------------

template <class F>
class scope_guard {
public:
    explicit scope_guard(F &&f) : f(std::forward<F>(f)), a(true) {}
    scope_guard(scope_guard &&o) : f(std::move(o.f)), a(o.a) { o.a = false; }
    ~scope_guard() { if (a) f(); }
    void disarm() { a = false; }
private:
    F f;
    bool a;
    scope_guard(const scope_guard &) = delete;
    scope_guard &operator=(const scope_guard &) = delete;
};

template <class F> scope_guard<F> defer(F &&f)
{
    return scope_guard<F>(std::forward<F>(f));
}

} // namespace ysfx
