//
//  FMMiniPlugIn.cpp
//  FMMiniPlugIn
//
//  Copyright © 2016 - 2024  Claris International Inc.
//  All rights reserved.
//
//  Claris International Inc. grants you a non-exclusive limited license to use this file solely
//  to enable licensees of Claris FileMaker Pro to compile plug-ins for use with Claris products.
//  Redistribution and use in source and binary forms, without modification, are permitted provided
//  that the following conditions are met:
//
//  * Redistributions of source code must retain the above copyright notice, this list of
//  conditions and the following disclaimer.
//
//  * The name Claris International Inc. may not be used to endorse or promote products derived
//  from this software without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY CLARIS INTERNATIONAL INC. ''AS IS'' AND ANY
//  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//  DISCLAIMED. IN NO EVENT SHALL FILEMAKER, INC. BE LIABLE FOR ANY DIRECT,
//  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
//  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
//  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
//  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

#include "FMWrapper/FMXTypes.h"
#include "FMWrapper/FMXText.h"
#include "FMWrapper/FMXFixPt.h"
#include "FMWrapper/FMXData.h"
#include "FMWrapper/FMXCalcEngine.h"

// Exported plug-in functions/script steps =================================================

static const char* kFMmp( "FMmp" );

enum { kFMmp_GetPSFontID = 100, kFMmp_GetPSFontMin = 1, kFMmp_GetPSFontMax = 1 };
static const char* kFMmp_GetPSFontName( "FMmp_GetPSFont" );
static const char* kFMmp_GetPSFontDefinition( "FMmp_GetPSFont( text )" );
static const char* kFMmp_GetPSFontDescription( "Returns the postscript name of the font of the first character if it has been set by the user" );

static FMX_PROC(fmx::errcode) Do_FMmp_GetPSFont( short /* funcId */, const fmx::ExprEnv& environment, const fmx::DataVect& dataVect, fmx::Data& results )
{
	fmx::errcode errorResult( 960 );
	fmx::TextUniquePtr outText;
	
	if (dataVect.Size() >= 1)
	{
		const fmx::Data&                dat( dataVect.At( 0 ) );
		const fmx::Text&                txt( dat.GetAsText() );
		fmx::CharacterStyleUniquePtr    chr;
		txt.GetStyle( *chr, 0 );

		if (chr->IsFontEnabled())
		{
			fmx::CharacterStyle::FontID fontID;
			fmx::CharacterStyle::Face fontFace;
			fmx::CharacterStyle::FontSize fontSize;
			chr->GetFontInformation( fontID, fontFace, fontSize );
			if (! dat.GetFontInfo( fontID, *outText, environment ))
			{
				outText->Assign( "COULD NOT GET FONT NAME FOR FIRST CHARACTER", fmx::Text::kEncoding_UTF8 );
			}
		}
		else
		{
			outText->Assign( "NO CUSTOM FONT FOR FIRST CHARACTER", fmx::Text::kEncoding_UTF8 );
		}

		results.SetAsText( *outText, dat.GetLocale() );
		errorResult = 0;
	}
	
	return errorResult;
}

enum { kFMmp_ConvertToBaseID = 200, kFMmp_ConvertToBaseMin = 1, kFMmp_ConvertToBaseMax = 1 };
static const char* kFMmp_ConvertToBaseName( "Convert To Base" );
static const char* kFMmp_ConvertToBaseDefinition( "<PluginStep>"
													  "<Parameter Type=\"target\" Label=\"Destination\" ShowInLine=\"true\"/>"
													  "<Parameter Type=\"calc\" DataType=\"number\" ShowInline=\"true\" Label=\"Number\" ID=\"0\"/>"
													  "<Parameter Type=\"list\" ShowInline=\"true\" Label=\"Base\" Default=\"16\" ID=\"1\">"
														  "<Value ID=\"2\">Binary</Value>"
														  "<Value ID=\"3\">Ternary</Value>"
														  "<Value ID=\"8\">Octal</Value>"
														  "<Value ID=\"12\">Duodecimal</Value>"
														  "<Value ID=\"16\">Hexadecimal</Value>"
													  "</Parameter>"
												  "</PluginStep>" );
static const char* kFMmp_ConvertToBaseDescription( "Converts the number into a string using the specified base" );

inline void PrependCharacter( fmx::Text& txt, fmx::Text& insertBuffer, fmx::uint16 ch )
{
	insertBuffer.AssignUnicodeWithLength( &ch, 1 );
	txt.InsertText( insertBuffer, 0 );
}

static FMX_PROC(fmx::errcode) Do_FMmp_ConvertToBase( short /* funcId */, const fmx::ExprEnv& /* environment */, const fmx::DataVect& dataVect, fmx::Data& results )
{
	fmx::errcode errorResult( fmx::ExprEnv::kPluginErrResult1 );
	fmx::TextUniquePtr outText;
	const fmx::Locale* outLocale( &results.GetLocale() );
	fmx::TextUniquePtr insertBuffer;

	if (dataVect.Size() >= 2)
	{
		const fmx::Data&    dat( dataVect.At( 0 ) );
		fmx::int32          number( dat.GetAsNumber().AsLong() );
		fmx::int32          base( dataVect.At( 1 ).GetAsNumber().AsLong() );

		if (base > 0)
		{
			if (number == 0)
			{
				PrependCharacter( *outText, *insertBuffer, '0' );
			}
			else
			{
				bool neg( number < 0 );
				if (neg) number = -number;
				while (number > 0)
				{
					fmx::uint16 digit( static_cast<fmx::uint16>(number % base) );
					PrependCharacter( *outText, *insertBuffer, digit + ((digit < 10) ? '0' : ('A' - 10)) );
					number /= base;
				}
				if (neg) PrependCharacter( *outText, *insertBuffer, '-' );
			}

			outLocale = &dat.GetLocale();
			errorResult = 0;
		}
		else
		{
			outText->AssignWide( L"BAD BASE VALUE" );
		}
	}
	else
	{
		outText->AssignWide( L"NOT ENOUGH PARAMETERS" );
	}

	results.SetAsText( *outText, *outLocale );
	return errorResult;
}

// Do_PluginInit ===========================================================================

static fmx::ptrtype Do_PluginInit( fmx::int16 version )
{
	fmx::ptrtype                    result( static_cast<fmx::ptrtype>(kDoNotEnable) );
	const fmx::QuadCharUniquePtr    pluginID( kFMmp[0], kFMmp[1], kFMmp[2], kFMmp[3] );
	fmx::TextUniquePtr              name;
	fmx::TextUniquePtr              definition;
	fmx::TextUniquePtr              description;
	fmx::uint32                     flags( fmx::ExprEnv::kDisplayInAllDialogs | fmx::ExprEnv::kFutureCompatible );

	if (version >= k150ExtnVersion)
	{
		name->Assign( kFMmp_GetPSFontName, fmx::Text::kEncoding_UTF8 );
		definition->Assign( kFMmp_GetPSFontDefinition, fmx::Text::kEncoding_UTF8 );
		description->Assign( kFMmp_GetPSFontDescription, fmx::Text::kEncoding_UTF8 );
		if (fmx::ExprEnv::RegisterExternalFunctionEx( *pluginID, kFMmp_GetPSFontID, *name, *definition, *description, kFMmp_GetPSFontMin, kFMmp_GetPSFontMax, flags, Do_FMmp_GetPSFont ) == 0)
		{
			result = kCurrentExtnVersion;
		}
	}
	else if (version == k140ExtnVersion)
	{
		name->Assign( kFMmp_GetPSFontName, fmx::Text::kEncoding_UTF8 );
		definition->Assign( kFMmp_GetPSFontDefinition, fmx::Text::kEncoding_UTF8 );
		if (fmx::ExprEnv::RegisterExternalFunction( *pluginID, kFMmp_GetPSFontID, *name, *definition, kFMmp_GetPSFontMin, kFMmp_GetPSFontMax, flags, Do_FMmp_GetPSFont ) == 0)
		{
			result = kCurrentExtnVersion;
		}
	}

	if (version >= k160ExtnVersion)
	{
		name->Assign( kFMmp_ConvertToBaseName, fmx::Text::kEncoding_UTF8 );
		definition->Assign( kFMmp_ConvertToBaseDefinition, fmx::Text::kEncoding_UTF8 );
		description->Assign( kFMmp_ConvertToBaseDescription, fmx::Text::kEncoding_UTF8 );
		if (fmx::ExprEnv::RegisterScriptStep( *pluginID, kFMmp_ConvertToBaseID, *name, *definition, *description, flags, Do_FMmp_ConvertToBase ) == 0)
		{
			result = kCurrentExtnVersion;
		}
	}
	
	return result;
}

// Do_PluginShutdown =======================================================================

static void Do_PluginShutdown( fmx::int16 version )
{
	const fmx::QuadCharUniquePtr    pluginID( kFMmp[0], kFMmp[1], kFMmp[2], kFMmp[3] );
	
	if (version >= k140ExtnVersion)
	{
		static_cast<void>(fmx::ExprEnv::UnRegisterExternalFunction( *pluginID, kFMmp_GetPSFontID ));
	}

	if (version >= k160ExtnVersion)
	{
		static_cast<void>(fmx::ExprEnv::UnRegisterScriptStep( *pluginID, kFMmp_ConvertToBaseID ));
	}
	
}

// Do_GetString ============================================================================

static void CopyUTF8StrToUnichar16Str( const char* inStr, fmx::uint32 outStrSize, fmx::unichar16* outStr )
{
	fmx::TextUniquePtr txt;
	txt->Assign( inStr, fmx::Text::kEncoding_UTF8 );
	const fmx::uint32 txtSize( (outStrSize <= txt->GetSize()) ? (outStrSize - 1) : txt->GetSize() );
	txt->GetUnicode( outStr, 0, txtSize );
	outStr[txtSize] = 0;
}

static void Do_GetString( fmx::uint32 whichString, fmx::uint32 /* winLangID */, fmx::uint32 outBufferSize, fmx::unichar16* outBuffer )
{
	switch (whichString)
	{
		case kFMXT_NameStr:
		{
			CopyUTF8StrToUnichar16Str( "FMMiniPlugIn", outBufferSize, outBuffer );
			break;
		}
			
		case kFMXT_AppConfigStr:
		{
			CopyUTF8StrToUnichar16Str( "Small example plug-in from FileMaker", outBufferSize, outBuffer );
			break;
		}
			
		case kFMXT_OptionsStr:
		{
			// Characters 1-4 are the plug-in ID
			CopyUTF8StrToUnichar16Str( kFMmp, outBufferSize, outBuffer );

			// Character 5 is always "1"
			outBuffer[4] = '1';
			
			// Character 6
			// use "Y" if you want to enable the Configure button for plug-ins in the Preferences dialog box.
			// Use "n" if there is no plug-in configuration needed.
			// If the flag is set to "Y" then make sure to handle the kFMXT_DoAppPreferences message.
			outBuffer[5] = 'n';
			
			// Character 7  is always "n"
			outBuffer[6] = 'n';
			
			// Character 8
			// Set to "Y" if you want to receive kFMXT_Init/kFMXT_Shutdown messages
			// In most cases, you want to set it to 'Y' since it's the best time to register/unregister your plugin functions.
			outBuffer[7] = 'Y';
			
			// Character 9
			// Set to "Y" if the kFMXT_Idle message is required.
			// For simple external functions this may not be needed and can be turned off by setting the character to "n"
			outBuffer[8] = 'n';
			
			// Character 10
			// Set to "Y" to receive kFMXT_SessionShutdown and kFMXT_FileShutdown messages
			outBuffer[9] = 'n';
			
			// Character 11 is always "n"
			outBuffer[10] = 'n';
			
			// NULL terminator
			outBuffer[11] = 0;
			break;
		}

		case kFMXT_HelpURLStr:
		{
			CopyUTF8StrToUnichar16Str( "http://httpbin.org/get?id=", outBufferSize, outBuffer );
			break;
		}

		default:
		{
			outBuffer[0] = 0;
			break;
		}
	}
}

// Do_PluginIdle ===========================================================================

static void Do_PluginIdle( FMX_IdleLevel idleLevel, fmx::ptrtype /* sessionId */ )
{
	// Check idle state
	switch (idleLevel)
	{
		case kFMXT_UserIdle:
		case kFMXT_UserNotIdle:
		case kFMXT_ScriptPaused:
		case kFMXT_ScriptRunning:
		case kFMXT_Unsafe:
		default:
		{
			break;
		}
	}
}

// Do_PluginPrefs ==========================================================================

static void Do_PluginPrefs( void )
{
}

// Do_SessionNotifications =================================================================

static void Do_SessionNotifications( fmx::uint64 /* sessionID */ )
{
}

// Do_FileNotifications ====================================================================

static void Do_FilenNotifications( fmx::uint64 /* sessionID */, fmx::uint64 /* fileID */ )
{
}

// Do_SchemaNotifications ====================================================================

static void Do_SchemaNotifications( char* /* json utf8 text */, fmx::uint64 /* json length */ )
{
}

// FMExternCallProc ========================================================================

FMX_ExternCallPtr gFMX_ExternCallPtr( nullptr );

void FMX_ENTRYPT FMExternCallProc( FMX_ExternCallPtr pb )
{
	// Setup global defined in FMXExtern.h (this will be obsoleted in a later header file)
	gFMX_ExternCallPtr = pb;
	
	// Message dispatcher
	switch (pb->whichCall)
	{
		case kFMXT_Init:
			pb->result = Do_PluginInit( pb->extnVersion );
			break;
			
		case kFMXT_Idle:
			Do_PluginIdle( pb->parm1, pb->parm2 );
			break;
			
		case kFMXT_Shutdown:
			Do_PluginShutdown( pb->extnVersion );
			break;
			
		case kFMXT_DoAppPreferences:
			Do_PluginPrefs();
			break;
			
		case kFMXT_GetString:
			Do_GetString( static_cast<fmx::uint32>(pb->parm1), static_cast<fmx::uint32>(pb->parm2), static_cast<fmx::uint32>(pb->parm3), reinterpret_cast<fmx::unichar16*>(pb->result) );
			break;
			
		case kFMXT_SessionShutdown:
			Do_SessionNotifications( pb->parm2 );
			break;

		case kFMXT_FileShutdown:
			Do_FilenNotifications( pb->parm2, pb->parm3 );
			break;
			
		case kFMXT_SchemaChange:
			Do_SchemaNotifications( reinterpret_cast<char*>(pb->parm2), pb->parm3 );
			break;
			
	} // switch whichCall

} // FMExternCallProc
