/*                                                         -*- c++ -*-
 * Copyright (C) 2007-2020 Andreas Persson
 * Copyright (C) 2007-2026 Christian Schoenebeck
 *
 * This program 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, or (at
 * your option) any later version.
 *
 * This program 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 program; see the file COPYING. If not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

#ifndef GIGEDIT_GLOBAL_H
#define GIGEDIT_GLOBAL_H

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <cstring>
#include <algorithm>

#ifdef GLIBMM_HEADER_FILE
# include GLIBMM_HEADER_FILE(glibmmconfig.h)
#else
# include <glibmmconfig.h>
#endif

#if !defined(WIN32)
# include <unistd.h>
# include <errno.h>
#endif

#include <sstream>
#include <iomanip>
#include <map>
#include <set>
#include <string>
#include <functional>
#ifdef SIGCPP_HEADER_FILE
# include SIGCPP_HEADER_FILE(signal.h)
#else
# include <sigc++/signal.h>
#endif

#ifdef LIBGIG_HEADER_FILE
# include LIBGIG_HEADER_FILE(gig.h)
# include LIBGIG_HEADER_FILE(Serialization.h)
#else
# include <gig.h>
# include <Serialization.h>
#endif

//FIXME: for some reason AC GETTEXT check fails on the Mac cross compiler?
#ifdef GETTEXT_HEADER_FILE
# include GETTEXT_HEADER_FILE(libintl.h)
#elif (HAVE_GETTEXT || defined(__APPLE__))
# include <libintl.h>
# define _(String) gettext(String)
#else
# define _(String) String
#endif

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

#define UNICODE_RIGHT_ARROW     Glib::ustring(1, gunichar(0x2192))
#define UNICODE_LEFT_ARROW      Glib::ustring(1, gunichar(0x2190))
#define UNICODE_SHIFT_KEY_SYMBOL    Glib::ustring("\xe2\x87\xa7")
#if defined(__APPLE__)
# define UNICODE_ALT_KEY_SYMBOL     Glib::ustring("\xe2\x8c\xa5")
#else
# define UNICODE_ALT_KEY_SYMBOL     Glib::ustring("Alt")
#endif
#define UNICODE_ERASE_KEY_SYMBOL    Glib::ustring("\xe2\x8c\xab")
#define UNICODE_CTRL_KEY_SYMBOL     Glib::ustring("Ctrl")
#define UNICODE_CMD_KEY_SYMBOL      Glib::ustring(1, gunichar(0x2318))
#if defined(__APPLE__)
# define UNICODE_PRIMARY_KEY_SYMBOL     UNICODE_CMD_KEY_SYMBOL
#else
# define UNICODE_PRIMARY_KEY_SYMBOL     UNICODE_CTRL_KEY_SYMBOL
#endif

// taken from gdk/gdkkeysyms.h
// (define on demand, to avoid unnecessary dev lib package build dependency)
#ifndef GDK_KEY_Control_L
# define GDK_KEY_Control_L 0xffe3
#endif
#ifndef GDK_KEY_Control_R
# define GDK_KEY_Control_R 0xffe4
#endif
#ifndef GDK_KEY_Left
# define GDK_KEY_Left 0xff51
#endif
#ifndef GDK_KEY_Right
# define GDK_KEY_Right 0xff53
#endif
#ifndef GDK_KEY_Up
# define GDK_KEY_Up 0xff52
#endif
#ifndef GDK_KEY_Down
# define GDK_KEY_Down 0xff54
#endif

#include <glibmm/convert.h>

#define GIG_STR_ENCODING "CP1252"

#ifndef NODISCARD
# if __cplusplus >= 201703L
#  define NODISCARD [[nodiscard]]
# elif (defined(__GNUC__) && __GNUC__ >= 3) || (defined(__clang__) && __clang_major__ >= 3)
#  define NODISCARD __attribute__((warn_unused_result))
# elif defined(_MSC_VER) && _MSC_VER >= 1500 /* >= Visual Studio 2008 v9.0 */
#  include <CodeAnalysis/SourceAnnotations.h>
#  define NODISCARD _Check_return_
# else
#  define NODISCARD
# endif
#endif

/** @brief Get all parameter values for passed dimension regions.
 *
 * This lambda function is called when hovering the mouse over a parameter
 * control in the editor. It returns all values of that particular sound
 * parameter for the passed list of dimension regions. As the intention is to
 * display the parameter values on screen, the parameter values are converted to
 * string objects.
 */
typedef std::function<
            std::set<std::string>( std::vector<gig::DimensionRegion*>& )
        > FnAltValues;

static inline NODISCARD
Glib::ustring gig_to_utf8(const gig::String& gig_string) {
    return Glib::convert_with_fallback(gig_string, "UTF-8", GIG_STR_ENCODING, "?");
}

static inline NODISCARD
gig::String gig_from_utf8(const Glib::ustring& utf8_string) {
    return Glib::convert_with_fallback(utf8_string, GIG_STR_ENCODING, "UTF-8", "?");
}

inline NODISCARD Glib::ustring ltrim(Glib::ustring s) {
    s.erase(
        s.begin(), std::find_if(
            s.begin(), s.end(), [](int c) { return !std::isspace(c); }
        )
    );
    return s;
}

inline NODISCARD Glib::ustring rtrim(Glib::ustring s) {
    s.erase(
        std::find_if(
            s.rbegin(), s.rend(), [](int c) { return !std::isspace(c); }
        ).base(), s.end()
    );
    return s;
}

inline NODISCARD Glib::ustring trim(Glib::ustring s) {
    return ltrim(rtrim(s));
}

template<class T> inline NODISCARD std::string ToString(T o) {
    std::stringstream ss;
    ss << o;
    return ss.str();
}

template<class T> inline NODISCARD std::string toHexStr(T value, int width) {
    std::ostringstream oss;
    oss << std::hex << std::setfill('0') << std::setw(width) << value;
    std::string foo = oss.str();
    return oss.str();
}

inline static NODISCARD
bool endsWith(const std::string& haystack, const std::string& needle, bool caseSensitive) {
    if (haystack.size() < needle.size()) return false;
    const std::string sub = haystack.substr(haystack.size() - needle.size(), needle.size());
    return (caseSensitive) ? (sub == needle) : (!strcasecmp(sub.c_str(), needle.c_str()));
}

inline NODISCARD int getIndexOf(gig::Instrument* instrument) {
    gig::File* file = (gig::File*) instrument->GetParent();
    for (size_t i = 0; gig::Instrument* instr = file->GetInstrument(i); ++i) {
        if (instr == instrument) return i;
    }
    return -1;
}

inline NODISCARD int getDimensionIndex(gig::dimension_t type, gig::Region* rgn) {
    for (uint i = 0; i < rgn->Dimensions; ++i)
        if (rgn->pDimensionDefinitions[i].dimension == type)
            return i;
    return -1;
}

inline NODISCARD int getDimensionRegionIndex(gig::DimensionRegion* dr) {
    if (!dr) return -1;
    gig::Region* rgn = (gig::Region*)dr->GetParent();
    for (uint i = 0; i < 256; ++i)
        if (rgn->pDimensionRegions[i] == dr)
            return i;
    return -1;
}

/// Find the number of bits required to hold the specified amount of zones.
inline NODISCARD int zoneCountToBits(int nZones) {
    if (!nZones) return 0;
    int iFinalBits = 0;
    int zoneBits = nZones - 1;
    for (; zoneBits > 1; iFinalBits += 2, zoneBits >>= 2);
    iFinalBits += zoneBits;
    return iFinalBits;
}

/**
 * Returns the sum of all bits of all dimensions defined before the given
 * dimensions (@a type). This allows to access cases of that particular
 * dimension directly. If the supplied dimension @a type does not exist in the
 * the supplied @a region, then this function returns -1 instead!
 *
 * @param type - dimension that shall be used
 * @param rgn - parent region of that dimension
 */
inline NODISCARD int baseBits(gig::dimension_t type, gig::Region* rgn) {
    int previousBits = 0;
    for (uint i = 0; i < rgn->Dimensions; ++i) {
        if (rgn->pDimensionDefinitions[i].dimension == type) return previousBits;
        previousBits += rgn->pDimensionDefinitions[i].bits;
    }
    return -1;
}

// key: dimension type, value: dimension's zone index
class DimensionCase : public std::map<gig::dimension_t,int> {
public:
    NODISCARD bool isViolating(const DimensionCase& c) const {
        for (DimensionCase::const_iterator it = begin(); it != end(); ++it) {
            if (c.find(it->first) == c.end()) continue;
            if (c.find(it->first)->second != it->second) return true;
        }
        return false;
    }

    // prevent passing gig::dimension_none from creating a new pair
    // (TODO: other invalid gig::dimension_t values should be filtered here as well)
    int& operator[](const gig::dimension_t& k) {
        static int unused = 0;
        if (k == gig::dimension_none) {
            unused = 0;
            return unused;
        }
        return std::map<gig::dimension_t,int>::operator[](k);
    }
};

//TODO: this function and caseOfDimRegion() from dimregionchooser.h are duplicates, eliminate either one of them!
inline NODISCARD DimensionCase dimensionCaseOf(gig::DimensionRegion* dr) {
    DimensionCase dimCase;
    int idr = getDimensionRegionIndex(dr);
    if (idr < 0) return dimCase;
    gig::Region* rgn = (gig::Region*)dr->GetParent();
    int bitpos = 0;
    for (int d = 0; d < rgn->Dimensions; ++d) {
        const gig::dimension_def_t& dimdef = rgn->pDimensionDefinitions[d];
        const int zone = (idr >> bitpos) & ((1 << dimdef.bits) - 1);
        dimCase[dimdef.dimension] = zone;
        bitpos += rgn->pDimensionDefinitions[d].bits;
    }
    return dimCase;
}

/**
 * Checks whether the passed dimension zones are within the boundaries of the
 * defined dimensions. This is especially relevant if there are dimensions
 * defined with an amount not equal to a power of two, in that case there are
 * unused dimensions regions which should be ignored.
 */
inline NODISCARD bool isUsedCase(const DimensionCase& c, gig::Region* rgn) {
    for (int d = 0; d < rgn->Dimensions; ++d) {
        gig::dimension_t type = rgn->pDimensionDefinitions[d].dimension;
        if (c.find(type) == c.end()) continue;
        int zone = c.find(type)->second;
        if (zone < 0 || zone >= rgn->pDimensionDefinitions[d].zones)
            return false;
    }
    return true;
}

inline NODISCARD std::vector<gig::DimensionRegion*> dimensionRegionsMatching(
    const DimensionCase& dimCase, gig::Region* rgn, bool skipUnusedZones = false)
{
    std::vector<gig::DimensionRegion*> v;
    for (int idr = 0; idr < 256; ++idr) {
        if (!rgn->pDimensionRegions[idr]) continue;
        DimensionCase c = dimensionCaseOf(rgn->pDimensionRegions[idr]);
        if (dimCase.isViolating(c)) continue;
        if (skipUnusedZones && !isUsedCase(c, rgn)) continue;
        v.push_back(rgn->pDimensionRegions[idr]);
    }
    return v;
}

inline NODISCARD
gig::DimensionRegion* dimensionRegionMatching(const DimensionCase& dimCase, gig::Region* rgn) {
    for (int idr = 0; idr < 256; ++idr) {
        if (!rgn->pDimensionRegions[idr]) continue;
        DimensionCase c = dimensionCaseOf(rgn->pDimensionRegions[idr]);
        if (c == dimCase) return rgn->pDimensionRegions[idr];
    }
    return NULL;
}

inline NODISCARD std::list<gig::Sample*> unusedSamples(gig::File* file) {
    std::list<gig::Sample*> lsamples;
    for (size_t s = 0; gig::Sample* sample = file->GetSample(s); ++s) {
        bool isUsed = false;
        for (size_t i = 0;
             gig::Instrument* instrument = file->GetInstrument(i);
             ++i)
        {
            for (size_t r = 0;
                 gig::Region* rgn = instrument->GetRegionAt(r);
                 ++r)
            {
                for (int dr = 0; dr < 256; ++dr) {
                    if (!rgn->pDimensionRegions[dr]) continue;
                    if (rgn->pDimensionRegions[dr]->pSample != sample) continue;
                    isUsed = true;
                    goto endOfRefSearch;
                }
            }
        }
        endOfRefSearch:
        if (!isUsed) lsamples.push_back(sample);
    }
    return lsamples;
}

inline NODISCARD std::vector<gig::Script*> unusedScripts(gig::File* file) {
    std::vector<gig::Script*> scripts;
    for (int iGroup = 0; file->GetScriptGroup(iGroup); ++iGroup) {
        gig::ScriptGroup* group = file->GetScriptGroup(iGroup);
        for (int iScript = 0; group->GetScript(iScript); ++iScript) {
            gig::Script* script = group->GetScript(iScript);
            bool isUsed = false;
            for (size_t i = 0;
                 gig::Instrument* instrument = file->GetInstrument(i);
                 ++i)
            {
                for (int iSlot = 0; iSlot < instrument->ScriptSlotCount(); ++iSlot)
                {
                    if (instrument->GetScriptOfSlot(iSlot) == script) {
                        isUsed = true;
                        goto endOfRefSearch;
                    }
                }
            }
            endOfRefSearch:
            if (!isUsed) scripts.push_back(script);
        }
    }
    return scripts;
}

template<typename T_Message>
class SignalGuard {
public:
    SignalGuard(sigc::signal<void, T_Message>& start, sigc::signal<void, T_Message>& end, T_Message message)
        : m_end(end), m_message(message)
    {
        if (message) start.emit(message);
    }

    virtual ~SignalGuard() {
        if (m_message) m_end.emit(m_message);
    }
protected:
    sigc::signal<void, T_Message>& m_end;
    T_Message m_message;
};

#endif // GIGEDIT_GLOBAL_H
