/*---------------------------------------------------------------------
 $Header: /Perl/OlleDB/handleattributes.cpp 4     11-08-07 23:25 Sommar $

  This file holds routines for getting and (in one case) retrieving
  handle attributes from the Win32::SqlServer hash. Many of them are
  format options.

  Copyright (c) 2004-2011   Erland Sommarskog

  $History: handleattributes.cpp $
 * 
 * *****************  Version 4  *****************
 * User: Sommar       Date: 11-08-07   Time: 23:25
 * Updated in $/Perl/OlleDB
 * Suppress warnings about data truncation on x64.
 * 
 * *****************  Version 3  *****************
 * User: Sommar       Date: 09-07-26   Time: 12:44
 * Updated in $/Perl/OlleDB
 * Determining whether an SV is defined through my_sv_is_defined to as
 * SvOK may return false, unless we first do SvGETMAGIC. This proved to be
 * an issue when using table-valued parameters with threads::shared.
 *
 * *****************  Version 2  *****************
 * User: Sommar       Date: 08-01-06   Time: 23:33
 * Updated in $/Perl/OlleDB
 * Replaced all unsafe CRT functions with their safe replacements in VC8.
 * olledb_message now takes a va_list as argument, so we pass it
 * parameterised strings and don't have to litter the rest of the code
 * with that.
 *
 * *****************  Version 1  *****************
 * User: Sommar       Date: 07-12-24   Time: 21:40
 * Created in $/Perl/OlleDB
  ---------------------------------------------------------------------*/

#include "CommonInclude.h"
#include "convenience.h"
#include "handleattributes.h"


// The names for all OlleDB attributes.
static char *hash_keys[] =
   { "internaldata", "PropsDebug",     "AutoConnect",    "RowsAtATime",
     "DecimalAsStr", "DatetimeOption", "TZOffset",       "BinaryAsStr",
     "DateFormat",   "MsecFormat",     "CommandTimeout", "MsgHandler",
     "QueryNotification", "SQL_version"};

// This enum is used to address option_hash_keys array.
typedef enum hash_key_enum
{
    HV_internaldata, HV_propsdebug,     HV_autoconnect, HV_rowsatatime,
    HV_decimalasstr, HV_datetimeoption, HV_tzoffset,    HV_binaryasstr,
    HV_dateformat,   HV_msecformat,     HV_cmdtimeout,  HV_msgcallback,
    HV_querynotification, HV_SQLversion
} hash_key_enum;


// Private routines for operating on the hash directly.
//---------------------------------------------------------------------
static SV **fetch_from_hash (SV* olle_ptr, hash_key_enum id) {
   HV * hv;
   hv = (HV *) SvRV(olle_ptr);
   return hv_fetch(hv, hash_keys[id], (I32) strlen(hash_keys[id]), FALSE);
}

static void delete_from_hash(SV *olle_ptr, hash_key_enum id) {
   HV * hv;
   hv = (HV *) SvRV(olle_ptr);
   hv_delete(hv, hash_keys[id], (I32) strlen(hash_keys[id]), G_DISCARD);
}

static SV * fetch_option(SV * olle_ptr, hash_key_enum id) {
// Fetches an option from the hash, and only returns an SV, if there is a
// defined value.
   SV  **svp;
   SV  * retsv = NULL;
   svp = fetch_from_hash(olle_ptr, id);
   if (svp != NULL && my_sv_is_defined(*svp)) {
       retsv = *svp;
   }
   return retsv;
}

double OptSqlVersion(SV * olle_ptr) {
   SV * sv;
   float retval = 6.5;
   if (sv = fetch_option(olle_ptr, HV_SQLversion)) {
      char * versionstr = SvPV_nolen(sv);
      sscanf_s(versionstr, "%f", &retval);
   }
   return retval;
}

// The purpose is to make sure that SQLversion does not have a defined value.
// We cannot set it to undef, because Perl thinks it's readonly. But we can
// delete it!
void drop_SQLversion(SV * olle_ptr) {
   delete_from_hash(olle_ptr, HV_SQLversion);
}

BOOL OptAutoConnect (SV * olle_ptr) {
   SV *sv;
   BOOL retval = FALSE;
   if (sv = fetch_option(olle_ptr, HV_autoconnect)) {
      retval = SvTRUE(sv);
   }
   return retval;
}

BOOL OptPropsDebug(SV * olle_ptr) {
   SV * sv;
   BOOL retval = FALSE;
   if (sv = fetch_option(olle_ptr, HV_propsdebug)) {
      retval = SvTRUE(sv);
   }
   return retval;
}

IV OptRowsAtATime(SV * olle_ptr) {
   SV * sv;
   IV retval = 100;
   if (sv = fetch_option(olle_ptr, HV_rowsatatime)) {
      retval = SvIV(sv);
      if (retval <= 0) {
          retval = 1;
      }
   }
   return retval;
}

BOOL OptDecimalAsStr(SV * olle_ptr) {
   SV * sv;
   BOOL retval = FALSE;
   if (sv = fetch_option(olle_ptr, HV_decimalasstr)) {
      retval = SvTRUE(sv);
   }
   return retval;
}

dt_options OptDatetimeOption(SV * olle_ptr) {
   SV * sv;
   dt_options retval = dto_iso;
   if (sv = fetch_option(olle_ptr, HV_datetimeoption)) {
      retval = (dt_options) SvIV(sv);
   }
   return retval;
}

tzinfo OptTZOffset(SV * olle_ptr) {
   tzinfo    tz = {FALSE, 0, 0};
   SV      * sv;

   if (sv = fetch_option(olle_ptr, HV_tzoffset)) {
      STRLEN tzlen;
      char * tzstr = SvPV(sv, tzlen);

      tz.inuse = TRUE;

      if (strcmp(tzstr, "local") == 0) {
         TIME_ZONE_INFORMATION WinTZ;
         DWORD ret = GetTimeZoneInformation(&WinTZ);
         int bias = WinTZ.Bias + (ret == TIME_ZONE_ID_STANDARD ?
                                  WinTZ.StandardBias : 0) +
                                 (ret == TIME_ZONE_ID_DAYLIGHT ?
                                  WinTZ.DaylightBias : 0);
         tz.sign   = (bias > 0 ? -1 : 1);  // Yes, that is how .Bias work.
         tz.hour   = abs(bias / 60);
         tz.minute = abs(bias % 60);
      }
      else if (tzlen == 6) {
         char s;
         int cnt = sscanf_s(tzstr, "%c%2d:%2d", &s, 1, &tz.hour, &tz.minute);
         if (cnt != 3 || ! (s == '+' || s == '-') ||
             tz.hour > 14 || tz.hour < 0 || tz.minute < 0 || tz.minute > 59) {
            croak("Illegal value for option TZOffset: '%s'", tzstr);
         }
         tz.sign = (s == '+' ? 1 : -1);
      }
      else {
         croak("Illegal value for option TZOffset: '%s'", tzstr);
      }
    }

    return tz;
}


bin_options OptBinaryAsStr(SV * olle_ptr) {
   SV * sv;
   bin_options retval = bin_binary;
   if (sv = fetch_option(olle_ptr, HV_binaryasstr)) {
      if (SvTRUE (sv)) {
         char * str = SvPV_nolen(sv);
         retval = bin_string;
         if (strcmp(str, "x") == 0) {
            retval = bin_string0x;
         }
      }
   }
   return retval;
}

char * OptDateFormat(SV * olle_ptr) {
   SV   * sv;
   char * retval = NULL;
   if (sv = fetch_option(olle_ptr, HV_dateformat)) {
      retval = SvPV_nolen(sv);
   }
   return retval;
}

char * OptMsecFormat(SV * olle_ptr) {
   SV * sv;
   char * retval = NULL;
   if (sv = fetch_option(olle_ptr, HV_msecformat)) {
      retval = SvPV_nolen(sv);
   }
   return retval;
}

SV * OptMsgCallback(SV * olle_ptr) {
    SV ** callback_ptr;
    SV * callback = NULL;
    // We must check that olle_ptr is OK, in case errors occurs at login.
    if (olle_ptr && SvOK(olle_ptr)) {
       if (callback_ptr = fetch_from_hash(olle_ptr, HV_msgcallback)) {
          callback = * callback_ptr;
       }
    }
    return callback;
}

IV OptCommandTimeout(SV * olle_ptr) {
   SV * sv;
   IV retval = 0;
   if (sv = fetch_option(olle_ptr, HV_cmdtimeout)) {
      retval = SvIV(sv);
   }
   return retval;
}

HV* OptQueryNotification(SV * olle_ptr) {
   SV * sv;
   HV * retval = NULL;
   if (sv = fetch_option(olle_ptr, HV_querynotification)) {
      retval = (HV *) SvRV(sv);
   }
   return retval;
}


// This one returns all format options in one struct.
formatoptions getformatoptions(SV   * olle_ptr) {
   formatoptions opts;

   opts.DecimalAsStr = OptDecimalAsStr(olle_ptr);
   opts.BinaryAsStr = OptBinaryAsStr(olle_ptr);
   opts.DatetimeOption = OptDatetimeOption(olle_ptr);
   opts.TZOffset = OptTZOffset(olle_ptr);
   if (opts.DatetimeOption == dto_strfmt) {
       opts.DateFormat = OptDateFormat(olle_ptr);
       opts.MsecFormat = OptMsecFormat(olle_ptr);
   }
   else {
       opts.DateFormat = NULL;
       opts.MsecFormat = NULL;
   }

   return opts;
}

// And this returns most important attribute of them all: the pointer to
// the internal data area for the XS code. Here we return it a void pointer,
// and the internaldata module will have to reinterpret the pointed.
void * OptInternalData(SV *olle_ptr)
{
    HV *hv;
    SV **svp;
    void * internaldata;

    if(!SvROK(olle_ptr))
        croak("olle_ptr parameter is not a reference!");
    hv = (HV *)SvRV(olle_ptr);
    if(! (svp = fetch_from_hash(olle_ptr, HV_internaldata)) )
        croak("Internal error: no internaldata key in hash");
    internaldata = (void *) SvIV(*svp);
    return internaldata;
}
