/* enchant
 * Copyright (C) 2004 Remi Payette
 * Copyright (C) 2004 Francis James Franklin
 * Copyright (C) 2017-2026 Reuben Thomas
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along along with this program; if not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <glib.h>
#include <gmodule.h>

#import <Cocoa/Cocoa.h>

#include "enchant-provider.h"

static EnchantProvider *provider;

class AppleSpellChecker
{
 public:
	AppleSpellChecker();

	~AppleSpellChecker();

	bool		checkWord (const char * word, size_t len, NSString * lang);
	char **		suggestWord (const char * const word, size_t len, size_t * nsug, NSString * lang);

	NSString *	requestDictionary (const char * const code);
	char **		listDictionaries (size_t *ndicts);
 private:
	char **		NSArrayToCArray (NSArray<NSString *> *array, size_t *nresult);

	NSSpellChecker *	m_checker;
	NSMutableDictionary *	m_languages;
	NSMutableDictionary *	m_dictionaries;
};

typedef struct
{
	AppleSpellChecker *	AppleSpell;
	NSString *		DictionaryName;
} AppleSpellDictionary;

AppleSpellChecker::AppleSpellChecker()
{
	m_checker = [NSSpellChecker sharedSpellChecker];
	m_languages = [[NSMutableDictionary alloc] initWithCapacity:16];
	m_dictionaries = [[NSMutableDictionary alloc] initWithCapacity:16];
}

AppleSpellChecker::~AppleSpellChecker()
{
	if (m_languages) {
		[m_languages release];
		m_languages = 0;
	}
	if (m_dictionaries) {
		[m_dictionaries release];
		m_dictionaries = 0;
	}
}

char **AppleSpellChecker::NSArrayToCArray (NSArray<NSString *> *array, size_t *nresult)
{
	char ** result = 0;
	*nresult = 0;

	if (unsigned int count = [array count]) {
		result = g_new0 (char *, static_cast<size_t>(count) + 1);
		if (result) {
			for (unsigned int i = 0; i < count; i++) {
				NSString *str = [array objectAtIndex:i];
				result[*nresult] = g_strdup ([str UTF8String]);
				if (result[*nresult])
					(*nresult)++;
			}
		}
	}
	return result;
}

bool AppleSpellChecker::checkWord (const char * word, size_t len, NSString * lang)
{
	if (!m_checker || !lang)
		return false;

	NSString * str = [[NSString alloc] initWithBytes:word length:len encoding:NSUTF8StringEncoding];
	if (!str)
		return false;

	[m_checker setLanguage:lang];

	NSRange result = [m_checker checkSpellingOfString:str startingAt:0];

	[str release];

	return (result.length ? true : false);
}

char ** AppleSpellChecker::suggestWord (const char * const word, size_t len, size_t * nsug, NSString * lang)
{
	if (!m_checker || !word || !len || !nsug || !lang)
		return 0;

	*nsug = 0;

	[m_checker setLanguage:lang];

	NSString * str = [[NSString alloc] initWithBytes:word length:len encoding:NSUTF8StringEncoding];
	if (!str)
		return 0;

	NSRange range = NSMakeRange(0, [str length]);
	NSArray<NSString *>* result = [m_checker guessesForWordRange:range inString:str language:lang inSpellDocumentWithTag:0];

	[str release];

	return NSArrayToCArray(result, nsug);
}

NSString * AppleSpellChecker::requestDictionary (const char * const code)
{
	if (!m_checker || !code)
		return 0;

	NSString * dictionary = [NSString stringWithUTF8String:code];
	NSString * language = [m_checker language];
	if (![m_checker setLanguage:dictionary])
		dictionary = 0;
	if (language)
		[m_checker setLanguage:language];
	return dictionary;
}

char **AppleSpellChecker::listDictionaries (size_t *ndict)
{
	if (!m_checker)
		return 0;

	NSArray<NSString *> *availLanguages = [m_checker availableLanguages];
	NSMutableArray<NSString *> *availDicts = [NSMutableArray arrayWithCapacity:[availLanguages count]];

	for (NSString *string in availLanguages)
		[availDicts addObject:string];

	return NSArrayToCArray(availDicts, ndict);
}

/*
 * Enchant
 */

static char ** appleSpell_dict_suggest (EnchantProviderDict * me, const char * const word, size_t len, size_t * out_n_suggs)
{
	@autoreleasepool {
		if (!me || !word || !len || !out_n_suggs)
			return 0;

		char ** result = 0;

		if (AppleSpellDictionary * ASD = static_cast<AppleSpellDictionary *>(me->user_data))
			result = ASD->AppleSpell->suggestWord (word, len, out_n_suggs, ASD->DictionaryName);

		return result;
	}
}

static int appleSpell_dict_check (EnchantProviderDict * me, const char * const word, size_t len)
{
	@autoreleasepool {
		if (!me || !word || !len)
			return 0;

		int result = 0;

		if (AppleSpellDictionary * ASD = static_cast<AppleSpellDictionary *>(me->user_data))
			result = ASD->AppleSpell->checkWord (word, len, ASD->DictionaryName);
		return result;
	}
}

static EnchantProviderDict * appleSpell_provider_request_dict (EnchantProvider * me, const char * const tag)
{
	@autoreleasepool {
		AppleSpellChecker * checker = static_cast<AppleSpellChecker *>(me->user_data);

		if (!me || !tag || !checker)
			return 0;

		AppleSpellDictionary * ASD = g_new0 (AppleSpellDictionary, 1);
		if (!ASD)
			return 0;

		ASD->AppleSpell     = checker;
		ASD->DictionaryName = checker->requestDictionary (tag);

		if (!ASD->DictionaryName) {
			g_free (ASD);
			return 0;
		}

		[ASD->DictionaryName retain];

		EnchantProviderDict * dict = enchant_provider_dict_new (provider, tag);
		if (!dict)
			return 0;

		dict->check     = appleSpell_dict_check;
		dict->suggest   = appleSpell_dict_suggest;
		dict->user_data = (void *) ASD;

		return dict;
	}
}

static void appleSpell_provider_dispose_dict (EnchantProvider * me, EnchantProviderDict * dict)
{
	@autoreleasepool {
		if (dict) {
			AppleSpellDictionary * ASD = static_cast<AppleSpellDictionary *>(dict->user_data);
			if (ASD) {
				[ASD->DictionaryName release];
				g_free (ASD);
			}
		}
	}
}

static int appleSpell_provider_dictionary_exists (EnchantProvider * me, const char * const tag)
{
	@autoreleasepool {
		int result = 0;

		AppleSpellChecker * checker = static_cast<AppleSpellChecker *>(me->user_data);
		if (checker)
			result = (checker->requestDictionary (tag) ? 1 : 0);

		return result;
	}
}

static char **appleSpell_provider_list_dicts (EnchantProvider *me, size_t *n_dicts)
{
	@autoreleasepool {
		char **result = 0;

		AppleSpellChecker * checker = static_cast<AppleSpellChecker *>(me->user_data);
		if (checker)
			result = checker->listDictionaries (n_dicts);
		else
			*n_dicts = 0;

		return result;
	}
}

static void appleSpell_provider_dispose (EnchantProvider * me)
{
	@autoreleasepool {
		if (me) {
			AppleSpellChecker * checker = static_cast<AppleSpellChecker *>(me->user_data);
			if (checker)
				delete checker;
		}
	provider = 0;
	}
}

static const char * appleSpell_provider_identify (EnchantProvider * me)
{
	return "AppleSpell";
}

static const char * appleSpell_provider_describe (EnchantProvider * me)
{
	return "AppleSpell Provider";
}

extern "C" {
	EnchantProvider *init_enchant_provider (void)
	{
		@autoreleasepool {
			provider = enchant_provider_new ();
			if (!provider)
				return 0;

			provider->dispose           = appleSpell_provider_dispose;
			provider->request_dict      = appleSpell_provider_request_dict;
			provider->dispose_dict      = appleSpell_provider_dispose_dict;
			provider->dictionary_exists = appleSpell_provider_dictionary_exists;
			provider->identify          = appleSpell_provider_identify;
			provider->describe          = appleSpell_provider_describe;
			provider->list_dicts        = appleSpell_provider_list_dicts;

			try {
				AppleSpellChecker * checker = new AppleSpellChecker;
				provider->user_data = (void *) checker;
			} catch (...) {
				g_free (provider);
				provider = 0;
			}

			return provider;
		}
	}
}
