/*
**  File:           Samples\ClusterUDF\ClusterUDF.c
**  Description:    XLL SDK Example
**  Platform:       Microsoft Windows 32-Bit or 64-bit
**
**
**  This file uses the framework library
**  (frmwrk32.lib).
**
*/

#include <ctype.h>
#include <windows.h>
#include <math.h>
#include <xlcall.h>
#include <framewrk.h>

void WINAPI xlAutoFree12(LPXLOPER12 pxFree);
LPXLOPER12 WINAPI XllFindPrimes(long nFrom, long nTo);

/*
** rgFuncs
**
** This is a table of all the functions exported by this module.
** These functions are all registered (in xlAutoOpen) when you
** open the XLL. Before every string, leave a space for the
** byte count. The format of this table is the same as
** the last seven arguments to the REGISTER function.
** rgFuncsRows define the number of rows in the table. The
** dimension [3] represents the number of columns in the table.
*/
#define rgFuncsRows 2

static LPWSTR rgFuncs[rgFuncsRows][7] = {
	{L"XllCountPrimes",				L"JJJ",     L"XllCountPrimes"},
	{L"XllCountPrimesC",			L"JJJ&",    L"XllCountPrimesC"},
};

/*
** DllMain
**
** This function is called by LibEntry which is called
** by Windows when the DLL is first loaded. LibEntry initializes the
** DLL's heap if a HEAPSIZE is specified in the DLL's .DEF file, and
** then calls DllMain. The following DllMain function satisfies that
** call. The DllMain function should perform additional initialization
** tasks required by the DLL. In this example, we byte-count all the strings
** in the preceding table. DllMain will be called will be called once per 
** process. In Win32 DllMain replaces both the LibMain and WEP functions.
**
** Arguments:
**
**      HANDLE hInstance			Instance handle
**      ULONG ul_reason_for_call	Reason DllMain was called
**      LPVOID lpReserved			Reserved
**
** Returns:
**
**      int                 1 if initialization is successful.
*/
BOOL WINAPI DllMain(HANDLE hInstance, ULONG ul_reason_for_call,
		   LPVOID lpReserved)
{

	/* Deprecated due to MSVC handling of statically allocated strings */

	/*
	** In the following for loops, the strings in rgFuncs[] are byte-counted
	** so that they won't need to be byte-counted later.
	*/

	/*if(ul_reason_for_call == DLL_PROCESS_ATTACH)
	{
		int i,j;

		for (i = 0; i < rgFuncsRows; i++) 
		{
			for (j = 0; j < 7; j++) 
			{
				rgFuncs[i][j][0] = (BYTE)lstrlen (rgFuncs[i][j]+1);
			}
		}
	}*/

	return 1;
}


/*
** xlAutoOpen
**
** xlAutoOpen is how Microsoft Excel loads XLL files.
** When you open an XLL, Microsoft Excel calls the xlAutoOpen
** function, and nothing more.
**
** More specifically, xlAutoOpen is called by Microsoft Excel:
**
**  - when you open this XLL file from the File menu,
**  - when this XLL is in the XLSTART directory, and is
**		automatically opened when Microsoft Excel starts,
**  - when Microsoft Excel opens this XLL for any other reason, or
**  - when a macro calls REGISTER(), with only one argument, which is the
**		name of this XLL.
**
** xlAutoOpen is also called by the Add-in Manager when you add this XLL
** as an add-in. The Add-in Manager first calls xlAutoAdd, then calls
** REGISTER("CLUSTERUDF.XLL"), which in turn calls xlAutoOpen.
**
** xlAutoOpen should:
**
**  - register all the functions you want to make available while this
**		XLL is open,
**
**  - add any menus or menu items that this XLL supports,
**
**  - perform any other initialization you need, and
**
**  - return 1 if successful, or return 0 if your XLL cannot be opened.
*/
__declspec(dllexport) int WINAPI xlAutoOpen(void)
{

	static XLOPER12 xDLL;	/* name of this DLL */
	int i;					/* Loop index */

	/*
	** In the following block of code the name of the XLL is obtained by
	** calling xlGetName. This name is used as the first argument to the
	** REGISTER function to specify the name of the XLL. Next, the XLL loops
	** through the rgFuncs[] table, registering each function in the table using
	** xlfRegister. Functions must be registered before you can add a menu
	** item.
	*/

	Excel12f(xlGetName, &xDLL, 0);

        for (i=0;i<rgFuncsRows;i++) 
		{
			Excel12f(xlfRegister, 0, 4,
				(LPXLOPER12)&xDLL,
				(LPXLOPER12)TempStr12(rgFuncs[i][0]),
				(LPXLOPER12)TempStr12(rgFuncs[i][1]),
				(LPXLOPER12)TempStr12(rgFuncs[i][2]));
		}

	/* Free the XLL filename */
	Excel12f(xlFree, 0, 1, (LPXLOPER12)&xDLL);

	return 1;
}

/*
** xlAutoClose
**
** xlAutoClose is called by Microsoft Excel:
**
**  - when you quit Microsoft Excel, or
**  - when a macro sheet calls UNREGISTER(), giving a string argument
**		which is the name of this XLL.
**
** xlAutoClose is called by the Add-in Manager when you remove this XLL from
** the list of loaded add-ins. The Add-in Manager first calls xlAutoRemove,
** then calls UNREGISTER("CLUSTERUDF.XLL"), which in turn calls xlAutoClose.
**
**
** xlAutoClose should:
**
**  - Remove any menus or menu items that were added in xlAutoOpen,
**
**  - do any necessary global cleanup, and
**
**  - delete any names that were added (names of exported functions, and
**		so on). Remember that registering functions may cause names to be created.
**
** xlAutoClose does NOT have to unregister the functions that were registered
** in xlAutoOpen. This is done automatically by Microsoft Excel after
** xlAutoClose returns.
**
** xlAutoClose should return 1.
*/
__declspec(dllexport) int WINAPI xlAutoClose(void)
{
	int i;

	/*
	** This block first deletes all names added by xlAutoOpen or by
	** xlAutoRegister.
	*/

	for (i = 0; i < rgFuncsRows; i++)
		Excel12f(xlfSetName, 0, 1, TempStr12(rgFuncs[i][2]));

	return 1;
}

///***************************************************************************
// lpwstricmp()
//
// Purpose: Compares a pascal string and a null-terminated C-string to see
// if they are equal.  Method is case insensitive
//
// Parameters:
//
//      LPWSTR s     First string (null-terminated)
//      LPWSTR t     Second string (byte counted)
//
// Returns: 
//
//      int         0 if they are equal
//                  Nonzero otherwise
//
// Comments:
//
//      Unlike the usual string functions, lpwstricmp
//      doesn't care about collating sequence.
//
// History:  Date       Author        Reason
///***************************************************************************

int lpwstricmp(LPWSTR s, LPWSTR t)
{
	int i;

	if (wcslen(s) != *t)
		return 1;

	for (i = 1; i <= s[0]; i++)
	{
		if (towlower(s[i-1]) != towlower(t[i]))
			return 1;
	}
	return 0;
}

/*
** xlAutoRegister
**
** This function is called by Microsoft Excel if a macro sheet tries to
** register a function without specifying the type_text argument. If that
** happens, Microsoft Excel calls xlAutoRegister, passing the name of the
** function that the user tried to register. xlAutoRegister should use the
** normal REGISTER function to register the function, but this time it must
** specify the type_text argument. If xlAutoRegister does not recognize the
** function name, it should return a #VALUE! error. Otherwise, it should
** return whatever REGISTER returned.
**
** Arguments:
**
**	    LPXLOPER12 pxName   xltypeStr containing the
**                          name of the function
**                          to be registered. This is not
**                          case sensitive, because
**                          Microsoft Excel uses Pascal calling
**                          convention.
**
** Returns:
**
**      LPXLOPER12          xltypeNum containing the result
**                          of registering the function,
**                          or xltypeErr containing #VALUE!
**                          if the function could not be
**                          registered.
*/
__declspec(dllexport) LPXLOPER12 WINAPI xlAutoRegister12(LPXLOPER12 pxName)
{
	static XLOPER12 xDLL, xRegId;
	int i;

	/*
	** This block initializes xRegId to a #VALUE! error first. This is done in
	** case a function is not found to register. Next, the code loops through the
	** functions in rgFuncs[] and uses lpstricmp to determine if the current
	** row in rgFuncs[] represents the function that needs to be registered.
	** When it finds the proper row, the function is registered and the
	** register ID is returned to Microsoft Excel. If no matching function is
	** found, an xRegId is returned containing a #VALUE! error.
	*/

	xRegId.xltype = xltypeErr;
	xRegId.val.err = xlerrValue;

	for (i = 0; i < rgFuncsRows; i++) 
	{
		if (!lpwstricmp(rgFuncs[i][0], pxName->val.str)) 
		{
			Excel12f(xlGetName, &xDLL, 0);

			Excel12f(xlfRegister, 0, 4,
		  		(LPXLOPER12)&xDLL,
		  		(LPXLOPER12)TempStr12(rgFuncs[i][0]),
		  		(LPXLOPER12)TempStr12(rgFuncs[i][1]),
		  		(LPXLOPER12)TempStr12(rgFuncs[i][2]));

			/* Free the XLL filename */
			Excel12f(xlFree, 0, 1, (LPXLOPER12)&xDLL);

			return (LPXLOPER12)&xRegId;
		}
	}

	//Word of caution - returning static XLOPERs/XLOPER12s is not thread safe
	//for UDFs declared as thread safe, use alternate memory allocation mechanisms

	return (LPXLOPER12)&xRegId;
}

/*
** xlAutoAdd
**
** This function is called by the Add-in Manager only. When you add a
** DLL to the list of active add-ins, the Add-in Manager calls xlAutoAdd()
** and then opens the XLL, which in turn calls xlAutoOpen.
**
*/
__declspec(dllexport) int WINAPI xlAutoAdd(void)
{
	XCHAR szBuf[255];

	wsprintfW((LPWSTR)szBuf, L"You will now be able to use UDFs XllCountPrimes (runs locally) and XllCountPrimesC (runs on cluster).");

	/* Display a dialog box indicating that the XLL was successfully added */
	Excel12f(xlcAlert, 0, 2, TempStr12(szBuf), TempInt12(2));
	return 1;
}

/*
** xlAutoRemove
**
** This function is called by the Add-in Manager only. When you remove
** an XLL from the list of active add-ins, the Add-in Manager calls
** xlAutoRemove() and then UNREGISTER("CLUSTERUDF.XLL").
**
** You can use this function to perform any special tasks that need to be
** performed when you remove the XLL from the Add-in Manager's list
** of active add-ins. For example, you may want to delete an
** initialization file when the XLL is removed from the list.
*/
__declspec(dllexport) int WINAPI xlAutoRemove(void)
{
	/* Display a dialog box indicating that the XLL was successfully removed */
	Excel12f(xlcAlert, 0, 2, TempStr12(L"The UDFs XllCountPrimes and XllCountPrimesC will no longer be available."), TempInt12(2));
	return 1;
}

/* xlAddInManagerInfo12
**
**
** This function is called by the Add-in Manager to find the long name
** of the add-in. If xAction = 1, this function should return a string
** containing the long name of this XLL, which the Add-in Manager will use
** to describe this XLL. If xAction = 2 or 3, this function should return
** #VALUE!.
**
** Arguments
**
**      LPXLOPER12 xAction    The information you want; either
**                          1 = the long name of the
**                              add in, or
**                          2 = reserved
**                          3 = reserved
**
** Return value
**
**      LPXLOPER12            The long name or #VALUE!.
**
*/
__declspec(dllexport) LPXLOPER12 WINAPI xlAddInManagerInfo12(LPXLOPER12 xAction)
{
	static XLOPER12 xInfo, xIntAction;

	/*
	** This code coerces the passed-in value to an integer. This is how the
	** code determines what is being requested. If it receives a 1, it returns a
	** string representing the long name. If it receives anything else, it
	** returns a #VALUE! error.
	*/

	Excel12f(xlCoerce, &xIntAction, 2, xAction, TempInt12(xltypeInt));

	if(xIntAction.val.w == 1) 
	{
		xInfo.xltype = xltypeStr;
		xInfo.val.str = L"\026Cluster UDF Example";
	}
	else 
	{
		xInfo.xltype = xltypeErr;
		xInfo.val.err = xlerrValue;
	}

	//Word of caution - returning static XLOPERs/XLOPER12s is not thread safe
	//for UDFs declared as thread safe, use alternate memory allocation mechanisms

	return (LPXLOPER12)&xInfo;
}

void WINAPI xlAutoFree12(LPXLOPER12 pxFree)
{
	//free the sub-parts properly
	switch (pxFree->xltype & (~xlbitDLLFree))
	{
		case xltypeStr:
			if (pxFree->val.str)
			{
				delete pxFree->val.str;
				pxFree->val.str = NULL;
			}
			break;
		case xltypeMulti:
			if (pxFree->val.array.lparray)
			{
				//delete the child elements
				int r;
				int c;
				for (r = 0; r < pxFree->val.array.rows; r++)
				{
					for (c = 0; c < pxFree->val.array.columns; c++)
					{
						xlAutoFree12(&(pxFree->val.array.lparray[r * pxFree->val.array.columns + c]));
					}
				}
				delete pxFree->val.array.lparray;
				pxFree->val.array.lparray = NULL;
			}
			break;
	}

	//free the base oper if it's ours to free
	if (pxFree->xltype & xlbitDLLFree)
	{
		delete pxFree;
	}
}

/*
** XllCountPrimes
**
** This function counts the number of primes in the specified range and returns the result.
**
*/
long WINAPI XllCountPrimes(long nFrom, long nTo)
{
	LPXLOPER12 list = XllFindPrimes(nFrom, nTo);
	//if it's a string then we want to return an error after freeing the string
	if (list->xltype & xltypeStr)
	{
		xlAutoFree12(list);
		return -1;
	}
	//if instead, it's still not an array, then return a different error
	if (!(list->xltype & xltypeMulti))
	{
		xlAutoFree12(list);
		return -2;
	}


	long count = list->val.array.columns;
	xlAutoFree12(list);
	return count;
}

/*
** XllCountPrimesC
**
** This function counts the number of primes in the specified range and returns the result.
** This function is marked cluster-safe.
**
*/
long WINAPI XllCountPrimesC(long nFrom, long nTo)
{
	return XllCountPrimes(nFrom, nTo);
}

/*
** XllCountPrimes
**
** This function finds the number of primes in the specified range and returns each of them
** in an XLOPER12 object.
**
*/
LPXLOPER12 WINAPI XllFindPrimes(long nFrom, long nTo)
{
	long *gotPrimes = (long*)malloc(0 * sizeof(long));
	if (gotPrimes == NULL)
	{
		return TempStr12(L"ERROR: Cannot alloc primes list");
	}

	int gotTotal = 0;

	for(long iter = nFrom; iter <= nTo; iter++)
	{
		bool notMe = false;
		long root = (long) sqrt((double) iter); //don't need to add one since a perfect root won't round down
		for (long fpr = 2; fpr <= root; fpr++)
		{
			if (iter % fpr == 0)
			{
				notMe = true;
				break;
			}
		}

		if (!notMe)
		{
			gotTotal++;
			long *newPrimes = (long*)realloc((void*)gotPrimes, gotTotal * sizeof(long));
			if (newPrimes == NULL)
			{
				free(gotPrimes);
				return TempStr12(L"ERROR: Cannot realloc primes list");
			}
			gotPrimes = newPrimes;
			gotPrimes[gotTotal - 1] = iter;
		}
	}

	LPXLOPER12 operPrimes = NULL;
	if (gotTotal <= 0)
	{
		free(gotPrimes);
		return TempMissing12();
	}

	operPrimes = new XLOPER12;
	if (operPrimes == NULL)
	{
		free(gotPrimes);
		return TempStr12(L"ERROR: failed to allocate result oper");
	}
	
	operPrimes->xltype = xltypeMulti | xlbitDLLFree;
	operPrimes->val.array.lparray = new XLOPER12[gotTotal];
	if (operPrimes->val.array.lparray == NULL)
	{
		free(operPrimes);
		free(gotPrimes);
		return TempStr12(L"ERROR: failed to allocate result array");
	}

	operPrimes->val.array.rows = 1;
	operPrimes->val.array.columns = gotTotal;
	for (long i = 0; i < gotTotal; i++)
	{
		operPrimes->val.array.lparray[i].xltype = xltypeNum;
		operPrimes->val.array.lparray[i].val.num = (double) (gotPrimes[i]);
	}

	free(gotPrimes);
	return operPrimes;
}