/*
===========================================
   responsible:    DanielD
   description:    Interface to the SAP DB Loader
   target:         Perl
   last changed:   24.06.2005

    ========== licence begin  GPL
    Copyright (c) 2000-2005 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end
===========================================*/


#include <stddef.h>

#ifdef __cplusplus
#include <math.h>
#include <stdlib.h>
extern "C" {
#else
#define bool char
#define HAS_BOOL 1
#endif

#define PERL_GLUE 1
#define Glue_BEGIN_ALLOW_THREADS
#define Glue_END_ALLOW_THREADS

#include "patchlevel.h"/* no check */

#if defined (ACTIVE_PERL)
    #if PATCHLEVEL < 6
        #define PERL_OBJECT
    #endif
#endif

#include "EXTERN.h"/* no check */
#include "perl.h"/* no check */
#if defined (PERL_OBJECT)
#include "perlCAPI.h"/* no check */
#endif
#include "XSUB.h"/* no check */
#undef free
#undef malloc
#undef realloc
#include <string.h>
#if defined (bool)
#undef bool
#endif
#if defined (PERL_OBJECT)
    #if !defined (PL_sv_undef)
        #define PL_sv_undef sv_undef
    #endif
    #if !defined (PL_sv_yes)
        #define PL_sv_yes sv_yes
    #endif
    #if !defined (PL_errgv)
        #define PL_errgv errgv
    #endif
#endif
#ifdef __cplusplus
}
#endif

#if defined (_WIN32)
#define GLUEEXPORT __declspec(dllexport)
#else
#define GLUEEXPORT
#endif

#define LIT(c) CONST_CAST(char *, c)
#define CROAKBUF_SIZE 200

#define MF__
#if defined (__cplusplus)
#define externC extern "C"
#else
#define externC
#endif

#define SL_None &PL_sv_undef
#define SL_isNone(val) (!SvOK (val))
#define SL_isTrue(val) SvTRUE (val)

static const char * invalidArgCount_C = "Invalid number of arguments";

static void *
getGluePointer (SV * sv, const char * className, char * croakBuf)
{
    IV   tmp;

    if (SvGMAGICAL(sv)) {
        mg_get(sv);
    }

    /* Check to see if this is an object */
    if (sv_isobject(sv)) {
        SV *tsv = (SV*) SvRV(sv);
        if ((SvTYPE(tsv) == SVt_PVHV)) {
            MAGIC *mg;
            if (SvMAGICAL(tsv)) {
                mg = mg_find(tsv,'P');
                if (mg) {
                    SV *rsv = mg->mg_obj;
                    if (sv_isobject(rsv)) {
                        tmp = SvIV((SV*)SvRV(rsv));
                    }
                }
            }
            else {
                strcpy (croakBuf, "Not a valid pointer value");
                return NULL;
            }
        }
        else {
            tmp = SvIV((SV*)SvRV(sv));
        }
    }
    else if (SL_isNone (sv)) {            /* Check for undef */
        strcpy (croakBuf, "Undef object not allowed");
        return NULL;
    }
    else if (SvTYPE(sv) == SVt_RV) {       /* Check for NULL pointer */
        if (!SvROK(sv)) {
            strcpy (croakBuf, "NULL object not allowed");
        }
        else {
            strcpy (croakBuf, "NULL object not allowed");
        }
        return NULL;
    }
    else {                                 /* Dont know what it is */
        strcpy (croakBuf, "Not a valid pointer value");
        return NULL;
    }
    /* Now see if the types match */

    if (!sv_isa (sv, (char *) className)) {
        sprintf (croakBuf, "Object is of class %s (should be %s)",
                HvNAME(SvSTASH(SvRV(sv))), className);
        return NULL;
    }
    return (void*) tmp;
}


/*
 * Exceptions
 */

static const char * CommunicationError_loader = "CommunicationError";
static const char * LoaderError_loader = "LoaderError";

/* snippet Perl_precode */
#define CN72 1
#undef na
#include "hsp100.h"
#include "hcn14.h"
#include "patchlevel.h"/* no check */
#if PATCHLEVEL >= 5
    #define na PL_na
#else
    #define na Perl_na
#endif

#if defined (__cplusplus)
#define REFPARM(name)   name
#define INLINE          inline
#else
#define REFPARM(name)   &name
#define INLINE

#undef ROUTINE_DBG_MSP00
#define ROUTINE_DBG_MSP00(lit) static const char * funcname_glue_ = lit

#define REINTERPRET_CAST(type, expr) ((type) expr)
#define STATIC_CAST(type, expr) ((type) expr)
#define CONST_CAST(type, expr) ((type) expr)
#define REFCAST_MEO00(type)
#endif


static const char * SQLErrorTagC = "SQL-Error: ";

typedef struct ReplyInfoT {
    tsp00_Int4        errCode;
    const _TCHAR  * errText;
    tsp00_Int4        errLen;
    tsp00_Int4        sqlCode;
    const _TCHAR  * sqlErrText;
    tsp00_Int4        sqlErrLen;
} ReplyInfoT;

static void trimPayload (
    const void * dataArg,
    tsp00_Int4 * len);

/*----------------------------------------*/

static int
commErrOccured (int rc, char * msg, char * croakBuf)
{
    if (rc != 0) {
        sprintf (croakBuf, "%s (%d): %s", CommunicationError_loader, rc, msg);
        return 1;
    }
    else {
        return 0;
    }
}

/*----------------------------------------*/

static int
loaderErrOccured (
    void * nself,
    int rc,
    tsp00_ErrTextc VAR_ARRAY_REF msg,
    const char * cmd,
    char * croakBuf,
    int raiseSqlErrors)
{
    ReplyInfoT    replyInfo;
    int           errOccurred = 0;

    if (rc == DBMAPI_COMMERR_CN14) {
        sprintf (croakBuf, "Communication Error: %s", (char *) msg);
        return 1;
    }
    else if (rc == DBMAPI_OK_CN14) {
        return 0;
    }
    cn14analyzeRpmAnswer (nself,
        &replyInfo.errCode, &replyInfo.errText, &replyInfo.errLen,
        &replyInfo.sqlCode, &replyInfo.sqlErrText, &replyInfo.sqlErrLen);
    if ((replyInfo.sqlCode != 0) && !raiseSqlErrors) {
        /* ignore */
    }
    else if (replyInfo.sqlCode != 0) {
        errOccurred = 1;
        sprintf (croakBuf, "Error Loader Server: [SQL %d] %*s",
            replyInfo.sqlCode, replyInfo.sqlErrLen, replyInfo.sqlErrText);
    }
    else if (replyInfo.errCode != 0) {
        errOccurred = 1;
        sprintf (croakBuf, "Error Loader Server: [%d] %*s",
            replyInfo.errCode, replyInfo.errLen, replyInfo.errText);
    }
    return errOccurred;
}

/*----------------------------------------*/

static int
allocFailed (void * ptr, char * croakBuf)
{
    if (ptr == NULL) {
        strcpy (croakBuf, "Out of memory");
        return 1;
    }
    else {
        return 0;
    }
}

/* endsnippet Perl_precode */
static const char * LoaderClassID = "Loader";

typedef struct LoaderObjectT {
    const char * classID;
    void *nself;
/* no code for key Loader Perl_/Generic_ in ['Generic_precode']*/
} LoaderObjectT;

#define isLoaderObject(op) ((op)->classID == LoaderClassID)

static const char * notClassLoader_C = "Object not of class Loader";

static LoaderObjectT *
newLoader ()
{
    LoaderObjectT * result;

    Newz (0, result, 1, LoaderObjectT);
    return result;
}

static void
freeLoader (
    LoaderObjectT * value)
{
    Safefree (value);
}
/* snippet Loader Generic_precode */

static char * buildInfo ()
{
    static tsp00_Versionc versionString;
    static bool isInitialized;
#if defined (PYTHON_GLUE)
    tsp100_CompName compName = "loaderpy ";
#elif defined (PERL_GLUE)
    tsp100_CompName compName = "loaderprl";
#else
    #error must be either Python or Perl
#endif

    if (!isInitialized) {
        sp100_GetVersionString (compName, s100buildnumber, versionString);
    }
    return versionString;
}

/*----------------------------------------*/

static void
trimPayload (
    const void * data,
    tsp00_Int4 * len)
{
    if (data != NULL) {
        const void * nullPos = memchr (data, '\0', *len);
        if (nullPos != NULL) {
            *len = (tsp00_Int4) (((const char *) nullPos) - ((const char *) data));
        }
    }
    else {
    *len = 0;
    }

}

/*----------------------------------------*/

static void
localRelease (
    void* nself)
{
    if (nself != NULL) {
        cn14release (&nself);
    }
}


/*----------------------------------------*/

#if defined (PYTHON_GLUE)
static void
destructor_Loader (
    PyObject * pyself)
{
    LoaderObjectT* self = REINTERPRET_CAST (LoaderObjectT*, pyself);

    localRelease (self->nself);
    PyObject_Del (pyself);
}
#endif

typedef struct ReadResultT {
    const char * data;
    int len;
} ReadResultT;

/*----------------------------------------*/

static tsp00_Int4 cmdAndRead (
   void * nself,
   const char * cmd,
   ReadResultT * output,
   tsp00_ErrTextc VAR_ARRAY_REF errtext)
{
    tsp00_Int4 rc;
    ReplyInfoT replyInfo;

    Glue_BEGIN_ALLOW_THREADS
    rc = cn14ExecuteLoaderCmd (nself, cmd, strlen (cmd), NULL, NULL, errtext);
    Glue_END_ALLOW_THREADS
    if (rc == DBMAPI_OK_CN14) {
        rc = cn14analyzeRpmAnswer (nself,
            &replyInfo.errCode, &replyInfo.errText, &replyInfo.errLen,
            &replyInfo.sqlCode, &replyInfo.sqlErrText, &replyInfo.sqlErrLen);
        if (replyInfo.errCode == 0) {
            output->data = replyInfo.errText;
            output->len = replyInfo.errLen;
        }
    }
    return rc;
}

/*----------------------------------------*/

static tsp00_Int4 rawCommand (
   void * nself,
   const char * cmd,
   ReadResultT * output,
   tsp00_ErrTextc VAR_ARRAY_REF errtext)
{
    tsp00_Int4 rc;

    Glue_BEGIN_ALLOW_THREADS
    rc = cn14ExecuteLoaderCmd (nself, cmd, strlen (cmd), NULL, NULL, errtext);
    Glue_END_ALLOW_THREADS
    if (rc == DBMAPI_OK_CN14) {
        output->len = cn14bytesAvailable (nself);
        output->data = cn14rawReadData (nself, &rc);
        if (output->data != NULL) {
            trimPayload (output->data, &output->len);
        }
    }
    return rc;
}

/*----------------------------------------*/

static int cancelCmd (
   void * nself)
{
    Glue_BEGIN_ALLOW_THREADS
    cn14cmdCancel (nself);
    Glue_END_ALLOW_THREADS
    return 0;
}

/*----------------------------------------*/

static int sql (
   void * nself,
   const char * cmd,
   tsp00_ErrTextc VAR_ARRAY_REF errtext)
{
    int result = 0;
    ReplyInfoT replyInfo;

    Glue_BEGIN_ALLOW_THREADS
    result = cn14ExecuteLoaderCmd (nself, cmd, strlen (cmd), NULL, NULL, errtext);
    Glue_END_ALLOW_THREADS
    if (result == DBMAPI_OK_CN14) {
        cn14analyzeRpmAnswer (nself,
            &replyInfo.errCode, &replyInfo.errText, &replyInfo.errLen,
            &replyInfo.sqlCode, &replyInfo.sqlErrText, &replyInfo.sqlErrLen);
        result = replyInfo.sqlCode;
    }
    return result;
}

/*----------------------------------------*/

#if defined (PYTHON_GLUE)
static int readResult2Python (
   void * nself,
   ReadResultT readResult,
   PyObject ** pyResult)
{
    *pyResult = PyString_FromStringAndSize (readResult.data, readResult.len);
    return *pyResult != NULL;
}
#endif

/*----------------------------------------*/

static int doConnect (
   const char * servernode,
   const char * dbname,
   const char * instroot,
   void      ** sessionOut,
   tsp00_ErrTextc VAR_ARRAY_REF errtext)
{
    int rc;

    Glue_BEGIN_ALLOW_THREADS
    rc = cn14connectRPM (
        REFCAST_MEO00 (tsp00_NodeIdc) servernode,
        REFCAST_MEO00 (tsp00_DbNamec) dbname,
        REFCAST_MEO00 (tsp00_VFilenamec) instroot,
        NULL, sessionOut, errtext);
    Glue_END_ALLOW_THREADS
    return rc;
}


/* endsnippet Loader Generic_precode */
/*----------------------------------------*/

externC XS (Loader_cmd)
{
    ROUTINE_DBG_MSP00 ("Loader_cmd");
/* no code for key Perl_/Generic_code */
    int ok = 1;
    int argvi = 0;
    char croakBuf [CROAKBUF_SIZE];
    dXSARGS;
    LoaderObjectT* self;
    void* nself;
    char * cmd;
    ReadResultT output;
    tsp00_ErrTextc msg;
    tsp00_Int4 result;
    
    cv = cv;
    if (items != 2) {
        croak (invalidArgCount_C);
    }
    self = (LoaderObjectT*) getGluePointer (ST(0), LoaderClassID, croakBuf);
    if ((self == NULL) || !isLoaderObject (self)) {
        croak (croakBuf);
    }
    nself = self->nself;
    switch (items) {
        case 2: cmd = (char *) SvPV (ST(1), na);
    }
    if (!ok) {
        ok = 0;
        goto cleanup_label;
    }
/* no code for key Perl_/Generic_nativeCall */
    result = cmdAndRead (nself, cmd, &output, msg);
    if (loaderErrOccured (nself, result, msg, cmd, croakBuf, 1)) {
        ok = 0;
        goto cleanup_label;
    }
    /* outargs */
    ST (argvi) = sv_newmortal ();
    sv_setpvn ((SV*) ST (argvi++), (char *) output.data, output.len);
  cleanup_label:
    if (!ok) {
        /* Cleanup */
        croak (croakBuf);
    }
    XSRETURN (argvi);
}

/*----------------------------------------*/

externC XS (Loader_rawCmd)
{
    ROUTINE_DBG_MSP00 ("Loader_rawCmd");
/* no code for key Perl_/Generic_code */
    int ok = 1;
    int argvi = 0;
    char croakBuf [CROAKBUF_SIZE];
    dXSARGS;
    LoaderObjectT* self;
    void* nself;
    char * cmd;
    ReadResultT output;
    tsp00_ErrTextc msg;
    tsp00_Int4 result;
    
    cv = cv;
    if (items != 2) {
        croak (invalidArgCount_C);
    }
    self = (LoaderObjectT*) getGluePointer (ST(0), LoaderClassID, croakBuf);
    if ((self == NULL) || !isLoaderObject (self)) {
        croak (croakBuf);
    }
    nself = self->nself;
    switch (items) {
        case 2: cmd = (char *) SvPV (ST(1), na);
    }
    if (!ok) {
        ok = 0;
        goto cleanup_label;
    }
/* no code for key Perl_/Generic_nativeCall */
    result = rawCommand (nself, cmd, &output, msg);
    if (commErrOccured (result, msg, croakBuf)) {
        ok = 0;
        goto cleanup_label;
    }
    /* outargs */
    ST (argvi) = sv_newmortal ();
    sv_setpvn ((SV*) ST (argvi++), (char *) output.data, output.len);
  cleanup_label:
    if (!ok) {
        /* Cleanup */
        croak (croakBuf);
    }
    XSRETURN (argvi);
}

/*----------------------------------------*/

externC XS (Loader_sql)
{
    ROUTINE_DBG_MSP00 ("Loader_sql");
/* no code for key Perl_/Generic_code */
    int ok = 1;
    int argvi = 0;
    char croakBuf [CROAKBUF_SIZE];
    dXSARGS;
    LoaderObjectT* self;
    void* nself;
    char * cmd;
    tsp00_ErrTextc msg;
    int result;
    
    cv = cv;
    if (items != 2) {
        croak (invalidArgCount_C);
    }
    self = (LoaderObjectT*) getGluePointer (ST(0), LoaderClassID, croakBuf);
    if ((self == NULL) || !isLoaderObject (self)) {
        croak (croakBuf);
    }
    nself = self->nself;
    switch (items) {
        case 2: cmd = (char *) SvPV (ST(1), na);
    }
    if (!ok) {
        ok = 0;
        goto cleanup_label;
    }
/* no code for key Perl_/Generic_nativeCall */
    result = sql (nself, cmd, msg);
    if (loaderErrOccured (nself, result, msg, cmd, croakBuf, 0)) {
        ok = 0;
        goto cleanup_label;
    }
    /* outargs */
    ST (argvi) = sv_newmortal ();
    sv_setiv (ST (argvi++), (IV) result);
  cleanup_label:
    if (!ok) {
        /* Cleanup */
        croak (croakBuf);
    }
    XSRETURN (argvi);
}

/*----------------------------------------*/

externC XS (Loader_cancelCmd)
{
    ROUTINE_DBG_MSP00 ("Loader_cancelCmd");
/* no code for key Perl_/Generic_code */
    int ok = 1;
    int argvi = 0;
    char croakBuf [CROAKBUF_SIZE];
    dXSARGS;
    LoaderObjectT* self;
    void* nself;
    int result;
    
    cv = cv;
    if (items != 1) {
        croak (invalidArgCount_C);
    }
    self = (LoaderObjectT*) getGluePointer (ST(0), LoaderClassID, croakBuf);
    if ((self == NULL) || !isLoaderObject (self)) {
        croak (croakBuf);
    }
    nself = self->nself;
/* no code for key Perl_/Generic_nativeCall */
    result = cancelCmd (nself);
    /* outargs */
    XSRETURN (argvi);
}

/*----------------------------------------*/

externC XS (Loader_release)
{
    ROUTINE_DBG_MSP00 ("Loader_release");
/* no code for key release Perl_/Generic_ in ['Generic_nativeCall']*/
    int ok = 1;
    int argvi = 0;
    char croakBuf [CROAKBUF_SIZE];
    dXSARGS;
    LoaderObjectT* self;
    void* nself;
    
    cv = cv;
    if (items != 1) {
        croak (invalidArgCount_C);
    }
    self = (LoaderObjectT*) getGluePointer (ST(0), LoaderClassID, croakBuf);
    if ((self == NULL) || !isLoaderObject (self)) {
        croak (croakBuf);
    }
    nself = self->nself;
/* snippet release Generic_nativeCall */
    localRelease (self->nself);
    self->nself = NULL;
    /* endsnippet release Generic_nativeCall */
    /* outargs */
    XSRETURN (argvi);
}


/*----------------------------------------*/

externC XS (Loader_destructor)
{
    ROUTINE_DBG_MSP00 ("Loader_destructor");
    char croakBuf [CROAKBUF_SIZE];
    dXSARGS;
    LoaderObjectT* self;
    void* nself;

    cv = cv;
    if (items != 1) {
        croak (invalidArgCount_C);
    }
    self = (LoaderObjectT*) getGluePointer (ST(0), LoaderClassID, croakBuf);
    if ((self == NULL) || !isLoaderObject (self)) {
        croak (croakBuf);
    }
    nself = self->nself;
    localRelease (nself);
    freeLoader (self);
    XSRETURN (0);
}

/*----------------------------------------*/

externC XS (loader_Loader)
{
    ROUTINE_DBG_MSP00 ("loader_Loader");
/* no code for key constructor Perl_/Generic_ in ['Generic_nativeCall']*/
    LoaderObjectT * newObj = NULL;
    int ok = 1;
    int argvi = 0;
    char croakBuf [CROAKBUF_SIZE];
    dXSARGS;
    char * servernode = LIT( "");
    char * dbname = LIT( "");
    char * instroot = LIT( "");
    void * session;
    tsp00_ErrTextc msg;
    tsp00_Int4 result;
    
    cv = cv;
    if ((items < 0) || (items > 3)) {
        croak (invalidArgCount_C);
    }
    switch (items) {
        case 3: instroot = (char *) SvPV (ST(2), na);
        case 2: dbname = (char *) SvPV (ST(1), na);
        case 1: servernode = (char *) SvPV (ST(0), na);
    }
    if (!ok) {
        ok = 0;
        goto cleanup_label;
    }
    newObj = newLoader ();
    if (allocFailed (newObj, croakBuf)) {
        ok = 0;
        newObj = NULL;
        goto cleanup_label;
    }
    newObj->classID = LoaderClassID;
/* snippet constructor Generic_nativeCall */
    result = doConnect (servernode, dbname, instroot, &session, msg);
    newObj->nself = session;
    if (commErrOccured (result, msg, croakBuf)) {
    ok = 0;
    goto cleanup_label;
    }
    /* endsnippet constructor Generic_nativeCall */
    /* outargs */
    ST (argvi) = sv_newmortal ();
    sv_setref_pv (ST (argvi++), (char *)LoaderClassID, (void *) newObj);
  cleanup_label:
    if (!ok) {
        /* Cleanup */
        if (newObj != NULL) {
            free (newObj);
        }
        croak (croakBuf);
    }
    XSRETURN (argvi);
}

/*----------------------------------------*/

externC XS (loader__buildInfo)
{
    ROUTINE_DBG_MSP00 ("loader__buildInfo");
/* no code for key Perl_/Generic_code */
    int ok = 1;
    int argvi = 0;
    char croakBuf [CROAKBUF_SIZE];
    dXSARGS;
    char * result;
    
    cv = cv;
    if (items != 0) {
        croak (invalidArgCount_C);
    }
/* no code for key Perl_/Generic_nativeCall */
    result = buildInfo ();
    /* outargs */
    ST (argvi) = sv_newmortal ();
    sv_setpv (ST (argvi++), result);
    XSRETURN (argvi);
}


#if defined (ACTIVE_PERL)
#define boot_SAP__DBTech__loader boot_SAP__DBTech__loaderaperl
#else
#define boot_SAP__DBTech__loader boot_SAP__DBTech__loadercperl
#endif

externC GLUEEXPORT XS(boot_SAP__DBTech__loader)
{
    dXSARGS;
    char * file = CONST_CAST (char *, __FILE__);
    cv = cv;
    items = items;
    newXS (LIT ("SAP::DBTech::loaderc::loader_Loader"), loader_Loader, file);
    newXS (LIT ("SAP::DBTech::loaderc::loader__buildInfo"), loader__buildInfo, file);
    newXS (LIT ("SAP::DBTech::loaderc::Loader_cmd"), Loader_cmd, file);
    newXS (LIT ("SAP::DBTech::loaderc::Loader_rawCmd"), Loader_rawCmd, file);
    newXS (LIT ("SAP::DBTech::loaderc::Loader_sql"), Loader_sql, file);
    newXS (LIT ("SAP::DBTech::loaderc::Loader_cancelCmd"), Loader_cancelCmd, file);
    newXS (LIT ("SAP::DBTech::loaderc::Loader_release"), Loader_release, file);
    newXS (LIT("SAP::DBTech::loaderc::Loader_destructor"), Loader_destructor, file);
/* no code for key Perl_/Generic_ in ['Python_precode', 'Perl_precode', 'Loader Generic_precode', 'constructor Generic_nativeCall', 'release Generic_nativeCall']*/
    ST(0) = &PL_sv_yes;
    XSRETURN(1);
}

