//
// 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 "XLOper.h"

#include <stdlib.h>
#include <limits.h>
#include <vector>

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

XLOper::APIVersionType XLOper::APIVersion = XLOper::APIVersionUndefined;

#if defined(VERBOSE_DEBUG_LOG)
void log_string_copying(const char*s){     printf("Copied string '%s'\n",s);   fflush(stdout); }
void log_string_copying(const wchar_t*s){ wprintf(L"Copied string '%ls'\n",s); fflush(stdout); }
#endif

#define MAX_BITS_VALUE(AS_TYPE,BITS)     (((AS_TYPE)1)<<(BITS-1)) + ( (((AS_TYPE)1)<<(BITS-1)) - 1 )
#define MAX_UNSIGNED_VALUE(TYPE,AS_TYPE) MAX_BITS_VALUE( AS_TYPE , (sizeof(TYPE)>sizeof(AS_TYPE)?sizeof(AS_TYPE):sizeof(TYPE))*CHAR_BIT )

template <class S, class T> bool Copy(const S* s, size_t length, T*& t){
    if (s) {
        size_t l = length;
        const size_t max_length = MAX_UNSIGNED_VALUE(*t,size_t);
        if (l>max_length) l=max_length;
        t = new T[l+2];
#if defined(_MSC_VER) && (_MSC_VER<=1500) // _MSC_VER==1500 for Microsoft Visual Studio 9.0 (2008), see https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros
        std::copy(s,s+l,stdext::checked_array_iterator<T*>(t + 1, l));
#else
        std::copy(s,s+l,t+1);
#endif
        t[0] = (T)l;
        t[l+1]=0; // Null-termination. Not required but often helpful.
#if defined(VERBOSE_DEBUG_LOG)
        log_string_copying(t+1);
#endif
    }
    return s!=0;
}

template <> bool Copy(const wchar_t* s, size_t length, char*& t){
    if (s) {
        int output_length = WideCharToMultiByte(CP_UTF8, 0, s, (int)length, 0, 0, 0, 0);
        t = new char[output_length+1];
        t[output_length] = 0;
        WideCharToMultiByte(CP_UTF8, 0, s, (int)length, t, output_length, 0, 0);
    }
    return s!=0;
}

XLOper4::XLOper4(const char *s) {     if (Copy(s, strlen(s), val.str)) xltype = (WORD)xltypeStr; }

XLOper4::XLOper4(const wchar_t * s) { if (Copy(s, wcslen(s), val.str)) xltype = (WORD)xltypeStr; }

XLOper4::XLOper4(const std::string & s) { if (Copy(s.data(), s.length(), val.str)) xltype = (WORD)xltypeStr; }

XLOper4::XLOper4(int i) {
    xltype = xltypeInt;
    val.w = (short)i;
}

XLOper4::XLOper4(double x) {
    xltype = xltypeNum;
    val.num = x;
}

XLOper4::XLOper4(bool b) {
    xltype = xltypeBool;
    val.xbool = b;
}

XLOper4::XLOper4(XLType xlOperType) {
    xltype = (WORD)xlOperType;
}

XLOper4::XLOper4() {
    xltype = (WORD)XLTypeNil;
}

XLOper12::XLOper12(const char *s) {     if (Copy(s, strlen(s), val.str)) xltype = xltypeStr; }

XLOper12::XLOper12(const wchar_t * s) { if (Copy(s, wcslen(s), val.str)) xltype = xltypeStr; }

XLOper12::XLOper12(const std::string & s) { if (Copy(s.data(), s.length(), val.str)) xltype = xltypeStr; }

XLOper12::XLOper12(int i) {
    xltype = xltypeInt;
    val.w = i;
}

XLOper12::XLOper12(double x) {
    xltype = xltypeNum;
    val.num = x;
}

XLOper12::XLOper12(bool b) {
    xltype = xltypeBool;
    val.xbool = b;
}

XLOper12::XLOper12(XLType xlOperType) {
    xltype = xlOperType;
}

XLOper12::XLOper12() {
    xltype = XLTypeNil;
}

namespace {

  const char* type_name(int type) {
    switch (type & ~(xlbitDLLFree|xlbitXLFree)) {
       case xltypeNum:     return "number";
       case xltypeStr:     return "string";
       case xltypeBool:    return "boolean";
       case xltypeErr:     return "#err";
       case xltypeMulti:   return "array";
       case xltypeMissing: return "missing";
       case xltypeNil:     return "nil";
       case xltypeInt:     return "integer";
       case xltypeBigData: return "binary";
    }
    return "<unknown>";
  }

  inline const XLOper4*  pxloper4(const XLOper&o){ return (const XLOper4*)(&o.o4); }
  inline const XLOper12* pxloper12(const XLOper&o){ return (const XLOper12*)(&o.o12); }

  int xtype(const XLOper4& o){  return o.xltype; }
  int xtype(const XLOper12& o){ return o.xltype; }
  int xtype(const XLOper& o){
    switch (XLOper::APIVersion) {
      case XLOper::APIVersion4:  return xtype(*pxloper4(o));
      case XLOper::APIVersion12: return xtype(*pxloper12(o));
    }
    return false;
  }

  bool xbool(const XLOper4& o){  return o.val.xbool != 0; }
  bool xbool(const XLOper12& o){ return o.val.xbool != 0; }
  bool xbool(const XLOper& o){
    switch (XLOper::APIVersion) {
      case XLOper::APIVersion4:  return xbool(*pxloper4(o));
      case XLOper::APIVersion12: return xbool(*pxloper12(o));
    }
    return false;
  }

  int xint(const XLOper4& o){    return o.val.w; }
  int xint(const XLOper12& o){   return o.val.w; }
  int xint(const XLOper& o){
    switch (XLOper::APIVersion) {
      case XLOper::APIVersion4:  return xint(*pxloper4(o));
      case XLOper::APIVersion12: return xint(*pxloper12(o));
    }
    return -1;
  }

#if defined(VERBOSE_DEBUG_LOG)
  const char* xloper_version(const XLOper4&){ return "4"; }
  const char* xloper_version(const XLOper12&){ return "12"; }
  const char* xloper_version(const XLOper&){ return ""; }
#endif

  std::string to_string(double x) {
    char buffer[32];
    snprintf(buffer, sizeof(buffer), "%0.17g", x );
    return buffer;
  }

  std::string string_from_wcs(const wchar_t* wcs, size_t length) {
    if (wcs) {
        int output_length = WideCharToMultiByte(CP_UTF8, 0, wcs, (int)length, 0, 0, 0, 0);
        char * buffer = new char[output_length+1];
        buffer[output_length] = 0;
        WideCharToMultiByte(CP_UTF8, 0, wcs, (int)length, buffer, output_length, 0, 0);
        std::string result(buffer,output_length);
        delete[] buffer;
        return result;
    }
    return std::string();
  }

  // No safety checks here.
  std::string xstring(const XLOper4& o){  return std::string(o.val.str+1,(size_t)(unsigned char)o.val.str[0]); }
  std::string xstring(const XLOper12& o){ return string_from_wcs(o.val.str+1, (size_t)o.val.str[0]);           }
  std::string xstring(const XLOper& o){
    switch (XLOper::APIVersion) {
      case XLOper::APIVersion4:  return xstring(*pxloper4(o));
      case XLOper::APIVersion12: return xstring(*pxloper12(o));
    }
    return "unsupported API version ("+to_string((double)XLOper::APIVersion)+")";
  }

  // Used for XLOper4, XLOper12, and XLOper
  template <class X> std::string to_string(const X & o) {
    int xltype = xtype(o);
    switch (xltype & ~(xlbitDLLFree|xlbitXLFree)) {
      case xltypeNum:     return to_string(*(const double*)(&o)); // field val.num is at offset 0 and of length 8 in API version 4 and 12 both for 32 bit and 64 bit.
      case xltypeStr:     return xstring(o);
      case xltypeInt:     return to_string((double)xint(o));
      case xltypeBool:    return xbool(o) ? "TRUE" : "FALSE";
      case xltypeErr:
      case xltypeMulti:
      case xltypeMissing:
      case xltypeNil:
      case xltypeBigData:
           return '<'+std::string(type_name(xltype))+'>';
    }
    return "unsupported XLOper type";
  }  
  
  // Used for XLOper4 and XLOper12
  template <class X> void free_xloper_content(X *p) {
#if defined(VERBOSE_DEBUG_LOG)
    printf("Freeing up XLOper%s (ptr=0x%p, type=0x%04X [%s]) of size %d with content '%s'\n",xloper_version(*p),p,p->xltype,type_name(p->xltype),sizeof(*p),to_string(*p).c_str());
    fflush(stdout);
#endif
    // XLOpers that are populated by Excel via callbacks into it may contain secondary memory that we cannot release.
    // Excel signals this by setting the xlbitXLFree bit on such objects. See https://msdn.microsoft.com/en-us/library/office/bb687840.aspx.
    switch (p->xltype & ~(xlbitDLLFree|xlbitXLFree)) {
        case xltypeStr:
            if (p->val.str) delete[] p->val.str; p->val.str = 0;
            break;
        case xltypeMulti: // This is recursive
            if (p->val.array.lparray) delete[] (X*)p->val.array.lparray; p->val.array.lparray = 0;
            break;
        default:
            break;
    }
    // The following statement is a safety precaution.
    p->xltype = xltypeNil;
  }

}

XLOper4::~XLOper4(){ free_xloper_content(this); }

XLOper4* XLOper4::toExcel(){
    if (0!=this)
        this->xltype |= (WORD)xlbitDLLFree;
    return this;
}

XLOper12::~XLOper12(){ free_xloper_content(this); }

XLOper12* XLOper12::toExcel(){
    if (0!=this)
        this->xltype |=       xlbitDLLFree;
    return this;
}

template <class A> void Construct(XLOper *p, const A& a) {
    switch (XLOper::APIVersion) {
        case XLOper::APIVersion4:
            new ((XLOper4*)&(p->o4)) XLOper4(a);
            break;
        case XLOper::APIVersion12:
            new ((XLOper12*)&(p->o12)) XLOper12(a);
            break;
        default:
            memset(&p,-1,sizeof(*p));
            break;
    }
}


XLOper::XLOper(const char *s) { Construct(this,s); }

XLOper::XLOper(const wchar_t *s) { Construct(this,s); }

XLOper::XLOper(const std::string&s) { Construct(this,s); }

XLOper::XLOper(int i) { Construct(this,i); }

XLOper::XLOper(double x) { Construct(this,x); }

XLOper::XLOper(bool b) { Construct(this,b); }

XLOper::XLOper(XLType t) { Construct(this,t); }

XLOper::XLOper() { Construct(this,XLTypeNil); }

XLOper::~XLOper() {
    switch (APIVersion) {
        case APIVersion4:
            ((XLOper4*)(&o4))->~XLOper4();
            break;
        case APIVersion12:
            ((XLOper12*)(&o12))->~XLOper12();
            break;
    }
}

XLType XLOper::type() const {
    switch (APIVersion) {
        case APIVersion4:  return (XLType)o4.xltype;
        case APIVersion12: return (XLType)o12.xltype;
    }
    return (XLType)-1;
}

const char* XLOper::type_name() const { return ::type_name(type()); }

XLOper* XLOper::toExcel(){
#if defined(VERBOSE_DEBUG_LOG)
    printf("toExcel() on XLOper of type=0x%04X (%s) of size %d with content '%s'\n",type(),type_name(),sizeof(*this),std::string(*this).c_str());
    fflush(stdout);
#endif
    if (0!=this)
        switch (XLOper::APIVersion) {
            case APIVersion4:  o4.xltype  |= (WORD)xlbitDLLFree; break;
            case APIVersion12: o12.xltype |=       xlbitDLLFree; break;
    }
    return this;
}

XLOper::operator std::string() const { return to_string(*this); }

char XLOper::xloper_code(){ // 'P' in API version 4, and 'Q' in API version 12.
    switch (XLOper::APIVersion) {
        case XLOper::APIVersion4:  return 'P';
        case XLOper::APIVersion12: return 'Q';
    }
    return '?';
}

std::string XLOper::replace_xloper_codes(const char *codes){ // '?' -> 'P' in API version 4, and 'Q' in API version 12.
    int n = (int)strlen(codes);
    std::vector<char> s(n+1);
    const char o = xloper_code();
    for (int i = 0 ; i < n ; ++i)
        s[i] = '?' == codes[i] ? o : codes[i];
    return &s[0];
}

XLOper missing() {
    //
    // The XLOper4  type member 'xltype' is at offset 8 in 32-bit and at offset 16 in 64 bit, and of length 2 [all in bytes].
    // The XLOper12 type member 'xltype' is at offset 24 both in 32-bit and in 64 bit, and of length 4.
    // We can therefore set both of these fields to value 'xltypeMissing', which works for both API versions.
    //
    XLOper missing;
    missing.o4.xltype = (WORD)XLTypeMissing;
    missing.o12.xltype = XLTypeMissing;
    return missing;
}

const XLOper XLOper::Missing = missing();

