/***************************************************************************
 *                                                                         *
 *   libgig - C++ cross-platform Gigasampler format file access library    *
 *                                                                         *
 *   Copyright (C) 2003-2026 by Christian Schoenebeck                      *
 *                              <cuse@users.sourceforge.net>               *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This library is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this library; if not, write to the Free Software           *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston,                 *
 *   MA  02111-1307  USA                                                   *
 ***************************************************************************/

#ifndef __LIBGIG_HELPER_H__
#define __LIBGIG_HELPER_H__

#include <string.h>
#include <string>
#include <sstream>
#include <algorithm>
#include <assert.h>
#include <locale>
#include <limits>

#if defined(WIN32) && !HAVE_CONFIG_H && !defined(_MSC_VER)
# include "../win32/libgig_private.h" // like config.h, automatically generated by Dev-C++
# define PACKAGE "libgig"
# define VERSION VER_STRING // VER_STRING defined in libgig_private.h
#endif // WIN32

#if (HAVE_CONFIG_H /*&& !HAVE_VASPRINTF*/ && defined(WIN32)) || defined(_MSC_VER)
# include <stdarg.h>
int vasprintf(char** ret, const char* format, va_list arg);
#endif

#include "RIFF.h"

// *************** Helper Functions **************
// *

// generalized C-locale guaranteed T to string conversion
template<typename T> inline
std::string ToString(const T& value) {
    //TODO: replace by locale-independent, maybe faster std::to_chars() [C++17]
    std::stringstream ss;
    ss.imbue(std::locale::classic()); // a.k.a. "C" locale
    ss << value;
    return ss.str();
}

// C-locale guaranteed double to string conversion
template<> inline
std::string ToString(const double& value) {
    //TODO: replace by locale-independent, maybe faster std::to_chars() [C++17]
    std::stringstream ss;
    ss.imbue(std::locale::classic()); // a.k.a. "C" locale
    // stringstream has lower precision by default compared to std::to_string()
    ss.precision(std::numeric_limits<double>::max_digits10);
    ss << value;
    return ss.str();
}

// generalized, C-locale guaranteed T_arg (some string type) to T_res conversion
template<typename T_res, typename T_arg> inline
T_res strTo(const T_arg& arg) {
    //TODO: replace by locale-independent, maybe faster std::from_chars() [C++17]
    T_res res;
    std::istringstream is(arg);
    is.imbue(std::locale::classic()); // a.k.a. "C" locale
    is >> res;
    return res;
}

// Behaves as printf() just that it returns it as string instead of writing to stdout.
static inline std::string strPrint(const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    char* buf = NULL;
    const int n = vasprintf(&buf, fmt, args);
    assert(n >= 0);
    std::string res = (buf && n > 0) ? buf : "";
    if (buf) free(buf);
    va_end(args);
    return res;
}

static inline std::string toLowerCase(std::string s) {
    std::transform(s.begin(), s.end(), s.begin(), ::tolower);
    return s;
}

static inline long Min(long A, long B) {
    return (A > B) ? B : A;
}

static inline long Abs(long val) {
    return (val > 0) ? val : -val;
}

static inline void swapBytes_16(void* Word) {
    uint8_t byteCache = *((uint8_t*) Word);
    *((uint8_t*) Word)     = *((uint8_t*) Word + 1);
    *((uint8_t*) Word + 1) = byteCache;
}

static inline void swapBytes_32(void* Word) {
    uint8_t byteCache = *((uint8_t*) Word);
    *((uint8_t*) Word)     = *((uint8_t*) Word + 3);
    *((uint8_t*) Word + 3) = byteCache;
    byteCache = *((uint8_t*) Word + 1);
    *((uint8_t*) Word + 1) = *((uint8_t*) Word + 2);
    *((uint8_t*) Word + 2) = byteCache;
}

static inline void swapBytes_64(void* Word) {
    uint8_t byteCache = ((uint8_t*)Word)[0];
    ((uint8_t*)Word)[0] = ((uint8_t*)Word)[7];
    ((uint8_t*)Word)[7] = byteCache;
    byteCache = ((uint8_t*)Word)[1];
    ((uint8_t*)Word)[1] = ((uint8_t*)Word)[6];
    ((uint8_t*)Word)[6] = byteCache;
    byteCache = ((uint8_t*)Word)[2];
    ((uint8_t*)Word)[2] = ((uint8_t*)Word)[5];
    ((uint8_t*)Word)[5] = byteCache;
    byteCache = ((uint8_t*)Word)[3];
    ((uint8_t*)Word)[3] = ((uint8_t*)Word)[4];
    ((uint8_t*)Word)[4] = byteCache;
}

static inline void swapBytes(void* Word, uint64_t WordSize) {
    uint8_t byteCache;
    uint64_t lo = 0, hi = WordSize - 1;
    for (; lo < hi; hi--, lo++) {
        byteCache = *((uint8_t*) Word + lo);
        *((uint8_t*) Word + lo) = *((uint8_t*) Word + hi);
        *((uint8_t*) Word + hi) = byteCache;
    }
}

/**
 * Stores a 16 bit integer in memory using little-endian format.
 *
 * @param pData - memory pointer
 * @param data  - integer to be stored
 */
static inline void store16(uint8_t* pData, uint16_t data) {
    pData[0] = data;
    pData[1] = data >> 8;
}

/**
 * Stores a 32 bit integer in memory using little-endian format.
 *
 * @param pData - memory pointer
 * @param data  - integer to be stored
 */
static inline void store32(uint8_t* pData, uint32_t data) {
    pData[0] = data;
    pData[1] = data >> 8;
    pData[2] = data >> 16;
    pData[3] = data >> 24;
}

/**
 * Stores a 64 bit integer in memory using little-endian format.
 *
 * @param pData - memory pointer
 * @param data  - integer to be stored
 */
static inline void store64(uint8_t* pData, uint64_t data) {
    pData[0] = data;
    pData[1] = data >> 8;
    pData[2] = data >> 16;
    pData[3] = data >> 24;
    pData[4] = data >> 32;
    pData[5] = data >> 40;
    pData[6] = data >> 48;
    pData[7] = data >> 56;
}

/**
 * Loads a 16 bit integer in memory using little-endian format.
 *
 * @param pData - memory pointer
 * @returns 16 bit data word
 */
static inline uint16_t load16(uint8_t* pData) {
    return uint16_t(pData[0]) |
           uint16_t(pData[1]) << 8;
}

/**
 * Loads a 32 bit integer in memory using little-endian format.
 *
 * @param pData - memory pointer
 * @returns 32 bit data word
 */
static inline uint32_t load32(uint8_t* pData) {
    return uint32_t(pData[0])       |
           uint32_t(pData[1]) << 8  |
           uint32_t(pData[2]) << 16 |
           uint32_t(pData[3]) << 24;
}

/**
 * Swaps the order of the data words in the given memory area
 * with a granularity given by \a WordSize.
 *
 * @param pData    - pointer to the memory area to be swapped
 * @param AreaSize - size of the memory area to be swapped (in bytes)
 * @param WordSize - size of the data words (in bytes)
 */
static inline
void SwapMemoryArea(void* pData, unsigned long AreaSize, uint WordSize) {
    if (!AreaSize) return; // AreaSize==0 would cause a segfault here
    switch (WordSize) { // TODO: unefficient
        case 1: {
            uint8_t* pDst = (uint8_t*) pData;
            uint8_t  cache;
            unsigned long lo = 0, hi = AreaSize - 1;
            for (; lo < hi; hi--, lo++) {
                cache    = pDst[lo];
                pDst[lo] = pDst[hi];
                pDst[hi] = cache;
            }
            break;
        }
        case 2: {
            uint16_t* pDst = (uint16_t*) pData;
            uint16_t  cache;
            unsigned long lo = 0, hi = (AreaSize >> 1) - 1;
            for (; lo < hi; hi--, lo++) {
                cache    = pDst[lo];
                pDst[lo] = pDst[hi];
                pDst[hi] = cache;
            }
            break;
        }
        case 4: {
            uint32_t* pDst = (uint32_t*) pData;
            uint32_t  cache;
            unsigned long lo = 0, hi = (AreaSize >> 2) - 1;
            for (; lo < hi; hi--, lo++) {
                cache    = pDst[lo];
                pDst[lo] = pDst[hi];
                pDst[hi] = cache;
            }
            break;
        }
        default: {
            uint8_t* pCache = new uint8_t[WordSize]; // TODO: unefficient
            unsigned long lo = 0, hi = AreaSize - WordSize;
            for (; lo < hi; hi -= WordSize, lo += WordSize) {
                memcpy(pCache, (uint8_t*) pData + lo, WordSize);
                memcpy((uint8_t*) pData + lo, (uint8_t*) pData + hi, WordSize);
                memcpy((uint8_t*) pData + hi, pCache, WordSize);
            }
            if (pCache) delete[] pCache;
            break;
        }
    }
}

/** @brief Load given info field (string).
 *
 * Load info field string from given info chunk (\a ck) and save value to \a s.
 */
static inline void LoadString(RIFF::Chunk* ck, std::string& s) {
    if (ck) {
        const char* str = (char*)ck->LoadChunkData();
        if (!str) {
            ck->ReleaseChunkData();
            s = "";
            return;
        }
        int size = (int) ck->GetSize();
        int len;
        for (len = 0 ; len < size ; len++)
            if (str[len] == '\0') break;
        s.assign(str, len);
        ck->ReleaseChunkData();
    }
}

/** @brief Apply given INFO field to the respective chunk.
 *
 * Apply given info value string to given info chunk, which is a
 * subchunk of INFO list chunk \a lstINFO. If the given chunk already
 * exists, value \a s will be applied. Otherwise if it doesn't exist yet
 * and either \a s or \a sDefault is not an empty string, such a chunk
 * will be created and either \a s or \a sDefault will be applied
 * (depending on which one is not an empty string, if both are not an
 * empty string \a s will be preferred).
 *
 * @param ChunkID  - 32 bit RIFF chunk ID of INFO subchunk (only used in case \a ck is NULL)
 * @param ck       - INFO (sub)chunk where string should be stored to
 * @param lstINFO  - parent (INFO) RIFF list chunk
 * @param s        - current value of info field
 * @param sDefault - default value
 * @param bUseFixedLengthStrings - should a specific string size be forced in the chunk?
 * @param size     - wanted size of the INFO chunk. This is ignored if bUseFixedLengthStrings is false.
 */
static inline
void SaveString(uint32_t ChunkID, RIFF::Chunk* ck, RIFF::List* lstINFO,
                const std::string& s, const std::string& sDefault,
                bool bUseFixedLengthStrings, int size)
{
    if (ck) { // if chunk exists already, use 's' as value
        if (!bUseFixedLengthStrings) size = (int) s.size() + 1;
        ck->Resize(size);
        char* pData = (char*) ck->LoadChunkData();
        strncpy(pData, s.c_str(), size);
    } else if (s != "" || sDefault != "" || bUseFixedLengthStrings) { // create chunk
        const std::string& sToSave = (s != "") ? s : sDefault;
        if (!bUseFixedLengthStrings) size = (int) sToSave.size() + 1;
        ck = lstINFO->AddSubChunk(ChunkID, size);
        char* pData = (char*) ck->LoadChunkData();
        strncpy(pData, sToSave.c_str(), size);
    }
}

// private helper function to convert progress of a subprocess into the global progress
static inline
void __notify_progress(RIFF::progress_t* pProgress, float subprogress) {
    if (pProgress && pProgress->callback) {
        const float totalrange    = pProgress->__range_max - pProgress->__range_min;
        const float totalprogress = pProgress->__range_min + subprogress * totalrange;
        pProgress->factor         = totalprogress;
        pProgress->callback(pProgress); // now actually notify about the progress
    }
}

// private helper function to divide a progress into subprogresses
static inline
void __divide_progress(const RIFF::progress_t* pParentProgress,
                       RIFF::progress_t* pSubProgress,
                       float totalTasks, float currentTask)
{
    if (pParentProgress && pParentProgress->callback) {
        const float totalrange    = pParentProgress->__range_max - pParentProgress->__range_min;
        pSubProgress->callback    = pParentProgress->callback;
        pSubProgress->custom      = pParentProgress->custom;
        pSubProgress->activity    = pParentProgress->activity;
        pSubProgress->__range_min = pParentProgress->__range_min + totalrange * currentTask / totalTasks;
        pSubProgress->__range_max = pSubProgress->__range_min + totalrange / totalTasks;
        pSubProgress->__private   = pParentProgress->__private;
    }
}

// private helper function to divide a progress into subprogresses
static inline
void __divide_progress(const RIFF::progress_t* pParentProgress,
                       RIFF::progress_t* pSubProgress,
                       float total, float lo, float hi)
{
    if (pParentProgress && pParentProgress->callback) {
        const float totalrange    = pParentProgress->__range_max - pParentProgress->__range_min;
        pSubProgress->callback    = pParentProgress->callback;
        pSubProgress->custom      = pParentProgress->custom;
        pSubProgress->activity    = pParentProgress->activity;
        pSubProgress->__range_min = pParentProgress->__range_min + totalrange * (lo / total);
        pSubProgress->__range_max = pSubProgress->__range_min + totalrange * ((hi-lo) / total);
        pSubProgress->__private   = pParentProgress->__private;
    }
}


/*****************************************************************************
 * Any problems with any of the following helper functions?                  *
 *                                                                           *
 * Then please first have a look at their current TEST CASES at              *
 * src/testcases/HelperTest.cpp as basis for your modifications!             *
 *****************************************************************************/


/// Removes one or more consecutive occurences of @a needle from the end of @a haystack.
static inline
std::string strip2ndFromEndOf1st(const std::string haystack, char needle) {
    if (haystack.empty()) return haystack;
    if (*haystack.rbegin() != needle) return haystack;
    for (ssize_t i = haystack.length() - 1; i >= 0; --i)
        if (haystack[i] != needle)
            return haystack.substr(0, i+1);
    return "";
}

#ifndef NATIVE_PATH_SEPARATOR
# ifdef _WIN32
#  define NATIVE_PATH_SEPARATOR '\\'
# else
#  define NATIVE_PATH_SEPARATOR '/'
# endif
#endif

/**
 * Returns the owning path of the given path (its parent path). So for example
 * passing "/some/path" would return "/some".
 */
static inline std::string parentPath(const std::string path) {
    if (path.empty()) return path;
    std::string s = strip2ndFromEndOf1st(path, NATIVE_PATH_SEPARATOR);
    if (s.empty()) {
        s.push_back(NATIVE_PATH_SEPARATOR); // i.e. return "/"
        return s;
    }
    #if defined(_WIN32)
    if (s.length() == 2 && s[1] == ':')
        return s;
    #endif
    std::size_t pos = s.find_last_of(NATIVE_PATH_SEPARATOR);
    if (pos == std::string::npos) return "";
    if (pos == 0) {
        s = "";
        s.push_back(NATIVE_PATH_SEPARATOR); // i.e. return "/"
        return s;
    }
    return s.substr(0, pos);
}

/**
 * Returns the last (lowest) portion of the given path. So for example passing
 * "/some/path" would return "path".
 */
static inline std::string lastPathComponent(const std::string path) {
    #if defined(_WIN32)
    if (path.length() == 2 && path[1] == ':')
        return "";
    #endif
    std::size_t pos = path.find_last_of(NATIVE_PATH_SEPARATOR);
    return (pos == std::string::npos) ? path : path.substr(pos+1);
}

/**
 * Returns the given path with the type extension being stripped from its end.
 * So for example passing "/some/path.foo" would return "/some/path".
 */
static inline std::string pathWithoutExtension(const std::string path) {
    std::size_t posSep = path.find_last_of(NATIVE_PATH_SEPARATOR);
    std::size_t posBase = (posSep == std::string::npos) ? 0 : posSep+1;
    std::size_t posDot = path.find_last_of(".");
    return (posDot != std::string::npos && posDot > posBase)
           ? path.substr(0, posDot) : path;
}

/**
 * Returns the type extension of the given path. So for example passing
 * "/some/path.foo" would return "foo".
 */
static inline std::string extensionOfPath(const std::string path) {
    std::size_t posSep = path.find_last_of(NATIVE_PATH_SEPARATOR);
    std::size_t posBase = (posSep == std::string::npos) ? 0 : posSep+1;
    std::size_t posDot = path.find_last_of(".");
    return (posDot != std::string::npos && posDot > posBase)
           ? path.substr(posDot+1) : "";
}

/**
 * Combines the two given paths with each other. So for example passing
 * "/some/path" and "/another/one" would return "/some/path/another/one".
 */
static inline std::string concatPath(const std::string path1, const std::string path2) {
    return (!path1.empty() && *(path1.rbegin()) != NATIVE_PATH_SEPARATOR &&
            !path2.empty() && *(path2.begin())  != NATIVE_PATH_SEPARATOR)
        ? path1 + NATIVE_PATH_SEPARATOR + path2
        : path1 + path2;
}

/**
 * Returns a hex string representation of the binary data being passed.
 */
static inline std::string binToHexStr(const void* pData, size_t sz) {
    std::string s;
    for (size_t i = 0; i < sz; ++i) {
        s += strPrint("%02x", ((const char*)pData)[i]);
    }
    return s;
}

#endif // __LIBGIG_HELPER_H__
