/*

   Copyright (c) 2011  Marius Popa <mapopa@gmail.com>
   Copyright (c) 2010  pilcrow <mjp@pilcrow.madison.wi.us>
   Copyright (c) 1999-2008  Edwin Pratomo
   Portions Copyright (c) 2001-2005  Daniel Ritz

   You may distribute under the terms of either the GNU General Public
   License or the Artistic License, as specified in the Perl README file.

*/

#include <DBIXS.h>              /* installed by the DBI module  */

/* make it compile with DBI < 1.20 */
#ifndef SQL_TYPE_DATE
#  define SQL_TYPE_DATE    91
#endif
#ifndef SQL_TYPE_TIME
#  define SQL_TYPE_TIME    92
#endif
#ifndef SQL_BLOB
#  define SQL_BLOB         30
#endif
#ifndef SQL_ARRAY
#  define SQL_ARRAY        50
#endif


static const int DBI_SQL_CHAR       = SQL_CHAR;
static const int DBI_SQL_NUMERIC    = SQL_NUMERIC;
static const int DBI_SQL_DECIMAL    = SQL_DECIMAL;
static const int DBI_SQL_INTEGER    = SQL_INTEGER;
static const int DBI_SQL_SMALLINT   = SQL_SMALLINT;
static const int DBI_SQL_FLOAT      = SQL_FLOAT;
static const int DBI_SQL_REAL       = SQL_REAL;
static const int DBI_SQL_DOUBLE     = SQL_DOUBLE;
static const int DBI_SQL_DATE       = SQL_DATE;
static const int DBI_SQL_TIME       = SQL_TIME;
static const int DBI_SQL_TIMESTAMP  = SQL_TIMESTAMP;
static const int DBI_SQL_VARCHAR    = SQL_VARCHAR;
static const int DBI_SQL_TYPE_TIME  = SQL_TYPE_TIME;
static const int DBI_SQL_TYPE_DATE  = SQL_TYPE_DATE;
static const int DBI_SQL_ARRAY      = SQL_ARRAY;
static const int DBI_SQL_BLOB       = SQL_BLOB;

/* conflicts */

#undef  SQL_CHAR
#undef  SQL_NUMERIC
#undef  SQL_DECIMAL
#undef  SQL_INTEGER
#undef  SQL_SMALLINT
#undef  SQL_FLOAT
#undef  SQL_REAL
#undef  SQL_DOUBLE
#undef  SQL_DATE
#undef  SQL_TIME
#undef  SQL_TIMESTAMP
#undef  SQL_VARCHAR
#undef  SQL_TYPE_TIME
#undef  SQL_TYPE_DATE
#undef  SQL_ARRAY
#undef  SQL_BLOB
#undef  SQL_BOOLEAN

#ifdef __CYGWIN__
    #define _WIN32
    #define __stdcall __attribute__((stdcall))
    #define __cdecl __attribute__((cdecl))
    #include <ibase.h>
    #undef _WIN32
#else
    #include <ibase.h>
#endif
#include <time.h>

/* defines */

/* Firebird API 20 */
#if !defined(FB_API_VER) || FB_API_VER < 20
typedef void (*ISC_EVENT_CALLBACK)();
#endif

#ifndef SQLDA_CURRENT_VERSION
#  define SQLDA_OK_VERSION SQLDA_VERSION1
#else
#  define SQLDA_OK_VERSION SQLDA_CURRENT_VERSION
#endif

#define IB_ALLOC_FAIL   2
#define IB_FETCH_ERROR  1

#ifndef ISC_STATUS_LENGTH
#  define ISC_STATUS_LENGTH 20
#endif

#ifndef SvPV_nolen
#  define SvPV_nolen(sv) SvPV(sv, na)
#endif

#define FREE_SETNULL(ptr) \
do {                      \
    if (ptr)              \
    {                     \
        Safefree(ptr);    \
        ptr = NULL;       \
    }                     \
} while (0)

#define DPB_FILL_BYTE(dpb, code, byte)  \
do {                                    \
    *dpb++ = code;                      \
    *dpb++ = 1;                         \
    *dpb++ = byte;                      \
} while (0)

#define DPB_FILL_INTEGER(dpb, code, integer)    \
do {                                            \
    ISC_LONG tmp = integer;                     \
    *dpb++ = code;                              \
    *dpb++ = sizeof(tmp);                                  \
    tmp = isc_vax_integer((char *) &tmp, sizeof(tmp));    \
    Copy(&tmp, dpb, 1, ISC_LONG);            \
    dpb += sizeof(tmp);                         \
} while (0)

#define DPB_FILL_STRING(dpb, code, string)  \
    DPB_FILL_STRING_LEN(dpb, code, string, strlen(string) )

#define DPB_FILL_STRING_LEN(dpb, code, string, len) \
do {                                                \
    if ( len > 255 )                                \
        croak("DPB string too long (%d)", len);     \
    *dpb++ = code;                                  \
    *dpb++ = len;                                   \
    strncpy(dpb, string, (size_t) len);             \
    dpb += len;                                     \
} while (0)

#define DPB_PREP_INTEGER(buflen)    \
do {                                \
    buflen += sizeof(ISC_LONG) + 2; \
} while (0)

#define DPB_PREP_STRING(buflen, string) \
    DPB_PREP_STRING_LEN(buflen, strlen(string))

#define DPB_PREP_STRING_LEN(buflen, len)    \
do {                                        \
    buflen += len + 2;                      \
} while (0)


#  define TIMESTAMP_FPSECS(value) \
   (long)(((ISC_TIMESTAMP *)value)->timestamp_time % ISC_TIME_SECONDS_PRECISION)
#  define TIMESTAMP_ADD_FPSECS(value, inc) \
   ((ISC_TIMESTAMP *)value)->timestamp_time += (inc % ISC_TIME_SECONDS_PRECISION)

#  define TIME_FPSECS(value) \
   (long)((*(ISC_TIME *)value) % ISC_TIME_SECONDS_PRECISION)
#  define TIME_ADD_FPSECS(value, inc) \
   (*(ISC_TIME *)value) += (inc % ISC_TIME_SECONDS_PRECISION)


#ifndef NO_TRACE_MSGS
#  define DBI_TRACE(level, args) \
do {                             \
    if (DBIS->debug >= level)    \
        PerlIO_printf args ;     \
} while (0)
#  define DBI_TRACE_imp_xxh(imp_xxh, level, args) \
do { \
    if (DBIc_TRACE_LEVEL(imp_xxh) >= level) \
        PerlIO_printf args;             \
} while (0)
#else
#  define DBI_TRACE(level, args) do {} while (0)
#  define DBI_TRACE_imp_xxh(imp_xxh, level, args) do {} while (0)
#endif

#define BLOB_SEGMENT        (256)
#define DEFAULT_SQL_DIALECT (3)
#define INPUT_XSQLDA        (1)
#define OUTPUT_XSQLDA       (0)
#define PLAN_BUFFER_LEN     2048

#define SUCCESS             (0)
#define FAILURE             (-1)

/*
 * Hardcoded limit on the length of a Blob that can be fetched into a scalar.
 * If you want to fetch Blobs that are bigger, write your own Perl
 */

#define MAX_SAFE_BLOB_LENGTH (1000000)

#define MAX_EVENTS          15

typedef enum { ACTIVE, INACTIVE } IB_EVENT_STATE;

/****************/
/* data types   */
/****************/

/* structs for event */
typedef struct
{
    imp_dbh_t       *dbh;               /* pointer to parent dbh */
    ISC_LONG        id;                 /* event id assigned by IB */
#if defined(INCLUDE_TYPES_PUB_H)
    ISC_UCHAR       *event_buffer;
    ISC_UCHAR       *result_buffer;
#else
    char ISC_FAR    *event_buffer;
    char ISC_FAR    *result_buffer;
#endif
    char ISC_FAR * ISC_FAR *names;      /* names of events of interest */
    unsigned short  num;                /* number of events of interest */
    short           epb_length;         /* length of event parameter buffer */
    SV              *perl_cb;           /* perl callback for this event */
    IB_EVENT_STATE  state;
    char            exec_cb;
} IB_EVENT;

/* Define driver handle data structure */
struct imp_drh_st
{
    dbih_drc_t com;     /* MUST be first element in structure */
};

/* Define dbh implementor data structure */
struct imp_dbh_st
{
    dbih_dbc_t      com;                /* MUST be first element in structure */
    isc_db_handle   db;
    isc_tr_handle   tr;
    char ISC_FAR    *tpb_buffer;        /* transaction parameter buffer */
    unsigned short  tpb_length;         /* length of tpb_buffer */
    unsigned short  sqldialect;         /* default sql dialect */
    char            soft_commit;        /* use soft commit ? */
    char            *ib_charset;
    bool            ib_enable_utf8;

    unsigned int    sth_ddl;            /* number of open DDL statments */
    imp_sth_t       *first_sth;         /* pointer to first statement */
    imp_sth_t       *last_sth;          /* pointer to last statement */

#if defined(USE_THREADS) || defined(USE_ITHREADS) || defined(MULTIPLICITY)
    void            *context;           /* perl context for threads / multiplicity */
#endif

    /* per dbh default strftime() formats */
    char            *dateformat;
    char            *timestampformat;
    char            *timeformat;

    unsigned char   *charset_bytes_per_char;
};

/* Define sth implementor data structure */
struct imp_sth_st
{
    dbih_stc_t      com;                /* MUST be first element in structure */
    isc_stmt_handle stmt;
    XSQLDA          *out_sqlda;         /* for storing select-list items */
    XSQLDA          *in_sqlda;          /* for storing placeholder values */
    char            *cursor_name;
    long            type;               /* statement type */
    char            count_item;
    int             affected;           /* number of affected rows */

    char            *dateformat;
    char            *timestampformat;
    char            *timeformat;
    imp_sth_t       *prev_sth;                /* pointer to prev statement */
    imp_sth_t       *next_sth;                /* pointer to next statement */
    HV              *param_values;      /* For storing the ParamValues attribute */
};


/* newer header file defines the struct already */
typedef struct dbd_vary
{
    short vary_length;
    char  vary_string [1];
} DBD_VARY;


/* These defines avoid name clashes for multiple statically linked DBD's */
#define dbd_init            ib_init
#define dbd_discon_all      ib_discon_all
#define dbd_db_login        ib_db_login
#define dbd_db_login6       ib_db_login6
#define dbd_db_do           ib_db_do
#define dbd_db_commit       ib_db_commit
#define dbd_db_rollback     ib_db_rollback
#define dbd_db_disconnect   ib_db_disconnect
#define dbd_db_destroy      ib_db_destroy
#define dbd_db_STORE_attrib ib_db_STORE_attrib
#define dbd_db_FETCH_attrib ib_db_FETCH_attrib
#define dbd_st_prepare      ib_st_prepare
#define dbd_st_rows         ib_st_rows
#define dbd_st_execute      ib_st_execute
#define dbd_st_fetch        ib_st_fetch
#define dbd_st_finish       ib_st_finish
#define dbd_st_destroy      ib_st_destroy
#define dbd_st_blob_read    ib_st_blob_read
#define dbd_st_STORE_attrib ib_st_STORE_attrib
#define dbd_st_FETCH_attrib ib_st_FETCH_attrib
#define dbd_bind_ph         ib_bind_ph

void    do_error _((SV *h, int rc, char *what));

void    dbd_init     _((dbistate_t *dbistate));
void    dbd_preparse _((SV *sth, imp_sth_t *imp_sth, char *statement));
int     dbd_describe _((SV *sth, imp_sth_t *imp_sth));
int     dbd_db_ping   (SV *dbh);

char* ib_error_decode(const ISC_STATUS *status);
int ib_error_check(SV *h, ISC_STATUS *status);

int ib_start_transaction   (SV *h, imp_dbh_t *imp_dbh);
int ib_commit_transaction  (SV *h, imp_dbh_t *imp_dbh);
int ib_rollback_transaction(SV *h, imp_dbh_t *imp_dbh);
long ib_rows(SV *xxh, isc_stmt_handle *h_stmt, char count_type);
void ib_cleanup_st_prepare (imp_sth_t *imp_sth);

SV* dbd_db_quote(SV* dbh, SV* str, SV* type);

/* end */
