#include "NET.h"

DBISTATE_DECLARE;
static void iqfree();
static void free_cursor();
static void iqclose();

/*---------------------------------------------------*
 * Function:    new_cursor
 *
 * Purpose:     locates a free cursor from the array
 *
 * Arguments:   none
 *
 * Returns:     cursor id or -1 if not found
 *---------------------------------------------------*/

void
dbd_init(dbistate)
	dbistate_t *dbistate;
{
	DBIS = dbistate;
	dbd_errnum = GvSV(gv_fetchpv("DBD::NET::err",    1, SVt_IV));
	dbd_errstr = GvSV(gv_fetchpv("DBD::NET::errstr", 1, SVt_PV));
}

extern char NETerrmsg[];
void do_error( )
{
	puts(NETerrmsg);
	sv_setiv( dbd_errnum, 0 );
	sv_setpv( dbd_errstr, NETerrmsg );
}

void
fbh_dump(fbh, i)
	imp_fbh_t *fbh;
	int i;
{
	FILE *fp = DBILOGFP;
	fprintf(fp, "fbh %d: '%s' %s, ",
		i, fbh->cbuf, (fbh->nullok) ? "NULLable" : "");
	fprintf(fp, "type %d,  dbsize %ld, dsize %ld, p%d s%d\n",
	    fbh->dbtype, (long)fbh->dbsize, (long)fbh->dsize, fbh->prec, fbh->scale);
	fprintf(fp, "   out: ftype %d, indp %d, bufl %d, rlen %d, rcode %d\n",
	    fbh->ftype, fbh->indp, fbh->bufl, fbh->rlen, fbh->rcode);
}


int
dbtype_is_long(dbtype)
	int dbtype;
{
	/* Is it a LONG, LONG RAW, LONG VARCHAR or LONG VARRAW?	*/
	return (dbtype==8 || dbtype==24 || dbtype==94 || dbtype==95) ? 1 : 0;
}

/* ================================================================== */

/*
static AV *imp_dbh_cache_av;
static IV imp_dbh_generation;

static imp_dbh_t *
alloc_imp_dbh()
{
	imp_dbh_t *imp_dbh;
	SV *sv;
	if (imp_dbh_cache_av && AvFILL(imp_dbh_cache_av) > -1) {
	imp_dbh = (imp_dbh_t *)av_pop(imp_dbh_cache_av);
	} else {
	Newz(42, imp_dbh, sizeof(*imp_dbh), imp_dbh_t);
	}
	imp_dbh->in_use = TRUE;
	imp_dbh->dbh_generation = ++imp_dbh_generation;
	return imp_dbh;
}
*/


int
dbd_db_login(dbh, host, dbname, user, pass)
	SV *dbh;
	char *host;
	char *dbname;
	char *user;
	char *pass;
{
	D_imp_dbh(dbh);
	int ret;

	if (host && !*host) host = 0;	/* Patch by Sven Verdoolaege */
/*    imp_dbh->lda.svsock = ( host );  */
/*    $database test; */
	/* Dump the information we have into the Lda_Def */
	if ((imp_dbh->lda.svsock=connect_db(host,user,pass,dbname))<=0)
		return 0;
	imp_dbh->lda.svdb = dbname;
/*    imp_dbh->logged_on = TRUE;
	XST_mIV(0, (IV)imp_dbh); */
	DBIc_IMPSET_on(imp_dbh);    /* imp_dbh set up now                   */
	DBIc_ACTIVE_on(imp_dbh);    /* call disconnect before freeing       */
	return 1;

}

/* Commit and Rollback don't exist in NET but we'll stub them anyway... */

int
dbd_db_commit(dbh)
	SV *dbh;
{
	D_imp_dbh(dbh);
	return 1;
}

int
dbd_db_rollback(dbh)
	SV *dbh;
{
	D_imp_dbh(dbh);
	return 1;
}

int
dbd_db_disconnect(dbh)
	SV *dbh;
{
	D_imp_dbh(dbh);
	/* We assume that disconnect will always work       */
	/* since most errors imply already disconnected.    */
	DBIc_ACTIVE_off(imp_dbh);
	if ( dbis->debug >= 2 )
	printf( "imp_dbh->sock: %i\n", imp_dbh->lda.svsock );
	net_disconnect(imp_dbh->lda.svsock);

	/* We don't free imp_dbh since a reference still exists	*/
	/* The DESTROY method is the only one to 'free' memory.	*/
	return 1;
}

void
dbd_db_destroy(dbh)
	SV *dbh;
{
	D_imp_dbh(dbh);
	if (DBIc_ACTIVE(imp_dbh))
	dbd_db_disconnect(dbh);
	/* XXX free contents of imp_dbh */
	DBIc_IMPSET_off(imp_dbh);
}

int
dbd_db_STORE(dbh, keysv, valuesv)
	SV *dbh;
	SV *keysv;
	SV *valuesv;
{
	D_imp_dbh(dbh);
	STRLEN kl;
	char *key = SvPV(keysv,kl);
	SV *cachesv = NULL;
	int on = SvTRUE(valuesv);

	if (kl==10 && strEQ(key, "AutoCommit")){
	/* Ignore SvTRUE warning: '=' where '==' may have been intended. */
/*        if ( (on) ? ocon(&imp_dbh->lda) : ocof(&imp_dbh->lda) ) {
	    ora_error(dbh, &imp_dbh->lda, imp_dbh->lda.rc, "ocon/ocof failed");
	} else {
	    cachesv = (on) ? &sv_yes : &sv_no;
	} */
	} else {
	return FALSE;
	}
	if (cachesv) /* cache value for later DBI 'quick' fetch? */
	hv_store((HV*)SvRV(dbh), key, kl, cachesv, 0);
	return TRUE;
}

SV *
dbd_db_FETCH(dbh, keysv)
	SV *dbh;
	SV *keysv;
{
	D_imp_dbh(dbh);
	STRLEN kl;
	char *key = SvPV(keysv,kl);
	int i;
	SV *retsv = NULL;
	/* Default to caching results for DBI dispatch quick_FETCH  */
	int cacheit = TRUE;

	if (1) {    /* no attribs defined yet       */
	return Nullsv;
	}
	if (cacheit) { /* cache for next time (via DBI quick_FETCH) */
	hv_store((HV*)SvRV(dbh), key, kl, retsv, 0);
	SvREFCNT_inc(retsv);    /* so sv_2mortal won't free it  */
	}
	return sv_2mortal(retsv);
}


/* ================================================================== */

/*
static imp_sth_t *
alloc_imp_sth(imp_dbh)
	imp_dbh_t *imp_dbh;
{
	imp_sth_t *imp_sth;
	Newz(42, imp_sth, sizeof(imp_sth_t), imp_sth_t);
	imp_sth->imp_dbh = imp_dbh;
	imp_sth->dbh_generation = imp_dbh->dbh_generation;
	return imp_sth;
}
static void
free_imp_sth(imp_sth)
	imp_sth_t *imp_sth;
{
	Safefree(imp_sth);
}
*/

int
dbd_st_prepare(sth, statement)
	SV *sth;
	char *statement;
{
	D_imp_sth(sth);
	D_imp_dbh_from_sth;
	int i, inside_quote, cursor_num;
	char func[64];
	int desc_count,netfd;

	imp_sth->done_desc = 0;
	imp_sth->cda = &imp_sth->cdabuf;

	/* Parse statement for binds ( also, INSERTS! ) */
	/* Lowercase the statement first */
	netfd=imp_dbh->lda.svsock;

	sscanf( statement, "%s", func );
	for ( i = 0 ; i < strlen( func ) ; i++ )
	if ( isupper( func[i] ) )
	    func[i] = tolower( func[i] );

	if ( strstr( func, "insert" ) != 0 ) {
	if ( dbis->debug >= 2 )
	    warn( "INSERT present in statement\n" );
	imp_sth->is_insert = 1;
	  }

	if ( strstr( func, "create" ) != 0 ) {
	if ( dbis->debug >= 2 )
	    warn( "CREATE present in statement\n" );
	imp_sth->is_create = 1;
	  }

	if ( strstr( func, "update" ) != 0 ) {
	if ( dbis->debug >= 2 )
	    warn( "UPDATE present in statement\n" );
	imp_sth->is_update = 1;
	  }

	if ( strstr( func, "drop" ) != 0 ) {
	if ( dbis->debug >= 2 )
	    warn( "DROP present in statement\n" );
	imp_sth->is_drop = 1;
	  }

	if ( strstr( func, "delete" ) != 0 ) {
	if ( dbis->debug >= 2 )
	    warn( "DELETE present in statement\n" );
	imp_sth->is_delete = 1;
	  }

	/** Do the special case stuff first */
	if ( ( imp_sth->is_create == 1 ) || ( imp_sth->is_drop == 1 ) ||
	 ( imp_sth->is_insert == 1 ) || ( imp_sth->is_delete == 1 ) ||
	 ( imp_sth->is_update == 1 ) ) {
		if (sql_prepare(netfd,statement,&cursor_num,&desc_count)<=0) {
			do_error();
			return 0;
		}
		DBIc_IMPSET_on( imp_sth );
		return 1;
	  }

	if (!sql_prepare(netfd,statement,&cursor_num,&desc_count)) {
		do_error();
		return 0;
	}
	/** Tell the sth how many fields we have in the cursor */
	imp_sth->fbh_num = desc_count;

	/** Reset row_num to 0 */
	imp_sth->row_num = 0;

	/* Store index into cursor array in statement handle */
	imp_sth->cursoridx = cursor_num;

	/* Get number of fields and space needed for field names      */
	if ( dbis->debug >= 2 )
	printf( "DBD::NET::dbd_db_prepare'imp_sth->fbh_num: %d\n",
		imp_sth->fbh_num );

	DBIc_IMPSET_on(imp_sth);
	return 1;
}

int
dbd_bind_ph(h, imp_sth, ph_name, newvalue)
	SV *h;
	imp_sth_t *imp_sth;
	char *ph_name;
	SV *newvalue;
{
	SV **svp;
	STRLEN value_len;
	void *value_ptr;
	phs_t *phs;

	if (dbis->debug >= 2)
	warn("bind '%s' ==> %s\n", SvPV(newvalue,na), ph_name );

	svp = hv_fetch(imp_sth->bind_names, ph_name, strlen(ph_name), 0);
	if (svp == NULL)
	croak("dbd_bind_ph placeholder '%s' unknown", ph_name);
	phs = (phs_t*)((void*)SvPVX(*svp));

	/* At the moment we always do sv_setsv() and rebind.        */
	/* Later we may optimise this so that more often we can     */
	/* just copy the value & length over and not rebind!        */

	if (SvOK(newvalue)) {
	sv_setsv(phs->sv, newvalue);
	value_ptr = SvPV(phs->sv, value_len);
	phs->indp = 0;
	phs->ftype = (SvCUR(phs->sv) <= 2000) ? 1 : 8;
	} else {
	value_ptr = "";
	value_len = 0;
	phs->indp = -1;
	phs->ftype = 1;
	}

	/* this will change to odndra sometime      */
/*    if (obndrv(imp_sth->cda, (text*)ph_name, -1,
	    (ub1*)value_ptr, (sword)value_len,
	    phs->ftype, -1, &phs->indp,
	    (text*)0, -1, -1)) {
	D_imp_dbh_from_sth;
	ora_error(h, &imp_dbh->lda, imp_sth->cda->rc, "obndrv failed");
	return 1;
	} */
	return 0;
}


int
dbd_describe(h, imp_sth)
	 SV *h;
	 imp_sth_t *imp_sth;
{
  sb1 *cbuf_ptr;
  int t_cbufl=0;
  sb4 f_cbufl[MAX_COLS];
  int i;
  int field_info_loop;
  int length;
  FILE *fp = DBILOGFP;
  struct sqlda *demodesc;
  int desc_count;
  char result[65536];
  int loop;
  int type;
  int len;
  char *tt;

  if ( dbis->debug >= 2 )
	  warn( "In: DBD::NET::dbd_describe()\n" );

  if (imp_sth->done_desc ) {
	  if ( dbis->debug >= 2 )
	  warn( "In: DBD::NET::dbd_describe()'done_desc = true\n" );
	  return 1;	/* success, already done it */
	}
  imp_sth->done_desc = 1;
  if (!sql_fetch(imp_sth->cursoridx, result, &f_cbufl[1], &t_cbufl,
	  &desc_count)) {
  	return 1; /* no more data */
  }

  field_info_loop = 0;
  imp_sth->row_num++;

  /* Assign the number of fields to fbh_num */

  /* allocate field buffers	*/
  Newz(42, imp_sth->fbh,      imp_sth->fbh_num + 1, imp_fbh_t);
  /* allocate a buffer to hold all the column names */
  Newz(42, imp_sth->fbh_cbuf, t_cbufl + imp_sth->fbh_num + 1, char);

  cbuf_ptr = (sb1*)imp_sth->fbh_cbuf;

  /* Foreach row, we need to allocate some space and link the
   * - header record to it */
  tt=result;
  for(i = 1 ; i <= imp_sth->fbh_num ; ++i ) {
	  imp_fbh_t *fbh = &imp_sth->fbh[i];
	  fbh->imp_sth = imp_sth;
	  fbh->cbuf    = cbuf_ptr;
	  fbh->cbufl   = f_cbufl[i];

	  if ( dbis->debug >= 2 )
	  warn( "In: DBD::NET::dbd_describe'LinkRow: %d\n", i );

	  strcpy( cbuf_ptr, tt );
	  if ( tt[0] == '\0' ) {
		if ( dbis->debug >= 2 )
	      warn( "Looks like a NULL!\n" );
		fbh->cbuf[0] = '\0';
		fbh->cbufl = 0;
		fbh->rlen = fbh->cbufl;
	  } else {
		fbh->rlen = strlen (tt);
	  }

	  if ( dbis->debug >= 2 )
	  warn( "Name: %s\t%i\n", fbh->cbuf, fbh->cbufl );
	  fbh->cbuf[fbh->cbufl] = '\0'; /* ensure null terminated */
	  cbuf_ptr += fbh->cbufl + 1;   /* increment name pointer	*/


	  /* Now define the storage for this field data.		*/
	  /* Hack buffer length value */

	  fbh->dsize = fbh->cbufl;

	  /* Is it a LONG, LONG RAW, LONG VARCHAR or LONG VARRAW?	*/
	  /* If so we need to implement oraperl truncation hacks.	*/
	  /* This may change in a future release.			*/

	  fbh->bufl = fbh->dsize + 1;

	  /* for the time being we fetch everything as strings	*/
	  /* that will change (IV, NV and binary data etc)	*/
	  /* currently we use an sv, later we'll use an array     */

	  if ( dbis->debug >= 2 )
	  warn( "In: DBD::NET::dbd_describe'newSV\n" );
	  fbh->sv = newSV((STRLEN)fbh->bufl);

	  if ( dbis->debug >= 2 )
	  warn( "In: DBD::NET::dbd_describe'SvUPGRADE\n" );
	  (void)SvUPGRADE(fbh->sv, SVt_PV);

	  if ( dbis->debug >= 2 )
	  warn( "In: DBD::NET::dbd_describe'SvREADONLY_ON\n" );
	  SvREADONLY_on(fbh->sv);

	  if ( dbis->debug >= 2 )
	  warn( "In: DBD::NET::dbd_describe'SvPOK_only\n" );
	  (void)SvPOK_only(fbh->sv);

	  if ( dbis->debug >= 2 )
	  warn( "In: DBD::NET::dbd_describe'SvPVX\n" );
	  fbh->buf = (ub1*)SvPVX(fbh->sv);
	  tt+=strlen(tt)+1;
   }
  if ( dbis->debug >= 3 ) {
	   printf( "Entering imp_sth->fbh test cycle\n" );
	   for(i = 1 ; i <= imp_sth->fbh_num /* && imp_sth->cda->rc!=10 */ ; ++i ) {

	    imp_fbh_t *fbh = &imp_sth->fbh[i];

	    printf( "In: DBD::NET::dbd_describe'FBHDump[%d]: %s\t%d\n",
		    i, fbh->cbuf, fbh->rlen );
	 }
	}
  if ( dbis->debug >= 2 )
	  warn( "Out: DBD::NET::dbd_describe()\n" );
  return 0;
}

SV *
dbd_st_readblob(sth, field, offset, len, destsv)
	SV *sth;
	int field;
	long offset;
	long len;
	SV *destsv;
{
	D_imp_sth(sth);
	ub4 retl;
	SV *bufsv;

	if (destsv) {               /* write to users buffer        */
	bufsv = SvRV(destsv);
	sv_setpvn(bufsv,"",0);  /* ensure it's writable string  */
	SvGROW(bufsv, len+1);   /* SvGROW doesn't do +1 itself  */
	} else {
	bufsv = newSV((STRLEN)len);     /* allocate new buffer  */
	}

	/* Sadly, even though retl is a ub4, oracle will cap the    */
	/* value of retl at 65535 even if more was returned!        */
	/* This is according to the OCI manual for Oracle 7.0.      */
	/* Once again Oracle causes us grief. How can we tell what  */
	/* length to assign to destsv? We do have a compromise: if  */
	/* retl is exactly 65535 we assume that all data was read.  */
	SvCUR_set(bufsv, (retl == 65535) ? len : retl);
	*SvEND(bufsv) = '\0'; /* consistent with perl sv_setpvn etc */

	return sv_2mortal(bufsv);
}

int
dbd_st_finish(sth)
	SV *sth;
{
	D_imp_sth(sth);
	/* Cancel further fetches from this cursor.                 */
	/* We don't close the cursor till DESTROY.                  */
	/* The application may re execute it.                       */
/* LOOK INTO   if (DBIc_ACTIVE(imp_sth) ) {
	do_error();
	return 0;
	} */
	DBIc_ACTIVE_off(imp_sth);
	return 1;
}

void
dbd_st_destroy(sth)
	SV *sth;
{
	D_imp_sth(sth);
	D_imp_dbh_from_sth;
	if (DBIc_ACTIVE(imp_dbh) /* && oclose(imp_sth->cda) */ ) {
	  }

	if ( dbis->debug >= 2 )
	warn( "In: DBD::NET::dbd_st_destroy, calling free_cursor(%d)\n",
	    imp_sth->cursoridx );
/*
	Need to free up resources so the cursor can be used again.
*/
	free_cursor (imp_sth->cursoridx);

	if ( dbis->debug >= 2 )
	warn( "In: DBD::NET::dbd_st_destroy, back from free_cursor\n" );

	/* XXX free contents of imp_sth here */
	DBIc_IMPSET_off(imp_sth);
}

int
dbd_st_STORE(sth, keysv, valuesv)
	SV *sth;
	SV *keysv;
	SV *valuesv;
{
	D_imp_sth(sth);
	STRLEN kl;
	char *key = SvPV(keysv,kl);
	SV *cachesv = NULL;
	int on = SvTRUE(valuesv);

	if (kl==8 && strEQ(key, "ora_long")){
	imp_sth->long_buflen = SvIV(valuesv);

	} else if (kl==9 && strEQ(key, "ora_trunc")){
	imp_sth->long_trunc_ok = on;

	} else {
	return FALSE;
	}
	if (cachesv) /* cache value for later DBI 'quick' fetch? */
	hv_store((HV*)SvRV(sth), key, kl, cachesv, 0);
	return TRUE;
}


SV *
dbd_st_FETCH(sth, keysv)
	SV *sth;
	SV *keysv;
{
	D_imp_sth(sth);
	STRLEN kl;
	char *key = SvPV(keysv,kl);
	int i;
	SV *retsv = NULL;
	/* Default to caching results for DBI dispatch quick_FETCH  */
	int cacheit = TRUE;

	if (!imp_sth->done_desc && dbd_describe(sth, imp_sth)) {
	return Nullsv;  /* dbd_describe called do_error()       */
	}

	i = imp_sth->fbh_num;

	if (kl==11 && strEQ(key, "ora_lengths")){
	AV *av = newAV();
	retsv = newRV((SV*)av);
	while(--i >= 0)
	    av_store(av, i, newSViv((IV)imp_sth->fbh[i].dsize));

	} else if (kl==9 && strEQ(key, "ora_types")){
	AV *av = newAV();
	retsv = newRV((SV*)av);
	while(--i >= 0)
	    av_store(av, i, newSViv(imp_sth->fbh[i].dbtype));

	} else if (kl==9 && strEQ(key, "NumParams")){
	HV *bn = imp_sth->bind_names;
	retsv = newSViv( (bn) ? HvKEYS(bn) : 0 );

	} else if (kl==4 && strEQ(key, "NAME")){
	AV *av = newAV();
	retsv = newRV((SV*)av);
	while(--i >= 0)
/*            av_store(av, i, newSVpv((char*)imp_sth->fbh[i].cbuf,0)); */
	      av_store(av, i, newSVpv(imp_sth->fbh[i].cbuf,0));

	} else {
	return Nullsv;
	}
	if (cacheit) { /* cache for next time (via DBI quick_FETCH) */
	hv_store((HV*)SvRV(sth), key, kl, retsv, 0);
	(void)SvREFCNT_inc(retsv);      /* so sv_2mortal won't free it  */
	}
	return sv_2mortal(retsv);
}

/*---------------------------------------------------*
 * Function:    free_cursor
 *
 * Purpose:     frees/closes/deallocates/removes/obliterates ...
 *
 * Arguments:   cursor index
 *
 * Returns:     status
 *---------------------------------------------------*/

static void free_cursor(sqc)
int sqc;
{
}

/*---------------------------------------------------
 * Function:    iqclose
 *
 * Purpose:    closes cursor
 *
 * Arguments:    cursor index
 *
 * Returns:    void
 *---------------------------------------------------*/

static void iqclose(sqc)
int sqc;
{
}

/*---------------------------------------------------
 * Function:    iqfree
 *
 * Purpose:    frees closed cursor
 *
 * Arguments:    cursor index
 *
 * Returns:    void
 *
 *---------------------------------------------------*/

static void iqfree(sqc)
int sqc;
{
}

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