#ifndef __linux__
#ifndef __APPLE__
//#include "stdafx.h"
#include <Tchar.h>
#endif
#endif

#include "saxonc/SaxonProcessor.h"
#include "saxonc/XdmArray.h"
#include "saxonc/XdmAtomicValue.h"
#include "saxonc/XdmFunctionItem.h"
#include "saxonc/XdmItem.h"
#include "saxonc/XdmMap.h"
#include "saxonc/XdmNode.h"
#include "saxonc/XdmValue.h"

//#define DEBUG

#ifdef DEBUG
#include <signal.h>
#endif

#include <stdio.h>

#if defined MEM_DEBUG

void *newImpl(std::size_t sz, char const *file, int line) {
  static int counter{};
  void *ptr = std::malloc(sz);
  std::cerr << "SaxonC Mem test: " << file << ": " << line << " " << ptr
            << std::endl;
  myAlloc.push_back(ptr);
  return ptr;
}

void *operator new(std::size_t sz, char const *file, int line) {
  return newImpl(sz, file, line);
}

void *operator new[](std::size_t sz, char const *file, int line) {
  return newImpl(sz, file, line);
}

void operator delete(void *ptr) noexcept {
  auto ind = std::distance(myAlloc.begin(),
                           std::find(myAlloc.begin(), myAlloc.end(), ptr));
  myAlloc[ind] = nullptr;
  std::free(ptr);
}

void operator delete(void *ptr, std::size_t) noexcept {
  auto ind = std::distance(myAlloc.begin(),
                           std::find(myAlloc.begin(), myAlloc.end(), ptr));
  std::cerr << "SaxonC Mem test deleting: " << ptr << std::endl;
  myAlloc[ind] = nullptr;
  std::free(ptr);
}

#define new new (__FILE__, __LINE__)




void SaxonProcessor::getInfo() {

  std::cout << std::endl;

  std::cout << "Allocation: " << std::endl;
  for (auto i : myAlloc) {
    if (i != nullptr)
      std::cout << " " << i << std::endl;
  }

  std::cout << std::endl;
}

#endif



// jobject cpp;
const char *failure;
sxnc_environment *SaxonProcessor::sxn_environ = 0;
int SaxonProcessor::jvmCreatedCPP = 0;

bool SaxonProcessor::exceptionOccurred() {
  return (bool)j_checkForException(sxn_environ->thread);
}

const char *SaxonProcessor::checkException() {
  const char *message = nullptr;
  message = checkForException(sxn_environ);
  return message;
}

/*SaxonApiException *SaxonProcessor::checkAndCreateException() {
    SaxonProcessor::attachCurrentThread();
    if (SaxonProcessor::sxn_environ->env->ExceptionCheck()) {
        SaxonApiException *exception =
checkForExceptionCPP(SaxonProcessor::sxn_environ->env, cppClass, nullptr);
#ifdef DEBUG
        SaxonProcessor::sxn_environ->env->ExceptionDescribe();
#endif
        return exception;
    }
    return nullptr;
}
*/
const char *SaxonProcessor::getErrorMessage() {
  if (exception == nullptr) {
    return nullptr;
  }
  return exception->getMessage();
}

void SaxonProcessor::exceptionClear() {

  if (exception != nullptr) {
    delete exception;
    exception = nullptr;
  }
  j_clearException(sxn_environ->thread);
}

/*SaxonProcessor::SaxonProcessor():  {
    licensei = false;
    SaxonProcessor(false);
}*/

SaxonApiException *SaxonProcessor::checkForExceptionCPP(
    /*JNIEnv *env, jclass callingClass, jobject callingObject*/) {

  /* if (env->ExceptionCheck()) {
       std::string result1 = "";
       std::string errorCode = "";
       jthrowable exc = env->ExceptionOccurred();

#ifdef DEBUG
       env->ExceptionDescribe();
#endif
       jclass exccls(env->GetObjectClass(exc));
       jclass clscls(env->FindClass("java/lang/Class"));

       jmethodID getName(env->GetMethodID(clscls, "getName",
"()Ljava/lang/String;")); jstring
name(static_cast<jstring>(env->CallObjectMethod(exccls, getName))); char const
*utfName(env->GetStringUTFChars(name, 0)); result1 = (std::string(utfName));
       env->ReleaseStringUTFChars(name, utfName);

       jmethodID getMessage(env->GetMethodID(exccls, "getMessage",
"()Ljava/lang/String;")); if (getMessage) {

           jstring message((jstring) (env->CallObjectMethod(exc, getMessage)));
           char const *utfMessage = nullptr;
           if (!message) {
               utfMessage = "";
               return nullptr;
           } else {
               utfMessage = (env->GetStringUTFChars(message, 0));
           }
           if (utfMessage != nullptr) {
               result1 = (result1 + " : ") + utfMessage;
           }

           env->ReleaseStringUTFChars(message, utfMessage);

           std::string prefix = "net.sf.saxon.s9api.SaxonApiException";

           if (result1.compare(0, prefix.length(),
"net.sf.saxon.s9api.SaxonApiException") == 0) {

               jclass
saxonApiExceptionClass(env->FindClass("net/sf/saxon/s9api/SaxonApiException"));
               static jmethodID lineNumID = nullptr;
               lineNumID = env->GetMethodID(saxonApiExceptionClass,
"getLineNumber", "()I"); static jmethodID ecID = nullptr; ecID =
env->GetMethodID(saxonApiExceptionClass, "getErrorCode",
"()Lnet/sf/saxon/s9api/QName;");

               static jmethodID esysID = nullptr;
               esysID = env->GetMethodID(saxonApiExceptionClass, "getSystemId",
"()Ljava/lang/String;");

               jobject errCodeQName = (jobject) (env->CallObjectMethod(exc,
ecID));

               jstring errSystemID = (jstring) (env->CallObjectMethod(exc,
esysID));

               int linenum = env->CallIntMethod(exc, lineNumID);

               jclass qnameClass(env->FindClass("net/sf/saxon/s9api/QName"));
               static jmethodID qnameStrID = nullptr;
               qnameStrID = env->GetMethodID(qnameClass, "toString",
"()Ljava/lang/String;");

               jstring qnameStr = nullptr;

               if(errCodeQName) {
                   qnameStr = (jstring) (env->CallObjectMethod(errCodeQName,
qnameStrID));
               }

               SaxonApiException *saxonExceptions = new
SaxonApiException(result1.c_str(), (qnameStr ? env->GetStringUTFChars(qnameStr,
                                                                                                             0)
                                                                                    : nullptr),
                                                                          (errSystemID
? env->GetStringUTFChars( errSystemID, 0) : nullptr), linenum);

               if (errCodeQName) {
                   env->DeleteLocalRef(errCodeQName);
               }
               if (errSystemID) {
                   env->DeleteLocalRef(errSystemID);
               }
               if (qnameStr) {
                   env->DeleteLocalRef(qnameStr);
               }

               if (message) {
                   env->DeleteLocalRef(message);
               }
               env->ExceptionClear();
               return saxonExceptions;
           }
       }
       SaxonApiException *saxonExceptions = new
SaxonApiException(result1.c_str());

       env->ExceptionClear();
       return saxonExceptions;
   } TODO remove comment*/
  return nullptr;
}

SaxonProcessor::SaxonProcessor(int64_t proc, bool l) {
  cwd = "";
  licensei = l;
  exception = nullptr;
  procRef = proc;
}

SaxonProcessor::SaxonProcessor() { initialize(false); }

SaxonProcessor::SaxonProcessor(bool l) { initialize(l); }

void SaxonProcessor::initialize(bool l) {
  cwd = "";
  licensei = l;
  exception = nullptr;
  procRef = SXN_UNSET;
  if (SaxonProcessor::jvmCreatedCPP == 0) {
    SaxonProcessor::jvmCreatedCPP = 1;
    SaxonProcessor::sxn_environ =
        new sxnc_environment;

    SaxonProcessor::sxn_environ->isolate = NULL;
	SaxonProcessor::sxn_environ->mainthread = NULL;
	SaxonProcessor::sxn_environ->thread = NULL;


    /*
     * InitializeGraalvm run-time.
     */
    create_graalvm_isolate(SaxonProcessor::sxn_environ);
#ifdef DEBUG
    setDebugMode(SaxonProcessor::sxn_environ->thread, 1);
#endif
  } else {
#ifdef DEBUG
    std::cerr << "SaxonProc constructor: jvm exists! jvmCreatedCPP="
              << jvmCreatedCPP << std::endl;
#endif
  }
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  procRef =
      createSaxonProcessor2(thread, (int)l);

  if (procRef == SXN_EXCEPTION) {
    throw SaxonApiException();
  }

}

void SaxonProcessor::attachCurrentThread() {
#ifdef DEBUG
  std::cerr << "attachCurrentThread called" << std::endl;
#endif
  int result = attach_graalvm_thread(SaxonProcessor::sxn_environ);
#ifdef DEBUG
  if (result != 0) {
    std::cerr << "attachCurrentThread failed or already in the current thread"
              << std::endl;
  }
#endif
}

void SaxonProcessor::detachCurrentThread() {

#ifdef DEBUG
  std::cerr << "detachCurrentThread called" << std::endl;
  int result =
#endif
  detach_graalvm_thread(SaxonProcessor::sxn_environ);

#ifdef DEBUG
  if (result != 0) {
    std::cerr << "detachCurrentThread failed" << std::endl;
  }
#endif
}

graal_isolatethread_t * SaxonProcessor::getOrAttachCurrentThread(sxnc_environment * sxn) {

  graal_isolatethread_t * thread = get_or_attach_thread(sxn);
  if (thread == nullptr) {
    if (getenv("SAXONC_DEBUG_FLAG")) {
      fprintf(stdout, "getOrAttachCurrentThread error - returned main thread\n");
      fflush(stdout);
    }
    return SaxonProcessor::sxn_environ->mainthread;//throw SaxonApiException("Could not attach to the current thread");
  }
  return thread;

}



void SaxonProcessor::attachCurrentThread(sxnc_environment * sxn) {
#ifdef DEBUG
  std::cerr << "detachCurrentThread called" << std::endl;
  int result =
#endif
  attach_graalvm_thread(sxn);
  if (!sxn->mainthread) {
    sxn->mainthread = SaxonProcessor::sxn_environ->mainthread;
  }
  if (!sxn->thread) {
    sxn->thread = SaxonProcessor::sxn_environ->thread;
  }
#ifdef DEBUG
  if (result != 0) {
    std::cerr << "attachCurrentThread failed or already in the current thread"
              << std::endl;
  }
#endif

}

/**
 * Detach JVM from the current thread
 */
void SaxonProcessor::detachCurrentThread(sxnc_environment * sxn) {
  if (getenv("SAXONC_DEBUG_FLAG")) {
    fprintf(stdout, "detach: Cur thread pointer %p main thread=%p\n", (void *)sxn->thread, (void *)sxn->mainthread);
    fflush(stdout);
  }
  if (((void *)(sxn)->thread) == ((void *)(SaxonProcessor::sxn_environ)->mainthread)) {
    if (getenv("SAXONC_DEBUG_FLAG")) {
      fprintf(stdout, "graal_detach_thread error - already in main thread\n");
      fflush(stdout);
    }
    return;
  }
  if (graal_detach_thread(sxn->thread) != 0) {
    // if (getenv("SAXONC_DEBUG_FLAG")) {
    fprintf(stdout, "graal_detach_thread error\n");
    fflush(stdout);
    //}

  }

}



SaxonProcessor::SaxonProcessor(const char *configFile) {
  cwd = "";

  licensei = true;
  exception = nullptr;
  procRef = SXN_UNSET;
  if (SaxonProcessor::jvmCreatedCPP == 0) {
    SaxonProcessor::jvmCreatedCPP = 1;
    SaxonProcessor::sxn_environ =
        new sxnc_environment; //(sxnc_environment
                              //*)malloc(sizeof(sxnc_environment));

    /*
     * InitializeGraalvm run-time.
     */

    create_graalvm_isolate(SaxonProcessor::sxn_environ);
  } else {
#ifdef DEBUG
    std::cerr << "SaxonProc constructor: jvm exists! jvmCreatedCPP="
              << jvmCreatedCPP << std::endl;
#endif
  }
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  procRef = createSaxonProcessorWithConfigurationFile(thread, (char *)configFile);
  if (procRef == SXN_EXCEPTION) {
    throw SaxonApiException();
    //"Failed to create SaxonProcessor object - with configuration file");
  }

#ifdef DEBUG
  setDebugMode(thread, 1);
#endif
}

SaxonProcessor::~SaxonProcessor() {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  clearConfigurationProperties();
  if (getenv("SAXONC_DEBUG_FLAG")) {
    std::cerr << "~SaxonProcessor destructor:"
              << " proc ref=" << ((long)procRef) << std::endl;
  }
  if (procRef != SXN_UNSET) {
    if (getenv("SAXONC_DEBUG_FLAG")) {
      std::cerr << "~SaxonProcessor destructor called with destroy:"
                << " ob ref=" << (this) << std::endl;
    }
    // SaxonProcessor::sxn_environ->env->DeleteGlobalRef(proc);
    j_handles_destroy(thread, (void *)procRef);
    procRef = SXN_UNSET;
  }
  if (!versionStr.empty()) {

    versionStr.clear();
  }
}

bool SaxonProcessor::isLicensed() {
  if (!licensei) {
    return false;
  }
  if (procRef < 0) {
      return false;
  }
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  return j_isLicensed(thread, (void *)procRef);
}


const char * SaxonProcessor::getSaxonEdition() {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  return j_c_getSaxonEdition(thread, (void *)procRef);
}


bool SaxonProcessor::isSchemaAwareProcessor() {
  if (!licensei) {
    return false;
  } else {

    if (procRef < 0) {
      return false;
    }
    graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
    licensei = j_isSchemaAware(thread, (void *)procRef);
    return licensei;
  }
}

void SaxonProcessor::applyConfigurationProperties() {

 /* if (configProperties.size() > 0) {
    int size = configProperties.size();
    int64_t processorDataRef = createProcessorDataWithCapacity(
        SaxonProcessor::sxn_environ->thread, size);

    int i = 0;
    std::map<std::string, std::string>::iterator iter =
        configProperties.begin();
    for (iter = configProperties.begin(); iter != configProperties.end();
         ++iter, i++) {
      addProcessorPropertyPair(
          SaxonProcessor::sxn_environ->thread, (void *)processorDataRef,
          (char *)(iter->first).c_str(), (char *)(iter->second).c_str());
    }

    int resultsi = j_applyPropertiesToConfiguration(SaxonProcessor::sxn_environ->thread, (void *)procRef,
                           (void *)processorDataRef);

    if (resultsi == SXN_EXCEPTION) {
      throw SaxonApiException();
    }

  }*/
}

XdmValue *SaxonProcessor::makeXdmValueFromRef(int64_t results) {
  if (results > 0) {
    graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
    XdmValue *value = nullptr;
    int typeRef = j_getXdmObjectType(thread, (void *)results);
//#ifdef DEBUG
    if (getenv("SAXONC_DEBUG_FLAG")) {
      std::cerr << "C++ makeXdmValueFromRef - ref="<<((long)results)<<", typeRef= " << typeRef<< std::endl;
    }
//#endif
    if (typeRef == SXN_EXCEPTION) {

      throw SaxonApiException();
    }
    XDM_TYPE xdm_type = static_cast<XDM_TYPE>(typeRef);
    if (xdm_type == XDM_ATOMIC_VALUE) {
      value = new XdmValue();
      value->addXdmItemFromUnderlyingValue(new XdmAtomicValue(results));

    } else if (xdm_type == XDM_NODE) {
      value = new XdmValue();
      value->addXdmItemFromUnderlyingValue(new XdmNode(results));

    } else if (xdm_type == XDM_MAP) {
      value = new XdmValue();
      value->addXdmItemFromUnderlyingValue(new XdmMap(results));

    } else if (xdm_type == XDM_ARRAY) {
      value = new XdmValue();
      value->addXdmItemFromUnderlyingValue(new XdmArray(results));

    } else if (xdm_type == XDM_FUNCTION_ITEM) {
      value = new XdmValue();
      value->addXdmItemFromUnderlyingValue(new XdmFunctionItem(results));
    } else if (typeRef == XDM_EMPTY) {
      // Empty Sequence detected.
      // TODO Maybe we should create an EmptySequence object
      j_handles_destroy(thread, (void *)results);
      results = SXN_UNSET;
      return nullptr;

    } else {
      value = new XdmValue(results, true);
      j_handles_destroy(thread, (void *)results);
      results = SXN_UNSET;
    }

    return value;
  } else if (results == SXN_EXCEPTION) {
    throw SaxonApiException();
  }

  return nullptr;
}

 void SaxonProcessor::destroyHandle(int64_t handleRef){

  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
   j_handles_destroy(thread,(void *)handleRef);
 }

XdmItem *SaxonProcessor::makeXdmItemFromRef(int64_t results) {
  if (results > 0) {
    graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
    int typeRef = j_getXdmObjectType(thread, (void *)results);
    if (getenv("SAXONC_DEBUG_FLAG")) {
      std::cerr << "C++ makeXdmItemFromRef - ref="<<((long)results)<<", typeRef= " << typeRef<< std::endl;
    }

    XDM_TYPE xdm_type = static_cast<XDM_TYPE>(typeRef);
    XdmItem *xdmItem = nullptr;
    if (typeRef == SXN_EXCEPTION) {

      throw SaxonApiException();
    }
    if (xdm_type == XDM_ATOMIC_VALUE) {
      xdmItem = new XdmAtomicValue(results);

    } else if (xdm_type == XDM_NODE) {
      xdmItem = new XdmNode(results);

    } else if (xdm_type == XDM_MAP) {
      xdmItem = new XdmMap(results);

    } else if (xdm_type == XDM_ARRAY) {
      xdmItem = new XdmArray(results);

    } else if (xdm_type == XDM_FUNCTION_ITEM) {
      xdmItem = new XdmFunctionItem(results);
    } else if (xdm_type == XDM_EMPTY) {
      // Empty Sequence detected.
      // TODO Maybe we should create an EmptySequence object
      j_handles_destroy(thread, (void *)results);
      results = SXN_UNSET;
      return nullptr;

    } else {
      throw SaxonApiException(
          "Internal Error: Cannot create XdmItem from reference");
    }

    return xdmItem;
  } else if (results == SXN_EXCEPTION) {
    throw SaxonApiException();
  }

  return nullptr;
}

int64_t SaxonProcessor::createJArray(XdmValue **values, int length) {
  if (length > 0) {
    graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
    int64_t processorDataRef = createProcessorDataWithCapacity(thread, length);

    for (int i = 0; i < length; i++) {

      addProcessorValue(thread,(void *)processorDataRef, (void *)(values[i])->getUnderlyingValue());
    }

    return processorDataRef;

  } else {
    return SXN_UNSET;
  }
}

int64_t SaxonProcessor::createParameterJArray(
    std::map<std::string, XdmValue *> parameters,
    std::map<std::string, std::string> properties, int additions) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int size = (int) parameters.size() + properties.size() + additions;
#ifdef DEBUG
  if (getenv("SAXONC_DEBUG_FLAG")) {
    std::cerr << "Properties size: " << properties.size() << std::endl;
    std::cerr << "Parameter size: " << parameters.size() << std::endl;
  }
#endif

  if (size > 0) {
    int64_t processorDataRef = createProcessorDataWithCapacity(thread, size);

    int i = 0;
    for (std::map<std::string, XdmValue *>::iterator iter = parameters.begin();
         iter != parameters.end(); ++iter, i++) {

#ifdef DEBUG
      std::cerr << "map 1" << std::endl;
      std::cerr << "iter->first" << (iter->first).c_str() << std::endl;
#endif

      /*#ifdef DEBUG
                  std::string s1 = typeid(iter->second).name();
                  std::cerr<<"Type of itr:"<<s1<<std::endl;

                  if((iter->second) == nullptr) {std::cerr<<"iter->second is
      nullptr"<<std::endl; } else { std::cerr<<"getting underlying
      value"<<std::endl; int64_t xx = (iter->second)->getUnderlyingValue();

                  if(xx == nullptr) {
                      std::cerr<<"value failed"<<std::endl;
                  } else {

                      std::cerr<<"Type of value:"<<((long)xx)<<std::endl;
                  }
                  if((iter->second)->getUnderlyingValue() == SXN_UNSET) {
                      std::cerr<<"(iter->second)->getUnderlyingValue() is
      nullptr"<<std::endl;
                  }}
      #endif  */

      if ((iter->second) == nullptr) {
        continue;
      }

      addProcessorDataPair(thread,
                           (void *)processorDataRef,
                           (char *)(iter->first).c_str(),
                           ((iter->second)->getUnderlyingValue() == SXN_UNSET ? (void *)nullptr : (void *)(iter->second)->getUnderlyingValue()));
    }

    for (std::map<std::string, std::string>::iterator iter = properties.begin();
         iter != properties.end(); ++iter, i++) {
      addProcessorPropertyPair(thread, (void *)processorDataRef,
          (char *)(iter->first).c_str(), (char *)(iter->second).c_str());
    }

    return processorDataRef;

  } else {
    return SXN_UNSET;
  }
}

int64_t SaxonProcessor::createParameterJArray2(std::map<std::string, XdmValue *> parameters) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int size = (int) parameters.size();
  int64_t processorDataRef = createProcessorDataWithCapacity(thread, size);

#ifdef DEBUG
  std::cerr << "Parameter size: " << parameters.size() << std::endl;
#endif
  if (size > 0) {

    int i = 0;
    for (std::map<std::string, XdmValue *>::iterator iter = parameters.begin();
         iter != parameters.end(); ++iter, i++) {

#ifdef DEBUG
      std::cerr << "map 1" << std::endl;
      std::cerr << "iter->first" << (iter->first).c_str() << std::endl;

#endif

      addProcessorDataPair(thread,(void *)processorDataRef,
                           (char *)(iter->first).c_str(),
                           (void *)(iter->second)->getUnderlyingValue());
    }

    return processorDataRef;

  } else {
    return SXN_UNSET;
  }
}

SaxonProcessor &SaxonProcessor::operator=(const SaxonProcessor &other) {
  procRef = other.procRef;
  cwd = other.cwd;

  parameters = other.parameters;
  //configProperties = other.configProperties;
  licensei = other.licensei;
  exception = other.exception;
  return *this;
}

SaxonProcessor::SaxonProcessor(const SaxonProcessor &other) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  procRef = j_copy_handles(thread, (void *)other.procRef);
  cwd = other.cwd;

  //configProperties = other.configProperties;
  licensei = other.licensei;
  std::map<std::string, XdmValue *>::const_iterator paramIter =
      other.parameters.begin();
  while (paramIter != other.parameters.end()) {

    XdmValue *valuei = paramIter->second;
    if (valuei == nullptr) {
      /*throw SaxonApiException("Error in XsltExecutable copy constructor -
         parameter value " "is NULL");*/
    } else {
      parameters[paramIter->first] = new XdmValue(*(valuei));
    }
    paramIter++;
  }
}

DocumentBuilder *SaxonProcessor::newDocumentBuilder() {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t docBuilderRef = createDocumentBuilder(thread, (void *)procRef);

  DocumentBuilder *builder = new DocumentBuilder(this, docBuilderRef, cwd);

  return builder;
}

Xslt30Processor *SaxonProcessor::newXslt30Processor() {
  return (new Xslt30Processor(this, cwd));
}

XQueryProcessor *SaxonProcessor::newXQueryProcessor() {
  return (new XQueryProcessor(this, cwd));
}

XPathProcessor *SaxonProcessor::newXPathProcessor() {
  return (new XPathProcessor(this, cwd));
}

SchemaValidator *SaxonProcessor::newSchemaValidator() {
  if (licensei) {
    return (new SchemaValidator(this, cwd));
  } else {
    std::cerr << "\nError: Processor is not licensed for schema processing!!"
              << std::endl;
    return nullptr;
  }
}

const char *SaxonProcessor::version() {

  if (versionStr.empty()) {
    graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
    versionStr = std::string(j_c_getProductVersion(thread, (void *)procRef));
  }
  return versionStr.c_str();
}

void SaxonProcessor::setcwd(const char *dir) {
  if (dir != nullptr) {
    cwd = std::string(dir);
  }
}

const char *SaxonProcessor::getcwd() { return cwd.c_str(); }

const char *SaxonProcessor::getResourcesDirectory() {
  return nullptr; 
}

void SaxonProcessor::setResourcesDirectory(const char *dir) {

}

void SaxonProcessor::setCatalog(const char *catalogFile) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);

  if (catalogFile == nullptr) {

    return;
  }

  if (procRef == SXN_UNSET) {
    throw SaxonApiException(
        "Processor is null in SaxonProcessor.setCatalogFiles");
    return;
  }
  int64_t processorDataRef =
      createProcessorDataWithCapacity(thread, 1);

  addProcessorProperty(thread, (void *)processorDataRef,
                       (char *)catalogFile);

  long result =
      j_setCatalogFiles(thread, (void *)procRef,
                        (char *)cwd.c_str(), (void *)processorDataRef);
  j_handles_destroy(thread, (void *)processorDataRef);
  if (result == SXN_UNSET) {
    throw SaxonApiException("setCatalogFile failed");
  }
}

void SaxonProcessor::setCatalogFiles(const char **catalogFiles, int length) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);

  if (catalogFiles == nullptr && length > 0) {

    return;
  }

  if (procRef == SXN_UNSET) {
    throw SaxonApiException(
        "Processor is null in SaxonProcessor.setCatalogFiles");
    return;
  }

  int64_t processorDataRef =
      createProcessorDataWithCapacity(thread, length);

  for (int i = 0; i < length; i++) {

    if (catalogFiles[i] != nullptr) {
      char* catFile = (char *)catalogFiles[i];
      addProcessorProperty(thread, (void *)processorDataRef,
                           catFile);
    }
  }

  long result =
      j_setCatalogFiles(thread, (void *)procRef,
                        (char *)cwd.c_str(), (void *)processorDataRef);
  j_handles_destroy(thread, (void *)processorDataRef);
  if (result == SXN_UNSET) {
    throw SaxonApiException("setCatalogFile failed");
  } else if (result == SXN_EXCEPTION) {
    throw SaxonApiException();
  }
}

XdmValue *SaxonProcessor::parseJsonFromString(const char *source,
                                              const char *encoding) {

  if (source == nullptr) {
    throw SaxonApiException("JSON source string is null");
  }

  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t xdmValueRef = parseJsonString(thread, (void *)procRef,
                                        (char *)source, (char *)encoding);
  if (xdmValueRef >= 0) {
    XdmValue *value = makeXdmValueFromRef(xdmValueRef);

    return value;
  }
  if (xdmValueRef == SXN_EXCEPTION) {
    throw SaxonApiException();
  }
  return nullptr;
}

XdmValue *SaxonProcessor::parseJsonFromFile(const char *source) {

  if (source == nullptr) {
    throw SaxonApiException("JSON source file name is null");
  }

  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t xdmValueRef = parseJsonFile(thread, (void *)procRef,
                                      (char *)cwd.c_str(), (char *)source);
  if (xdmValueRef > 0) {
    XdmValue *value = makeXdmValueFromRef(xdmValueRef);

    return value;
  }

  if (xdmValueRef == SXN_EXCEPTION) {
    throw SaxonApiException();
  }
  return nullptr;
}

XdmNode *SaxonProcessor::parseXmlFromString(const char *source,
                                            const char *encoding,
                                            SchemaValidator *validator) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t xdmNodeRef = parseXmlStringWithValidator(
      thread, (void *)procRef, (char *)encoding,
      (validator != nullptr ? (void *)validator->getUnderlyingValidator()
                            : (void *)NULL),
      (char *)source);
  if (xdmNodeRef > 0) {
    XdmNode *value = new XdmNode(xdmNodeRef);

    return value;
  } else if (xdmNodeRef == SXN_EXCEPTION) {
    throw SaxonApiException();
  }
  return nullptr;
}

int SaxonProcessor::getNodeKind(int64_t nodeRef) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int nodeKind = j_getNodeKind(thread, (void *)nodeRef);

  return nodeKind;
}

XdmNode *SaxonProcessor::parseXmlFromFile(const char *filename,
                                          SchemaValidator *validator) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t xdmNodeRef = parseXmlFileWithValidator(
      thread, (char *)cwd.c_str(), (void *)procRef,
      (validator != nullptr ? (void *)validator->getUnderlyingValidator()
                            : (void *)nullptr),
      (char *)filename);
  if (xdmNodeRef > 0) {
    XdmNode *value = nullptr;
    value = new XdmNode(xdmNodeRef);

    return value;
  } else if (xdmNodeRef == SXN_EXCEPTION) {
    throw SaxonApiException();
  }
  return nullptr;
}

XdmNode *SaxonProcessor::parseXmlFromUri(const char *uri,
                                         SchemaValidator *validator) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t xdmNodeRef = parseXmlFileWithValidator(
      thread, (char *)cwd.c_str(), (void *)procRef,
      (validator != nullptr ? (void *)validator->getUnderlyingValidator()
                            : (void *)nullptr),
      (char *)uri);
  if (xdmNodeRef >= 0) {
    XdmNode *value = nullptr;
    value = new XdmNode(xdmNodeRef);

    return value;
  } else if (xdmNodeRef == SXN_EXCEPTION) {
    throw SaxonApiException();
  }
  return nullptr;
}


// Set a configuration property.
void SaxonProcessor::setConfigurationProperty(const char *name,
                                              const char *value) {
  if (name != nullptr && value != nullptr) {
    graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
    int resultsi = (int) j_applyPropertyToConfiguration(thread, (void *)procRef,
                           (char *)name, (char *)value);

    if (resultsi == SXN_EXCEPTION) {
      throw SaxonApiException();
    }
  }

}

void SaxonProcessor::clearConfigurationProperties() {

}

void SaxonProcessor::createHeapDump(bool live) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  j_createHeapDump(thread, (int)live);
}

void SaxonProcessor::release() {
  if (SaxonProcessor::jvmCreatedCPP != 0) {
    SaxonProcessor::jvmCreatedCPP = 0;
    if (getenv("SAXONC_DEBUG_FLAG")) {
      std::cerr << "SaxonProc: JVM finalized calling !" << std::endl;
      j_printObjectHandlesPoolInfo(sxn_environ->thread);
    }

    //SaxonProcessor::createHeapDump(true);

    // finalizeJavaRT(SaxonProcessor::sxn_environ->jvm);

    // delete SaxonProcessor::sxn_environ;

    if (graal_detach_thread(sxn_environ->thread) != 0) {
      fprintf(stderr, "graal_detach_thread error\n");
      return;
    }

  } else {
    if (getenv("SAXONC_DEBUG_FLAG")) {
      std::cerr << "SaxonProc: JVM finalize not called!" << std::endl;
    }
  }
}

void SaxonProcessor::deleteXdmValueArray(XdmValue **arr, int len) {
  if (arr != nullptr) {
    delete[] arr;
  }
}

void SaxonProcessor::deleteXdmAtomicValueArray(XdmAtomicValue **arr, int len) {
  if (arr != nullptr) {
    delete[] arr;
  }
}

const char * SaxonProcessor::encodeString(const char * stringValue, const char * toCharSetName) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  allocFn fn = operator new;
  const char * result = j_cpp_encodeString(thread, (char *)stringValue, (char *)toCharSetName, (void *)fn);
  if(result == nullptr && (bool)j_checkForException(thread)) {
      throw SaxonApiException();
  }
  return result;
}

/* ========= Factory method for Xdm ======== */

XdmAtomicValue *SaxonProcessor::makeStringValue(const char *str,
                                                const char *encoding) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t stringValueRef =
      j_makeStringValue(thread, (char *)str, (char *)encoding);
  XdmAtomicValue *value = new XdmAtomicValue(stringValueRef, "xs:string");
  return value;
}

XdmAtomicValue *SaxonProcessor::makeStringValue(std::string str,
                                                const char *encoding) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t stringValueRef = j_makeStringValue(
      thread, (char *)str.c_str(), (char *)encoding);
  XdmAtomicValue *value = new XdmAtomicValue(stringValueRef, "xs:string");
  return value;
}

XdmAtomicValue *SaxonProcessor::makeIntegerValue(int i) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t intValueRef = j_makeIntegerValue(thread, i);
  XdmAtomicValue *value = new XdmAtomicValue(
      intValueRef, "Q{http://www.w3.org/2001/XMLSchema}integer");
  return value;
}

XdmAtomicValue *SaxonProcessor::makeDoubleValue(double d) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t doubleValueRef = j_makeDoubleValue(thread, d);
  XdmAtomicValue *value = new XdmAtomicValue(
      doubleValueRef, "Q{http://www.w3.org/2001/XMLSchema}double");
  return value;
}

XdmAtomicValue *SaxonProcessor::makeFloatValue(float d) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t floatValueRef = j_makeFloatValue(thread, d);
  XdmAtomicValue *value = new XdmAtomicValue(
      floatValueRef, "Q{http://www.w3.org/2001/XMLSchema}float");
  return value;
}

XdmAtomicValue *SaxonProcessor::makeLongValue(long l) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t longValueRef = j_makeLongValue(thread, l);
  XdmAtomicValue *value = new XdmAtomicValue(
      longValueRef, "Q{http://www.w3.org/2001/XMLSchema}long");
  return value;
}

XdmAtomicValue *SaxonProcessor::makeBooleanValue(bool b) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t boolValueRef = j_makeBooleanValue(thread, (int)b);
  XdmAtomicValue *value = new XdmAtomicValue(
      boolValueRef, "Q{http://www.w3.org/2001/XMLSchema}boolean");
  return value;
}

XdmAtomicValue *SaxonProcessor::makeQNameValue(const char *str) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t qnameValueRef = j_makeQNameValue(thread, (char *)str);
  XdmAtomicValue *value = new XdmAtomicValue(qnameValueRef, "QName");
  return value;
}

XdmAtomicValue *SaxonProcessor::makeAtomicValue(const char *typei,
                                                const char *strValue) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  if(typei == nullptr || strValue == nullptr) {
    return nullptr;
  }
  int64_t obj = j_makeAtomicValue(thread, (char *)typei, (char *)strValue);
  if(obj == -1) {
    return nullptr;
  } else if(obj == SXN_EXCEPTION) {
    throw SaxonApiException();
  }
  XdmAtomicValue *value = new XdmAtomicValue(obj, typei);
  return value;
}

const char *SaxonProcessor::clarkNameToEQName(const char *name) {
  allocFn fn = operator new;
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  const char *result = j_clarkNameToEQName(thread,
                                           (void *)fn, (char *)name);
  return result;
}

const char *SaxonProcessor::EQNameToClarkName(const char *name) {
  allocFn fn = operator new;
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  const char *result = j_EQNameToClarkName(thread,(void *)fn, (char *)name);
  return result;
}

const char *SaxonProcessor::getStringValue(XdmItem *item) {
  allocFn fn = operator new;
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  const char *result =
      j_cpp_getStringValue(thread, (void *)item->getUnderlyingValue(), (void *)fn);
#ifdef DEBUG
  if (result == nullptr) {
    std::cerr << "getStringValue of XdmItem is nullptr" << std::endl;
  } else {
    std::cerr << "getStringValue of XdmItem is OK" << std::endl;
  }
#endif

  return result;
}


XdmArray *SaxonProcessor::makeArray(short *input, int length) {
  if (input == nullptr) {
    /*std::cerr << "Error found when converting pointer array of short values to "
                 "XdmArray"; */
    return nullptr;
  }
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t xdmArrayRef =
      j_makeXdmArrayFromShort(thread, input, length);

  XdmArray *newArray = new XdmArray(xdmArrayRef, length);
  return newArray;
}

XdmArray *SaxonProcessor::makeArray(XdmValue **input, int length) {
  int64_t processorDataRef = SXN_UNSET;
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  if (length == 0) {
    processorDataRef = createProcessorData(thread);
  } else {
    processorDataRef =
        createProcessorDataWithCapacity(thread, length);
  }

  for (int i = 0; i < length; i++) {
    if (input[i] == nullptr || input[i]->getUnderlyingValue() <= SXN_UNSET) {

      return nullptr;
    }
    addProcessorValue(thread, (void *)processorDataRef,
                      (void *)input[i]->getUnderlyingValue());
  }

  int64_t xdmArrayRef =
      j_makeXdmArray(thread, (void *)processorDataRef);

  if (getenv("SAXONC_DEBUG_FLAG")) {
    std::cerr<<"DEBUG makeArray ref= "<<((long)xdmArrayRef)<<std::endl;
  }

  j_handles_destroy(thread, (void *)processorDataRef);

  if (xdmArrayRef == SXN_EXCEPTION) {
    throw SaxonApiException();
  }

  auto *newArray = new XdmArray(xdmArrayRef, length);
  return newArray;
}

XdmArray *SaxonProcessor::makeArray(int *input, int length) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  if (length < 0) {
    throw SaxonApiException("Error found when converting pointer array of int values to XdmArray");
  }
  int64_t xdmArrayRef =
      j_makeXdmArrayFromInt(thread, input, length);

  if (xdmArrayRef == SXN_EXCEPTION) {
    throw SaxonApiException();
  }
  auto *newArray = new XdmArray(xdmArrayRef, length);
  return newArray;
}

XdmArray *SaxonProcessor::makeArray(long long *input, int length) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  if (length < 0) {
    throw SaxonApiException("Error found when converting pointer array of long values to XdmArray");
  }
  int64_t xdmArrayRef =
      j_makeXdmArrayFromLong(thread, input, length);

  if (xdmArrayRef == SXN_EXCEPTION) {
    throw SaxonApiException();
  }
  XdmArray *newArray = new XdmArray(xdmArrayRef, length);
  return newArray;
}

XdmArray *SaxonProcessor::makeArray(bool *input, int length) {

  if (input == nullptr || length < 0) {
    throw SaxonApiException("Error found when converting pointer array of bool values to XdmArray");
  }
  int *inputAsInts = new int[length];

  for (int i = 0; i < length; i++) {
    inputAsInts[i] = (int)input[i];
  }
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);

  int64_t xdmArrayRef =
      j_makeXdmArrayFromBool(thread, inputAsInts, length);
  XdmArray *newArray = new XdmArray(xdmArrayRef, length);
  return newArray;
}

XdmArray *SaxonProcessor::makeArray(char **input, int length) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  if(length < 0) {
    throw SaxonApiException("Error creating XdmArray - length < 0");
  }
  int64_t  xdmArrayRef = j_makeXdmArrayFromStrings(thread,
    input, length);

  if(xdmArrayRef == SXN_EXCEPTION) {
    throw SaxonApiException();
  }

  auto * newArray = new XdmArray(xdmArrayRef, length);
  return newArray;
}

XdmMap *
SaxonProcessor::makeMap(std::map<XdmAtomicValue *, XdmValue *> dataMap) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t mapDataRef = SXN_UNSET;

  mapDataRef =
      j_create_mapDataWithCapacity(thread, (int) dataMap.size());

  for (std::map<XdmAtomicValue *, XdmValue *>::iterator iter = dataMap.begin();
       iter != dataMap.end(); ++iter) {
    if ((iter->first)->getUnderlyingValue() <= SXN_UNSET ||
        (iter->second)->getUnderlyingValue() <= SXN_UNSET) {
        throw SaxonApiException("Error found when converting array of XdmValue to XdmArray");
    }
    j_addMapPair(thread, (void *)mapDataRef,
                 (void *)(iter->first)->getUnderlyingValue(),
                 (void *)(iter->second)->getUnderlyingValue());
  }

  int64_t xdmmapRef = j_makeXdmMap(thread, (void *)mapDataRef);

  j_handles_destroy(thread, (void *)mapDataRef);

  if (xdmmapRef == SXN_EXCEPTION) {
    throw SaxonApiException();
  }

  XdmMap *newArray = new XdmMap(xdmmapRef);
  return newArray;
}

XdmMap *SaxonProcessor::makeMap2(std::map<std::string, XdmValue *> dataMap) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t mapDataRef = SXN_UNSET;

  mapDataRef =
      j_create_mapDataWithCapacity(thread, (int) dataMap.size());

  for (std::map<std::string, XdmValue *>::iterator iter = dataMap.begin();
       iter != dataMap.end(); ++iter) {
    if ((iter->first).empty() ||
        (iter->second)->getUnderlyingValue() <= SXN_UNSET) {
        throw SaxonApiException("Error found when converting array of XdmValue to XdmArray");
    }
    int64_t stringValueRef = j_makeStringValue(
        thread, (char *)(iter->first).c_str(), (char *)nullptr);
    XdmAtomicValue *stringValue =
        new XdmAtomicValue(stringValueRef, "xs:string");
    j_addMapPair(thread, (void *)mapDataRef,
                 (void *)stringValue->getUnderlyingValue(),
                 (void *)(iter->second)->getUnderlyingValue());
    delete stringValue;
  }

  int64_t xdmmapRef = j_makeXdmMap(thread, (void *)mapDataRef);

  j_handles_destroy(thread, (void *)mapDataRef);

  if (xdmmapRef == SXN_EXCEPTION) {
    throw SaxonApiException();

  }

  XdmMap *newArray = new XdmMap(xdmmapRef);
  return newArray;
}

XdmMap *SaxonProcessor::makeMap3(XdmAtomicValue **keys, XdmValue **values,
                                 int size) {
  graal_isolatethread_t *thread = SaxonProcessor::getOrAttachCurrentThread(SaxonProcessor::sxn_environ);
  int64_t mapDataRef = SXN_UNSET;

  mapDataRef = j_create_mapDataWithCapacity(thread, size);

  for (int i = 0; i < size; i++) {
    if (keys[i]->getUnderlyingValue() <= SXN_UNSET ||
        values[i]->getUnderlyingValue() <= SXN_UNSET) {
      throw SaxonApiException("Error found when converting array of XdmValue to XdmArray");
    }
    j_addMapPair(thread, (void *)mapDataRef,
                 (void *)keys[i]->getUnderlyingValue(),
                 (void *)values[i]->getUnderlyingValue());
  }

  int64_t xdmmapRef = j_makeXdmMap(thread, (void *)mapDataRef);
  
  j_handles_destroy(thread, (void *)mapDataRef);

  if (xdmmapRef == SXN_EXCEPTION) {
    throw SaxonApiException("Error found when converting std:map of pair (XdmAtomicValue, XdmValue)");
  }

  auto *newArray = new XdmMap(xdmmapRef);
  return newArray;
}

