//
// This source code resides at www.jaeckel.org/ImpliedNormalVolatility.7z .
//
// ======================================================================================
// Copyright © 2022 Peter Jäckel.
//
// Permission to use, copy, modify, and distribute this software is freely granted,
// provided that this notice is preserved.
//
// WARRANTY DISCLAIMER
// The Software is provided "as is" without warranty of any kind, either express or implied,
// including without limitation any implied warranties of condition, uninterrupted use,
// merchantability, fitness for a particular purpose, or non-infringement.
// ======================================================================================
//

#include "XLFunctions.h"

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <assert.h>
#include <vector>

#define  STRINGIFY(x)    #x
#define  TOSTRING(x)     STRINGIFY(x)

#if defined( WIN32 ) || defined( _WIN32 ) || defined( WIN64 ) || defined( _WIN64 )
# define sleep(length) Sleep(length*1000);
#else
# include <unistd.h> // For sleep().
#endif

#ifdef _MSC_VER
# define snprintf(buffer,sizeOfBuffer,...) _snprintf_s((buffer),(sizeOfBuffer),_TRUNCATE,__VA_ARGS__)
#endif

#include <string>

// See http://gcc.gnu.org/wiki/Visibility
#ifndef DLL_EXPORT
# if defined( WIN32 ) || defined( _WIN32 ) || defined( WIN64 ) || defined( _WIN64 )
#  define DLL_EXPORT __declspec(dllexport)
# else
#  define DLL_EXPORT __attribute__((visibility("default")))
# endif
#endif

void RaiseException(const char * message){
    fprintf(stderr,message);
    fflush(stderr);
    exit(-1);
}

void RaiseException(const std::string & message){
    RaiseException(message.c_str());
}

typedef int (WINAPI * PFN_EXCEL_CALLBACK)(int xlfn, void* pOperRes, int count, void** ppOpers);
PFN_EXCEL_CALLBACK      pExcel4v = 0;

typedef int (PASCAL *MDCALLBACK12PROC) (int xlfn, int coper, void**rgpxloper12, void* xloper12Res);
MDCALLBACK12PROC pMdCallBack12 = 0;

std::string toString(int x) { char buffer[32]; snprintf(buffer,32,"%d",x); return buffer; }

std::vector<char> xloper_code_vector(int n){
    std::vector<char> codes(n+1,0);
    std::fill(codes.begin(),codes.begin()+n,XLOper::xloper_code());
    return codes;
}

int RegisteredNumberOfFunctions = 0;

int CallbackToExcelV(int xlfn, XLOper &operRes, int count, void**opers) {
    int result = -1;
    switch (XLOper::APIVersion) {
        case XLOper::APIVersion4:
            result = pExcel4v(xlfn, &operRes, count, opers);
            break;
        case XLOper::APIVersion12:
            result = pMdCallBack12(xlfn, count, opers, &operRes);
            break;
        default:
            RaiseException("Excel callback interface not found.");
            break;
    }
    if (0==result && xlfRegister==xlfn && xltypeErr!=operRes.type()) ++RegisteredNumberOfFunctions;
#if defined(VERBOSE_DEBUG_LOG)
    if (xlfRegister==xlfn){
        if (0==result && xltypeErr!=operRes.type())
            printf("Registered function with Excel name '%s' and DLL name '%s'.\n",std::string(*(XLOper*)opers[3]).c_str(),std::string(*(XLOper*)opers[1]).c_str());
        else
            printf("Failed to register function with Excel name '%s' and DLL name '%s'.\n",std::string(*(XLOper*)opers[3]).c_str(),std::string(*(XLOper*)opers[1]).c_str());
        fflush(stdout);
    }
#endif
    return result;
}

// i=1; while [ $i -le 30 ] ; do  echo -n ", const XLOper &o$i=XLOper::Missing" ; i=$[ $i + 1 ] ; done
int CallbackToExcel(int xlfn, XLOper &operRes, int count, const XLOper &o1=XLOper::Missing, const XLOper &o2=XLOper::Missing, const XLOper &o3=XLOper::Missing, const XLOper &o4=XLOper::Missing, const XLOper &o5=XLOper::Missing, const XLOper &o6=XLOper::Missing, const XLOper &o7=XLOper::Missing, const XLOper &o8=XLOper::Missing, const XLOper &o9=XLOper::Missing, const XLOper &o10=XLOper::Missing, const XLOper &o11=XLOper::Missing, const XLOper &o12=XLOper::Missing, const XLOper &o13=XLOper::Missing, const XLOper &o14=XLOper::Missing, const XLOper &o15=XLOper::Missing, const XLOper &o16=XLOper::Missing, const XLOper &o17=XLOper::Missing, const XLOper &o18=XLOper::Missing, const XLOper &o19=XLOper::Missing, const XLOper &o20=XLOper::Missing, const XLOper &o21=XLOper::Missing, const XLOper &o22=XLOper::Missing, const XLOper &o23=XLOper::Missing, const XLOper &o24=XLOper::Missing, const XLOper &o25=XLOper::Missing, const XLOper &o26=XLOper::Missing, const XLOper &o27=XLOper::Missing, const XLOper &o28=XLOper::Missing, const XLOper &o29=XLOper::Missing, const XLOper &o30=XLOper::Missing) {
    // i=1; while [ $i -le 30 ] ; do  echo -n "&o$i," ; i=$[ $i + 1 ] ; done
    const void* opers[] = { &o1,&o2,&o3,&o4,&o5,&o6,&o7,&o8,&o9,&o10,&o11,&o12,&o13,&o14,&o15,&o16,&o17,&o18,&o19,&o20,&o21,&o22,&o23,&o24,&o25,&o26,&o27,&o28,&o29,&o30 };
    return CallbackToExcelV(xlfn, operRes, count, (void**)opers);
}

std::string replace(std::string hay_stack, const char * needle, const std::string& needle_replacement) {
  size_t pos = 0, l=strlen(needle);
  while((pos = hay_stack.find(needle, pos)) != std::string::npos) {
    hay_stack.replace(pos, l, needle_replacement);
    pos += needle_replacement.length();
  }
  return hay_stack;
}

namespace {
  
  // As returned from a call back to Excel's xlGetName function.
  std::string get_file_name() {
    XLOper xDLL, xlRet; // NIL initialised.
    CallbackToExcel(xlGetName, xDLL, 0); // This causes Excel to place memory into xDLL which we don't own - we need to delegate freeing it up back to Excel via the xlFree callback further down.
    std::string file_name(xDLL);
#if defined(VERBOSE_DEBUG_LOG)
    printf("xlGetName returned %s .\n",file_name.c_str()); fflush(stdout);
#endif
    CallbackToExcel(xlFree, xlRet, 1, xDLL);
    new (&xDLL) XLOper(); // Set to NIL without any cleaning up. This is to suppress the destructor of class XLOper attempting to release memory that is here actually managed by Excel.
    return file_name;
  }
  
  std::string strip_path(const std::string & file_name){
    const size_t b = file_name.find_last_of("/\\",std::string::npos,2);
    return std::string::npos == b ? file_name : file_name.substr(b+1);
  }
  
  std::string strip_extension(const std::string & file_name){
    const size_t e = file_name.find_last_of('.');
    return std::string::npos == e ? file_name : file_name.substr(0,e);
  }

#ifdef XL_CATEGORY_NAME
# define THIS_XL_CATEGORY_NAME TOSTRING(XL_CATEGORY_NAME)
#else
# define THIS_XL_CATEGORY_NAME this_xll_base_name()
#endif
  
  // As returned from a call back to Excel's xlGetName function.
  const std::string & this_file_name() { static std::string name = get_file_name(); return name; }
  
  // The file name of the DLL, without path, but including the extension (i.e., ".xll").
  const std::string & this_xll_name() { static std::string name = strip_path(this_file_name()); return name; }

  // The file name of the DLL, without path, and without any extension (i.e., ".xll").
  const std::string & this_xll_base_name() { static std::string name = strip_extension(this_xll_name()); return name; }

  // If XL_CATEGORY_NAME is not defined, the Excel function category is determined dynamically as the XLL's base file name (without path and without extension).
  const std::string & this_xl_category_name() { static std::string name = THIS_XL_CATEGORY_NAME; return name; }

}

void RegisterAllFunctions(){
#if defined(VERBOSE_DEBUG_LOG)
    printf("RegisterAllFunctions().\n"); fflush(stdout);
#endif
    RegisteredNumberOfFunctions = 0;
    const std::string & file_name = this_file_name(), & xll_name = this_xll_name(), & xll_base_name = this_xll_base_name(), & xl_category_name = this_xl_category_name();
    XLOper xDLL(file_name), xlRet/*NIL initialised.*/;
#if defined(VERBOSE_DEBUG_LOG)
    printf("This xll name: %s .\nThis xll base name: %s .\n",xll_name.c_str(),xll_base_name.c_str()); fflush(stdout);
#endif
    std::string message("Registering library "+file_name);
    CallbackToExcel(xlcMessage, xlRet, 2, true, message.c_str());
    const std::vector<XLFunction> &funcs = XLFunction::DeclaredFunctions();
    XLOper xl_function_type("1"), xl_category(xl_category_name), xl_shortcut_text(""), xl_help_topic("");
    for (size_t i = 0 ; i < funcs.size(); ++i){
      XLOper dll_name(funcs[i].DLLName), data_codes(XLOper::replace_xloper_codes(funcs[i].DataCodes.c_str())), xl_name('.'==funcs[i].XLName[0]?xl_category_name+funcs[i].XLName:funcs[i].XLName), arg_names(funcs[i].ArgumentNames), func_desc(replace(funcs[i].FunctionDescription, "@THIS_XLL_NAME@", xll_name));
      XLOper* opers[30] = { &xDLL,&dll_name,&data_codes,&xl_name,&arg_names,&xl_function_type,&xl_category,&xl_shortcut_text,&xl_help_topic,&func_desc,0 };
      int arg_count = (int)funcs[i].ArgumentDescriptions.size();
      std::vector<XLOper> arg_descs(arg_count);
      for (int j=0;j<arg_count;++j)
	new ((opers[10+j]=&arg_descs[j])) XLOper(funcs[i].ArgumentDescriptions[j]);
      CallbackToExcelV(xlfRegister, xlRet, 10+arg_count, (void**)opers);
    }
    message = "Registered "+toString(RegisteredNumberOfFunctions)+(1==RegisteredNumberOfFunctions?" function":" functions")+" from library "+file_name;
#if defined(VERBOSE_DEBUG_LOG)
    printf("%s\n",message.c_str()); fflush(stdout);
#endif
    // For some versions of Excel, the below message never appears - we only see the above "Registering library ...".
    CallbackToExcel(xlcMessage, xlRet, 2, true, message);
    sleep(2); // To give us a chance to notice the message 'Registered ...' at the bottom of the window.
    CallbackToExcel(xlcMessage, xlRet, 1, false);
}

#if UNUSED
std::string dll_file_name(HMODULE hModule){
  char buffer[_MAX_PATH*2] = { 0 };
  ::GetModuleFileNameA(hModule,buffer,sizeof(buffer));
  return buffer;
}
#endif

std::string dll_time_stamp(void*dll_handle){
    IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)dll_handle;
    assert(dosHeader->e_magic == IMAGE_DOS_SIGNATURE);
    const BYTE* base_address = (BYTE*)(dosHeader);
    IMAGE_NT_HEADERS* ntHeaders = (IMAGE_NT_HEADERS*)(base_address + dosHeader->e_lfanew);
    assert(ntHeaders->Signature == IMAGE_NT_SIGNATURE);
    const time_t t = ntHeaders->FileHeader.TimeDateStamp;
    char *s = ctime(&t);
    s[24]=0; // Wipe the \n character.
    return s;
}

std::string ThisDLLsTimeStamp;

extern "C" DLL_EXPORT XLOper* BuildDate() { return toExcel(ThisDLLsTimeStamp); }

// "." means it will be registered to appear in Excel with the same name as here in the code, prefixed with "<this_dll_name>.".
DECLARE_XL_FUNCTION( BuildDate, "." , &xloper_code_vector(1)[0], "", "returns the build date of this DLL (@THIS_XLL_NAME@).", 0 )

extern "C" DLL_EXPORT XLOper* CompilerVersion() {
    const char * s =
#if   defined(_MSC_VER)
        "MSVC " TOSTRING(_MSC_VER);
#elif defined(__GNUC__)
        "GCC " TOSTRING(__GNUC__) "." TOSTRING(__GNUC_MINOR__) "." TOSTRING(__GNUC_PATCHLEVEL__);
#else
        "<unknown>";
#endif
    return toExcel(s);
}

// "." means it will be registered to appear in Excel with the same name as here in the code, prefixed with "<this_dll_name>.".
DECLARE_XL_FUNCTION( CompilerVersion, "." , &xloper_code_vector(1)[0], "", "returns the compiler name and version number that this DLL (@THIS_XLL_NAME@) was built with.", 0 )


extern "C" DLL_EXPORT int ExcelAPIVersion() { return (int)XLOper::APIVersion; }

// "." means it will be registered to appear in Excel with the same name as here in the code, prefixed with "<this_dll_name>.".
// Data type 'J' is a 32 bit integer, see https://docs.microsoft.com/en-us/office/client-developer/excel/data-types-used-by-excel.
DECLARE_XL_FUNCTION( ExcelAPIVersion, "." , "J", "", "returns the Excel API version in use.", 0 )


extern "C" DLL_EXPORT int Bitness() { return 8*(int)sizeof(intptr_t); }

// "." means it will be registered to appear in Excel with the same name as here in the code, prefixed with "<this_dll_name>.".
// Data type 'J' is a 32 bit integer, see https://docs.microsoft.com/en-us/office/client-developer/excel/data-types-used-by-excel.
DECLARE_XL_FUNCTION( Bitness, "." , "J", "", "returns the bitness of this XLL.", 0 )


extern "C" BOOL APIENTRY DllMain( HMODULE hModule, DWORD reason, LPVOID lpReserved ) {
    (void)lpReserved;
    switch (reason) {
        case DLL_PROCESS_ATTACH: {
#if defined(VERBOSE_DEBUG_LOG)
            printf("Process attaching.\n"); fflush(stdout);
#endif
            ThisDLLsTimeStamp = dll_time_stamp(hModule);
#if defined(VERBOSE_DEBUG_LOG)
            printf("DLL build date: %s\n",ThisDLLsTimeStamp.c_str()); fflush(stdout);
#endif
            HMODULE module_handle = ::GetModuleHandle(0);
            if ( 0 != module_handle ){
                pMdCallBack12 = (MDCALLBACK12PROC)::GetProcAddress(module_handle,"MdCallBack12");
                if ( 0 != pMdCallBack12 )
                    XLOper::APIVersion = XLOper::APIVersion12;
#if !defined(PREFER_API_VERSION_4)
                else
#endif
		  {
                    ::SetErrorMode(SEM_FAILCRITICALERRORS); // avoid message box if library not found
                    HMODULE xlcall32_handle = ::LoadLibraryA("xlcall32.dll");
                    ::SetErrorMode(0);
                    if ( 0 != xlcall32_handle ){
		      pExcel4v = (PFN_EXCEL_CALLBACK)::GetProcAddress(xlcall32_handle,"Excel4v");
		      if ( 0 != pExcel4v )
			XLOper::APIVersion = XLOper::APIVersion4;
                    }
		  }
            }
#if defined(VERBOSE_DEBUG_LOG)
	    if ( 0 != pExcel4v )      printf("Found 'Excel4v'.\n");
	    if ( 0 != pMdCallBack12 ) printf("Found 'MdCallBack12'.\n");
	    if ( XLOper::APIVersionUndefined == XLOper::APIVersion ) printf("Found no Excel callback interface.\n");
	    printf("Selected API version: %d.\n",(int)XLOper::APIVersion);
            fflush(stdout);
#endif
            break;
	}
#if defined(VERBOSE_DEBUG_LOG)
        case DLL_THREAD_ATTACH:
	  printf("Thread attaching.\n"); fflush(stdout);
	  break;
        case DLL_THREAD_DETACH:
	  printf("Thread detaching.\n"); fflush(stdout);
	  break;
        case DLL_PROCESS_DETACH:	  
	  printf("Process detaching.\n"); fflush(stdout);
	  break;
#endif
    }
    return TRUE;
}

#if defined(_MSC_VER) && defined( _WIN32 ) && !defined( _WIN64 )
// In order to avoid signatures such as '_xlAutoOpen@0', etc., to show up in the export table of the DLL in this configuration,
// we do not export the respective WINAPI functions via a compiler directive, but instead use a linker directive (see end of this file).
// Note that GCC, by default, also decorates exported WINAPI functions, e.g., 'xlAutoOpen@0', but there we can use the kill-at command
// line instruction to avoid this abomination.
#  define DLL_EXPORT_XLAPI_FUNCTION
#else
#  define DLL_EXPORT_XLAPI_FUNCTION DLL_EXPORT
#endif

#ifdef DLL_EXPORT_WINAPI_XLAUTOOPEN
extern "C" DLL_EXPORT
#else
extern "C" DLL_EXPORT_XLAPI_FUNCTION
#endif
short WINAPI xlAutoOpen() {
    RegisterAllFunctions();
    return 1;
}

extern "C" DLL_EXPORT_XLAPI_FUNCTION short WINAPI xlAutoClose() {
    return 1;
}

extern "C" DLL_EXPORT_XLAPI_FUNCTION void WINAPI xlAutoFree(XLOper4* p) {
#if defined(VERBOSE_DEBUG_LOG)
    printf("xlAutoFree(ptr=0x%p, type=0x%04X)\n",p,p?p->xltype:(WORD)-1);
    fflush(stdout);
#endif
    if (p && (p->xltype & xlbitDLLFree) ) delete p;
}

extern "C" DLL_EXPORT_XLAPI_FUNCTION void WINAPI xlAutoFree12(XLOper12* p) {
#if defined(VERBOSE_DEBUG_LOG)
    printf("xlAutoFree12(ptr=0x%p, type=0x%04X)\n",p,p?p->xltype:-1);
    fflush(stdout);
#endif
    if (p && (p->xltype & xlbitDLLFree) ) delete p;
}

const std::string & AddInManagerInfo(){
  static std::string info = this_xl_category_name()+" Excel add-in.";
#if defined(VERBOSE_DEBUG_LOG)
    printf("AddInManagerInfo() returns \"%s\" .\n",info.c_str()); fflush(stdout);
#endif
  return info;
}

extern "C" DLL_EXPORT_XLAPI_FUNCTION XLOper4* WINAPI xlAddInManagerInfo(XLOper4* pxAction) {
#if defined(VERBOSE_DEBUG_LOG)
    printf("xlAddInManagerInfo(ptr=0x%p, type=0x%04X)\n",pxAction,pxAction?pxAction->xltype:(WORD)-1);
    fflush(stdout);
#endif
   // See https://learn.microsoft.com/en-us/office/client-developer/excel/xladdinmanagerinfo-xladdinmanagerinfo12.
   XLOper4 xIntAction, xDestType(xltypeInt);
   const void* opers[2] = { pxAction, &xDestType };
   pExcel4v(xlCoerce, &xIntAction, 2, (void**)opers);
   if (1 == xIntAction.val.w)
      return (new XLOper4(AddInManagerInfo()))->toExcel();
   return 0;
}

extern "C" DLL_EXPORT_XLAPI_FUNCTION XLOper12* WINAPI xlAddInManagerInfo12(XLOper12* pxAction) {
#if defined(VERBOSE_DEBUG_LOG)
    printf("xlAddInManagerInfo12(ptr=0x%p, type=0x%04X)\n",pxAction,pxAction?pxAction->xltype:-1);
    fflush(stdout);
#endif
   // See https://learn.microsoft.com/en-us/office/client-developer/excel/xladdinmanagerinfo-xladdinmanagerinfo12.
   XLOper12 xIntAction, xDestType(xltypeInt);
   const void* opers[2] = { pxAction, &xDestType };
   pMdCallBack12(xlCoerce, 2, (void**)opers, &xIntAction);
   if (1 == xIntAction.val.w)
      return (new XLOper12(AddInManagerInfo()))->toExcel();
   return 0;
}

#if defined(_MSC_VER) && defined( _WIN32 ) && !defined( _WIN64 )
#  pragma comment(linker, "/EXPORT:xlAutoOpen=_xlAutoOpen@0")
#  pragma comment(linker, "/EXPORT:xlAutoClose=_xlAutoClose@0")
#  pragma comment(linker, "/EXPORT:xlAutoFree=_xlAutoFree@4")
#  pragma comment(linker, "/EXPORT:xlAutoFree12=_xlAutoFree12@4")
#  pragma comment(linker, "/EXPORT:xlAddInManagerInfo=_xlAddInManagerInfo@4")
#  pragma comment(linker, "/EXPORT:xlAddInManagerInfo12=_xlAddInManagerInfo12@4")
#endif
