/*********************************************************************************
 * Package   : c-sqld-mysql.c
 * Author    : Hans Oesterholt-Dijkema.
 * Copyright : HOD 2004/2005.
 * License   : The Elemental Programming Artistic License.
 * CVS       : $Id: c-sqld-mysql.c,v 1.4 2006/01/04 20:15:18 HansOesterholt Exp $
 *********************************************************************************/

#include "sqlid.h"

#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#ifdef MZSCHEME
#  include <escheme.h>
#  include "c-threads.c"
#endif

#include <mysql.h>

/*#define DBG(a) a*/
#define DBG(a) 


/* 
=head1 Name

SQLD-MYSQL - C Part

=head1 Author

Hans Oesterholt-Dijkema

=head1 Copyright/License

(c) 2004 Hans Oesterholt-Dijkema, LGPL.

=head1 Version

$Id: c-sqld-mysql.c,v 1.4 2006/01/04 20:15:18 HansOesterholt Exp $
*/

typedef struct {
  MYSQL *myData;
} mysql_t;

typedef struct {
  mysql_t         *handle;
  int              nrows;
  int              nfields;
  Scheme_Object  **fields;
} mysql_query_t;


#ifdef WIN32
#define ALLOCA(a)         _alloca(a)
#define FREE_ALLOCA(p)
#else
#define ALLOCA(a)         _alloca(a)
#define FREE_ALLOCA(p)
#endif


static
int c_mysql_client_version(void) 
{
  return mysql_get_client_version();
}

static
int c_mysql_server_version(MYSQL *myData)
{
  return mysql_get_server_version(myData);
}


static Scheme_Object *gc_strdup(char *s) /* utf8 s */
{
  if (s==NULL) { s=""; }
  return scheme_make_utf8_string(s);
}

static Scheme_Object *MYSQL_Type=NULL;
static Scheme_Object *Query_Type=NULL;

#define init_types() \
  if (MYSQL_Type==NULL) { \
     MYSQL_Type=scheme_make_byte_string("MySQL"); \
     Query_Type=scheme_make_byte_string("MySQL_Query"); \
  }

#define SCHEME_STR_VAL(a)   SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(a))
#define STRVAL(a)           SCHEME_STR_VAL(a)
#define EQ_CTYPE(cobj,type) (SCHEME_CPTR_TYPE(cobj)==type)
#define IS_STRINGP(obj)     SCHEME_CHAR_STRINGP(obj)


char *get_dsn_part(char *part,char *dsn)
{
  char *p=strstr(dsn,part);
  char *val=NULL;
  if (p!=NULL) {
    p+=strlen(part);
    if (p[0]=='\0' || isspace(p[0])) {
    }
    else {
      char *b=p;
      val=p;
      for(;p[0]!='\0' && !isspace(p[0]);p++);
      { 
	char c=p[0];
	p[0]='\0';
	val=(char *) scheme_malloc(strlen(b)+1);
	strcpy(val,b);
	p[0]=c;
      }
    }
  }

  return val;
}
 


#define FUNC "c_mysql_open"
static
Scheme_Object *c_mysql_open(int argc, Scheme_Object **argv)
{
  mysql_t *handle=(mysql_t *) scheme_malloc(sizeof(mysql_t));

  init_types();

  if (!IS_STRINGP(argv[0])) {
    scheme_wrong_type(FUNC,"string",0,argc,argv);
  }

  {
    char *host=NULL;
    char *user=NULL;
    char *passwd=NULL;
    char *db=NULL;
    unsigned int port=0;
    char *dsn=STRVAL(argv[0]);

    host=get_dsn_part("host=",dsn);
    user=get_dsn_part("user=",dsn);
    passwd=get_dsn_part("passwd=",dsn);
    db=get_dsn_part("db=",dsn);
    if (db==NULL) {
      db=get_dsn_part("dbname=",dsn);
    }

    {
      char *p=get_dsn_part("port=",dsn);
      if (p!=NULL) { port=atoi(p); }
    }

    DBG(printf("dsn=%s\n",dsn));
    DBG(printf("host=%s, user=%s, passwd=%s, db=%s, port=%d\n",host,user,passwd,db,port));

    handle->myData=mysql_init(NULL);

    {

      MYSQL *conn=mysql_real_connect(handle->myData,
				     host,
				     user,
				     passwd,
				     db,
				     port,
				     NULL,
				     0
				     );

      return scheme_make_cptr(handle,MYSQL_Type);
    }
  }
}
#undef FUNC

#define FUNC "c_mysql_close"
static
Scheme_Object *c_mysql_close(int argc, Scheme_Object **argv)
{
  init_types();

  if (!SCHEME_CPTRP(argv[0])) {
    scheme_wrong_type(FUNC,"MySQL",0,argc,argv);
  }
  else if (!EQ_CTYPE(argv[0],MYSQL_Type)) { 
    scheme_wrong_type(FUNC,"MySQL",0,argc,argv);
  }

  {
    mysql_t *handle=(mysql_t *) SCHEME_CPTR_VAL(argv[0]);

    if (handle->myData==NULL) {
      scheme_signal_error(FUNC ": Invalid mysql handle (MYSQL: NULL pointer)");
    }

    mysql_close(handle->myData);
    handle->myData=NULL;
  }

  return scheme_void;
}
#undef FUNC


#define FUNC "c_mysql_escape"
static
Scheme_Object *c_mysql_escape(int argc, Scheme_Object **argv)
{
  Scheme_Object *obj;

  init_types();
  
  if (!SCHEME_CPTRP(argv[0])) {
    scheme_wrong_type(FUNC,"MySQL",0,argc,argv);
  }
  else if (!EQ_CTYPE(argv[0],MYSQL_Type)) { 
    scheme_wrong_type(FUNC,"MySQL",0,argc,argv);
  }

  if (!IS_STRINGP(argv[1])) {
    scheme_wrong_type(FUNC,"string",1,argc,argv);
  }

  {
    mysql_t *handle=(mysql_t *) SCHEME_CPTR_VAL(argv[0]);
    char    *string=STRVAL(argv[1]);
    int      len=strlen(string);
    char    *to=(char *) malloc((len+1)*2);
    
    if (handle->myData==NULL) {
      scheme_signal_error(FUNC ": Invalid mysql handle (MYSQL: NULL pointer)");
    }
    mysql_real_escape_string(handle->myData,to,string,len);
    
    obj=gc_strdup(to);

    free(to);
  }

  return obj;
}
#undef FUNC

static
char *trim(char *s)
{
  int N=strlen(s)-1;

  for(;N>=0 && isspace(s[N]);N--);
  s[N+1]='\0';

  {
    char *p=s;
    for(;p[0]!='\0' && isspace(p[0]);p++);
    return p;
  }
}

typedef struct {
  mysql_t   *handle;
  char      *query;
  int        result;
  int        ready;
} mysql_exec_t;


static
int query(void *data)
{
  mysql_exec_t *H=(mysql_exec_t *) data;

  {
    mysql_t *handle=H->handle;
    char *query=H->query;
    int   i;
    int   N=strlen(query);
    char *q;
    char *b;
    int   in_string=0;
    int   R;
      
    
    b=q=trim(query);
    R=0;
      
    for(i=0;R==0 && i<N;i++) {
      if (q[i]=='\'') { in_string=!in_string; }
      if (q[i]==';' && !in_string) {
	char c=q[i];
	
	q[i]='\0';
	R=mysql_real_query(handle->myData,b,strlen(b));
	q[i]=c;
	b=&q[i+1];
      }
    }
    
    if (R==0 && b!=&q[i]) {
      R=mysql_real_query(handle->myData,b,strlen(b));
    }

    H->result=R;
  }
    

  H->ready=(1==1);

  return 0;
}

static
int query_ready(Scheme_Object *data)
{
  mysql_exec_t *H=(mysql_exec_t *) data;
  return H->ready;
}


#define FUNC "c_mysql_query"
static
Scheme_Object *c_mysql_query(int argc, Scheme_Object **argv) //void *db,char *query)
{
  Scheme_Object *obj;

  init_types();

  if (!SCHEME_CPTRP(argv[0])) {
    scheme_wrong_type(FUNC,"MySQL",0,argc,argv);
  }
  else if (!EQ_CTYPE(argv[0],MYSQL_Type)) { 
    scheme_wrong_type(FUNC,"MySQL",0,argc,argv);
  }

  if (!IS_STRINGP(argv[1])) {
    scheme_wrong_type(FUNC,"string",1,argc,argv);
  }

  {
    mysql_t *handle=(mysql_t *) SCHEME_CPTR_VAL(argv[0]);
    int      R=0;

    if (handle->myData==NULL) {
      scheme_signal_error(FUNC ": Invalid mysql handle (MYSQL: NULL pointer)");
    }

    {
      mysql_exec_t *H=(mysql_exec_t *) malloc(sizeof(mysql_exec_t));
      H->handle=handle;
      H->query=SCHEME_STR_VAL(argv[1]);
      H->result=0;
      H->ready=0;

      {
	t_c_thread_id id;

	id=c_thread_create(query,(void *) H);
	scheme_block_until(query_ready,NULL,(Scheme_Object *) H,-1);
	c_thread_join(id);
      }

      R=H->result;

      free(H);
    }
      
    /* Query has been done, fetch results */

    {
      mysql_query_t *q=(mysql_query_t *) scheme_malloc(sizeof(mysql_query_t));
      MYSQL_RES *res=mysql_store_result(handle->myData);

      q->handle=handle;
      if (res==NULL) { q->nrows=0; q->nfields=0; q->fields=NULL; }
      else {
	q->nrows=mysql_num_rows(res);
	q->nfields=mysql_num_fields(res);
	DBG(printf("Query result: %d, %d\n",q->nrows,q->nfields));
	q->fields=(Scheme_Object **) scheme_malloc(sizeof(Scheme_Object *)*q->nfields*q->nrows);

	{
	  MYSQL_ROW       row;
	  unsigned long  *lengths;
	  int             i,k,M=q->nfields,N=q->nrows;
	  Scheme_Object **fields=q->fields;

	  for(i=0;i<N;i++) {
	    row=mysql_fetch_row(res);
	    lengths=mysql_fetch_lengths(res);
	    DBG(printf("Row fetched; %p\n",row));
	    for(k=0;k<M;k++) {
	      if (row[k]==NULL) { 
		fields[i*N+k]=scheme_make_utf8_string("");
	      }
	      else {
		fields[i*M+k]=scheme_make_utf8_string(row[k]);
		DBG(printf("field %d,%d set\n",i,k));
	      }
	      DBG(printf("set  %d,%d\n",i,k));
	    }
	  }
	  
	  DBG(printf("free resultset\n"));
	  mysql_free_result(res);
	}
      }

      obj=scheme_make_cptr(q,Query_Type);
    }

  }
  
  return obj;
}
#undef FUNC


#define FUNC "c_mysql_nrows"
static
Scheme_Object *c_mysql_nrows(int argc, Scheme_Object **argv)
{
  Scheme_Object *obj;

  init_types();

  if (!SCHEME_CPTRP(argv[0])) {
    scheme_wrong_type(FUNC,"MySQL_Query",0,argc,argv);
  }
  else if (!EQ_CTYPE(argv[0],Query_Type)) { 
    scheme_wrong_type(FUNC,"MySQL_Query",0,argc,argv);
  }

  {
    mysql_query_t *q=SCHEME_CPTR_VAL(argv[0]);
    obj=scheme_make_integer(q->nrows);
  }

  return obj;
}
#undef FUNC

#define FUNC "c_mysql_nfields"
static
Scheme_Object *c_mysql_nfields(int argc, Scheme_Object **argv)
{
  Scheme_Object *obj;

  init_types();

  if (!SCHEME_CPTRP(argv[0])) {
    scheme_wrong_type(FUNC,"MySQL_Query",0,argc,argv);
  }
  else if (!EQ_CTYPE(argv[0],Query_Type)) { 
    scheme_wrong_type(FUNC,"MySQL_Query",0,argc,argv);
  }

  {
    mysql_query_t *q=SCHEME_CPTR_VAL(argv[0]);
    obj=scheme_make_integer(q->nfields);
  }

  return obj;
}
#undef FUNC

#define FUNC "c_mysql_field"
static
Scheme_Object *c_mysql_field(int argc, Scheme_Object **argv)
{
  Scheme_Object *obj;

  init_types();

  if (!SCHEME_CPTRP(argv[0])) {
    scheme_wrong_type(FUNC,"MySQL_Query",0,argc,argv);
  }
  else if (!EQ_CTYPE(argv[0],Query_Type)) { 
    scheme_wrong_type(FUNC,"MySQL_Query",0,argc,argv);
  }

  if (!SCHEME_INTP(argv[1])) {
    scheme_wrong_type("c-mysql-field","integer",1,argc,argv);
  }
  if (!SCHEME_INTP(argv[2])) {
    scheme_wrong_type("c-mysql-field","integer",2,argc,argv);
  }

  {
    mysql_query_t *q=SCHEME_CPTR_VAL(argv[0]);
    int row=SCHEME_INT_VAL(argv[1]);
    int field=SCHEME_INT_VAL(argv[2]);
    
    if (row>=q->nrows) {
      scheme_signal_error(FUNC ": row out of bound");
    }
    {
      int N=q->nfields;
      if (field>=N) {
	scheme_signal_error(FUNC ": field out of bound");
      }

      DBG(printf("q->nrows=%d, q->nfields=%d, q->fields=%p (%d,%d)\n",
		 q->nrows,q->nfields,q->fields, row, field));

      obj=q->fields[row*N+field];
    }
  }

  return obj;
}
#undef FUNC


#define FUNC "c_mysql_lasterr"
static
Scheme_Object *c_mysql_lasterr(int argc, Scheme_Object **argv)
{
  Scheme_Object *obj;

  init_types();
  
  if (!SCHEME_CPTRP(argv[0])) {
    scheme_wrong_type(FUNC,"MySQL",0,argc,argv);
  }
  else if (!EQ_CTYPE(argv[0],MYSQL_Type) && !EQ_CTYPE(argv[0],Query_Type)) { 
    scheme_wrong_type(FUNC,"MySQL",0,argc,argv);
  }

  if (EQ_CTYPE(argv[0],MYSQL_Type)) {
    mysql_t *handle=(mysql_t *) SCHEME_CPTR_VAL(argv[0]);
    if (handle->myData==NULL) {
      scheme_signal_error(FUNC ": Invalid mysql handle (MYSQL: NULL pointer)");
    }
    obj=gc_strdup((char *) mysql_error(handle->myData));
  }
  else {
    mysql_query_t *q=SCHEME_CPTR_VAL(argv[0]);
    mysql_t       *handle=q->handle;

    if (handle->myData==NULL) {
      scheme_signal_error(FUNC ": Invalid mysql handle (MYSQL: NULL pointer)");
    }

    obj=gc_strdup((char *) mysql_error(q->handle->myData));
  }
    
  return obj;
}
#undef FUNC


static 
Scheme_Object *c_mysql_version(int argc, Scheme_Object **argv)
{
  return scheme_make_integer(c_mysql_client_version());
}


Scheme_Object *scheme_reload(Scheme_Env *env)
{
  Scheme_Env *menv;
  Scheme_Object *proc;

  menv = scheme_primitive_module(scheme_intern_symbol("c-sqld-mysql"),env);
  proc = scheme_make_prim_w_arity(c_mysql_version, "c-mysql-version", 0, 0);
  scheme_add_global("c-mysql-version", proc, menv);
  proc = scheme_make_prim_w_arity(c_mysql_open, "c-mysql-open", 1, 1);
  scheme_add_global("c-mysql-open", proc, menv);
  proc = scheme_make_prim_w_arity(c_mysql_close, "c-mysql-close", 1, 1);
  scheme_add_global("c-mysql-close", proc, menv);
  proc = scheme_make_prim_w_arity(c_mysql_query, "c-mysql-query", 2, 2);
  scheme_add_global("c-mysql-query", proc, menv);
  proc = scheme_make_prim_w_arity(c_mysql_nrows, "c-mysql-nrows", 1, 1);
  scheme_add_global("c-mysql-nrows", proc, menv);
  proc = scheme_make_prim_w_arity(c_mysql_nfields, "c-mysql-nfields", 1, 1);
  scheme_add_global("c-mysql-nfields", proc, menv);
  proc = scheme_make_prim_w_arity(c_mysql_field, "c-mysql-field", 3, 3);
  scheme_add_global("c-mysql-field", proc, menv);
  proc = scheme_make_prim_w_arity(c_mysql_lasterr, "c-mysql-lasterr", 1, 1);
  scheme_add_global("c-mysql-lasterr", proc, menv);
  proc = scheme_make_prim_w_arity(c_mysql_escape, "c-mysql-escape", 2, 2);
  scheme_add_global("c-mysql-escape", proc, menv);
  scheme_finish_primitive_module(menv);

  return scheme_void;
}



Scheme_Object *scheme_initialize(Scheme_Env *env)
{
  return scheme_reload(env);
}

Scheme_Object *scheme_module_name(void)
{
  return scheme_intern_symbol("c-sqld-mysql");
}

/*=verbatim

=cut
*/


