/*
 *  This file is part of Poedit (https://poedit.com)
 *
 *  Copyright (C) 2014-2026 Vaclav Slavik
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *  DEALINGS IN THE SOFTWARE.
 *
 */

#include "spellchecking.h"

#include "str_helpers.h"
#include "text_control.h"

#ifdef __WXGTK__
    #include <gtk/gtk.h>
    extern "C" {
    #include <gtkspell/gtkspell.h>
    }
#endif

#ifdef __WXMSW__
    #include <wx/msw/wrapwin.h>
    #include <Richedit.h>
    #ifndef IMF_SPELLCHECKING
        #define IMF_SPELLCHECKING 0x0800
    #endif
#endif

#include "edapp.h"

namespace
{

inline std::string SpellcheckCode(const Language& lang)
{
#ifdef __WXOSX__
    return lang.LanguageTag();
#else
    return lang.Code();
#endif
}

// Run the provided code with not only the original language, but spellchecking-relevant
// variants too. On some platforms (Linux/hunspell most notably) base language may be unavailable
// and a full locale required, for example.
// The function takes std::string language code, not Language, for practical reasons
template<typename TFunc>
auto DoWithExpandedLanguages(const Language& lang, TFunc&& lambda)
{
#if defined(__WXGTK__) || defined(__WXOSX__)
    if (lang.Code() == "sr@latin" || lang.Code() == "sr_RS@latin")
    {
    #ifdef __WXOSX__
        // This has to be done before normal match, because OSX will match "sr" (Cyrillic) from
        // Dictionaries.app otherwise; it matches hunspell too, so let's try it there too
        return lambda("sr-Latn");
    #else
        // Linux hunspell for Serbian is a mess. Let's try a few variants observed in the field.
        if (auto res = lambda("sr_Latn")) // upstream
            return res;
        if (auto res = lambda("sr_Latn")) // upstream
            return res;
        if (auto res = lambda("sr_Latn_RS")) // Debian/Ubuntu
            return res;
        if (auto res = lambda("sh_YU")) // Fedora/RedHat as of 2026, yes for real
            return res;
    #endif
    }
#endif

    if (auto res = lambda(SpellcheckCode(lang)))
        return res;

    auto lmin = lang.MinimizeSubtags();
    if (auto res = lambda(SpellcheckCode(lmin)))
        return res;

    if (!lmin.Country().empty() && lmin.Variant().empty())
    {
        // try the base language, because minimization won't collapse e.g. de_AT
        lmin = Language::FromLanguageTag(lmin.Lang());
        if (auto res = lambda(SpellcheckCode(lmin)))
            return res;
    }

    // try expansion because e.g. hunspell has cs_CZ, nl_NL etc. dicts, but not cs, nl
    auto lmax = lmin.AddRelevantLikelySubtags();
    return lambda(SpellcheckCode(lmax));
}

} // anonymous namespace


#ifdef __WXGTK__

bool InitTextCtrlSpellchecker(CustomizedTextCtrl *text, bool enable, const Language& lang)
{
    GtkTextView *textview = GTK_TEXT_VIEW(text->GetGtkTextView());
    wxASSERT_MSG( textview, "wxTextCtrl is supposed to use GtkTextView" );

    GtkSpellChecker *spell = gtk_spell_checker_get_from_text_view(textview);

    if (enable)
    {
        if (!spell)
        {
            spell = gtk_spell_checker_new();
            gtk_spell_checker_attach(spell, textview);
        }

        return DoWithExpandedLanguages(lang, [=](const std::string& lang){
            return gtk_spell_checker_set_language(spell, lang.c_str(), nullptr);
        });
    }
    else
    {
        if (spell)
            gtk_spell_checker_detach(spell);
        return true;
    }
}

#endif // __WXGTK__

#ifdef __WXOSX__
bool SetSpellcheckerLang(const Language& lang)
{
    NSSpellChecker *sc = [NSSpellChecker sharedSpellChecker];
    [sc setAutomaticallyIdentifiesLanguages:NO];
    return DoWithExpandedLanguages(lang, [=](const std::string& lang){
        return [sc setLanguage: str::to_NS(lang)];
    });
}

bool InitTextCtrlSpellchecker(CustomizedTextCtrl *text, bool enable, const Language& /*lang*/)
{
    NSScrollView *scroll = (NSScrollView*)text->GetHandle();
    NSTextView *view = [scroll documentView];

    [view setContinuousSpellCheckingEnabled:enable];
    [view setGrammarCheckingEnabled:enable];

    return true;
}
#endif // __WXOSX__

#ifdef __WXMSW__
void PrepareTextCtrlForSpellchecker(CustomizedTextCtrl *text)
{
    // Set spellchecking-friendly style on the text control. Enabling spellchecking
    // itself is done with EM_SETLANGOPTIONS in InitTextCtrlSpellchecker()
    HWND hwnd = (HWND)text->GetHWND();
    auto editStyle = SES_USECTF | SES_CTFALLOWEMBED | SES_CTFALLOWSMARTTAG | SES_CTFALLOWPROOFING;
    ::SendMessage(hwnd, EM_SETEDITSTYLE, editStyle, editStyle);
}

bool InitTextCtrlSpellchecker(CustomizedTextCtrl *text, bool enable, const Language& /*lang*/)
{
    HWND hwnd = (HWND) text->GetHWND();
    auto langOptions = ::SendMessage(hwnd, EM_GETLANGOPTIONS, 0, 0);
    if (enable)
        langOptions |= IMF_SPELLCHECKING;
    else
        langOptions &= ~IMF_SPELLCHECKING;
    ::SendMessage(hwnd, EM_SETLANGOPTIONS, 0, langOptions);
    return true;
}
#endif // __WXMSW__


#ifndef __WXMSW__
void ShowSpellcheckerHelp()
{
#if defined(__WXOSX__)
    wxGetApp().OpenPoeditWeb("/help/spellchecking-mac");
#elif defined(__UNIX__)
    wxGetApp().OpenPoeditWeb("/help/spellchecking-linux");
#else
    #error "missing spellchecker instructions for platform"
#endif
}
#endif // !__WXMSW__
