//
// This source code resides at www.jaeckel.org/StrikeFromVolatilityAndDeltaWithPremium.7z .
//
// ======================================================================================
// Copyright © 2020 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)

static const char * const theNameOfThisXLL = "StrikeFromForwardDelta";

#define XLL_NAME_STRING theNameOfThisXLL

#ifndef XLL_NAME_STRING
#error You must define an XLL_NAME_STRING.
#endif

#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;

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);
}

void RegisterAllFunctions(){
    RegisteredNumberOfFunctions = 0;
    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 lib_name(xDLL);
    std::string message("Registering library "+lib_name);
    CallbackToExcel(xlcMessage, xlRet, 2, true, message.c_str());
    std::string xll_name(XLL_NAME_STRING);
    const std::vector<XLFunction> &funcs = XLFunction::DeclaredFunctions();
    XLOper xl_function_type("1"), xl_category(XLL_NAME_STRING), 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]?XLL_NAME_STRING+funcs[i].XLName:funcs[i].XLName), arg_names(funcs[i].ArgumentNames), func_desc(funcs[i].FunctionDescription);
        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);
    }
    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.
    message = "Registered "+toString(RegisteredNumberOfFunctions)+(1==RegisteredNumberOfFunctions?" function":" functions")+" from library "+lib_name;
#if defined(VERBOSE_DEBUG_LOG)
    printf("%s\n",message.c_str()); fflush(stdout);
#endif
    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);
}


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); }

DECLARE_XL_FUNCTION( BuildDate, ".BuildDate" , &xloper_code_vector(1)[0], "", "returns the build date of this DLL ("+std::string(XLL_NAME_STRING)+").")

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);
}

DECLARE_XL_FUNCTION( CompilerVersion, ".CompilerVersion" , &xloper_code_vector(1)[0], "", "returns the compiler name and version number that this DLL ("+std::string(XLL_NAME_STRING)+") was built with." )

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("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;
                else {
                    ::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)
            switch (XLOper::APIVersion) {
        case XLOper::APIVersion4:  printf("Found 'Excel4v'.\n");                   break;
        case XLOper::APIVersion12: printf("Found 'MdCallBack12'.\n");              break;
        default:                   printf("Found no Excel callback interface.\n"); break;
            }
            fflush(stdout);
#endif
            break;
                                 }
    }
    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;
}

#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")
#endif
