/*************************************************************************
*									 *
*	 YAP Prolog 							 *
*									 *
*	Yap Prolog was developed at NCCUP - Universidade do Porto	 *
*									 *
* Copyright L.Damas, V.S.Costa and Universidade do Porto 1985-1997	 *
*									 *
**************************************************************************
*									 *
* File:		myddas_odbc.c						 *
* Last rev:	22/03/05						 *
* mods:									 *
* comments:	Predicates for comunicating with ODBC drivers   	 *
*									 *
*************************************************************************/

#if defined MYDDAS_ODBC

#if !defined(ODBCVER)
typedef void *SQLHDBC;
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Yap.h"
#include "Yatom.h"
#include "myddas.h"
#include "../myddas_util.h"
#include "cut_c.h"
#include <sql.h>
#include <sqlucode.h>

/* Return enviromment identifier*/
SQLHENV myddas_util_get_odbc_enviromment(SQLHDBC);

static Int null_id = 0;

static Int c_db_odbc_connect(USES_REGS1);
static Int c_db_odbc_disconnect(USES_REGS1);
static Int c_db_odbc_number_of_fields(USES_REGS1);
static Int c_db_odbc_get_attributes_types(USES_REGS1);
static Int c_db_odbc_query(USES_REGS1);
static Int c_db_odbc_row(USES_REGS1);
static Int c_db_odbc_row_cut(USES_REGS1);
static Int c_db_odbc_get_fields_properties(USES_REGS1);
static Int c_db_odbc_number_of_fields_in_query(USES_REGS1);

static int odbc_error(SQLSMALLINT type, SQLHANDLE hdbc, char *msg,
                      char *print) {
  SQLCHAR SqlState[6], Msg[SQL_MAX_MESSAGE_LENGTH];
  SQLINTEGER NativeError;
  SQLSMALLINT i = 1, MsgLen;

  SQLGetDiagRec(type, hdbc, i, SqlState, &NativeError, Msg, sizeof(Msg),
                &MsgLen);
  fprintf(stderr,
          "%% error in SQLConnect: %s got error code %s\n%% SQL Message: %s\n",
          print, SqlState, Msg);
  return FALSE;
}

static int SQLALLOCHANDLE(SQLSMALLINT HandleType, SQLHANDLE hdbc,
                          SQLHANDLE *outHandle, char *print) {
  SQLRETURN retcode;

  retcode = SQLAllocHandle(HandleType, hdbc, outHandle);
  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO) {
    return odbc_error(HandleType, hdbc, "SQLAllocHandle(ENV)", print);
  }
  return TRUE;
}

static int SQLSETENVATTR(SQLHENV henv, SQLINTEGER att, SQLPOINTER p,
                         SQLINTEGER len, char *print) {
  SQLRETURN retcode;

  retcode = SQLSetEnvAttr(henv, att, p, len);
  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO) {
    return odbc_error(SQL_HANDLE_ENV, henv, "SQLSetEnvAttr", print);
  }
  return TRUE;
}

static int SQLCONNECT(SQLHDBC hdbc, SQLCHAR *driver, SQLCHAR *user,
                      SQLCHAR *password, char *print) {
  SQLRETURN retcode;

  retcode = SQLConnect(hdbc, driver, SQL_NTS, user, SQL_NTS, password, SQL_NTS);
  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_DBC, hdbc, "SQLConnect", print);
  return TRUE;
}

static int SQLEXECDIRECT(SQLHSTMT StatementHandle, SQLCHAR *StatementText,
                         char *print) {
  SQLRETURN retcode;
  retcode = SQLExecDirect(StatementHandle, StatementText, SQL_NTS);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_STMT, StatementHandle, "SQLExecDirect", print);
  return TRUE;
}

static int SQLDESCRIBECOL(SQLHSTMT sth, SQLSMALLINT colno, SQLCHAR *colname,
                          SQLSMALLINT bflength, SQLSMALLINT *nmlengthp,
                          SQLSMALLINT *dtptr, SQLULEN *colszptr,
                          SQLSMALLINT *ddptr, SQLSMALLINT *nullableptr,
                          char *print) {
  SQLRETURN retcode;
  retcode = SQLDescribeCol(sth, colno, colname, bflength, nmlengthp, dtptr,
                           colszptr, ddptr, nullableptr);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_STMT, sth, "SQLDescribeCol", print);
  return TRUE;
}

static int SQLSETCONNECTATTR(SQLHDBC hdbc, SQLINTEGER attr, SQLPOINTER vptr,
                             SQLINTEGER slen, char *print) {
  SQLRETURN retcode;
  retcode = SQLSetConnectAttr(hdbc, attr, vptr, slen);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_STMT, hdbc, "SQLSetConnectAttr", print);
  return TRUE;
}

static int SQLBINDCOL(SQLHSTMT sthandle, SQLUSMALLINT colno, SQLSMALLINT tt,
                      SQLPOINTER tvptr, SQLLEN blen, SQLLEN *strl,
                      char *print) {
  SQLRETURN retcode;
  retcode = SQLBindCol(sthandle, colno, tt, tvptr, blen, strl);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_STMT, sthandle, "SQLBindCol", print);
  return TRUE;
}

static int SQLNUMRESULTCOLS(SQLHSTMT sthandle, SQLSMALLINT *ncols,
                            char *print) {
  SQLRETURN retcode;
  retcode = SQLNumResultCols(sthandle, ncols);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_STMT, sthandle, "SQLNumResultCols", print);
  return TRUE;
}

static int SQLCLOSECURSOR(SQLHSTMT sthandle, char *print) {
  SQLRETURN retcode;
  retcode = SQLCloseCursor(sthandle);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_STMT, sthandle, "SQLCloseCursor", print);
  return TRUE;
}

#define SQLFETCH(A, print)                                                     \
  {                                                                            \
    SQLRETURN retcode;                                                         \
    retcode = SQLFetch(A);                                                     \
    if (retcode == SQL_NO_DATA)                                                \
      break;                                                                   \
    if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO) {          \
      printf("Error in SQLFETCH: %s\n", print);                                \
      return FALSE;                                                            \
    }                                                                          \
  }

static int SQLGETDATA(SQLHSTMT sthandle, SQLUSMALLINT Col_or_Param_Num,
                      SQLSMALLINT TargetType, SQLPOINTER TargetValuePtr,
                      SQLLEN BufferLength, SQLLEN *StrLen_or_IndPtr,
                      char *print) {
  SQLRETURN retcode;
  retcode = SQLGetData(sthandle, Col_or_Param_Num, TargetType, TargetValuePtr,
                       BufferLength, StrLen_or_IndPtr);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_STMT, sthandle, "SQLGetData", print);
  return TRUE;
}

static int SQLDISCONNECT(SQLHSTMT sthandle, char *print) {
  SQLRETURN retcode;
  retcode = SQLDisconnect(sthandle);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_DBC, sthandle, "SQLDisconnect", print);
  return TRUE;
}

static int SQLFREEHANDLE(SQLSMALLINT HandleType, SQLHANDLE Handle,
                         char *print) {
  SQLRETURN retcode;
  retcode = SQLFreeHandle(HandleType, Handle);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(HandleType, Handle, "SQLDisconnect", print);
  return TRUE;
}

static int SQLPRIMARYKEYS(SQLHSTMT StatementHandle, SQLCHAR *CatalogName,
                          SQLSMALLINT NameLength1, SQLCHAR *SchemaName,
                          SQLSMALLINT NameLength2, SQLCHAR *TableName,
                          SQLSMALLINT NameLength3, char *print) {
  SQLRETURN retcode;
  retcode = SQLPrimaryKeys(StatementHandle, CatalogName, NameLength1,
                           SchemaName, NameLength2, TableName, NameLength3);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_STMT, StatementHandle, "SQLPrimaryKeys",
                      print);
  return TRUE;
}

/********************************************
 NOT IN USE
static int SQLGETTYPEINFO(SQLHSTMT      StatementHandle,
                          SQLSMALLINT   DataType,
                          char *          print)
{
  SQLRETURN retcode;
  retcode = SQLGetTypeInfo(StatementHandle, DataType);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_STMT, StatementHandle, "SQLGetTypeInfo",
print);
  return TRUE;
}
********************************************/

static int SQLCOLATTRIBUTE(SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber,
                           SQLUSMALLINT FieldIdentifier,
                           SQLPOINTER CharacterAttributePtr,
                           SQLSMALLINT BufferLength,
                           SQLSMALLINT *StringLengthPtr,
                           SQLLEN *NumericAttributePtr, char *print) {
  SQLRETURN retcode;
  retcode = SQLColAttribute(StatementHandle, ColumnNumber, FieldIdentifier,
                            CharacterAttributePtr, BufferLength,
                            StringLengthPtr, NumericAttributePtr);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return odbc_error(SQL_HANDLE_STMT, StatementHandle, "SQLColAttribute",
                      print);
  return TRUE;
}

/* Verificar tipo de dados*/
#define IS_SQL_INT(FIELD)                                                      \
  FIELD == SQL_DECIMAL || FIELD == SQL_NUMERIC || FIELD == SQL_SMALLINT ||     \
      FIELD == SQL_INTEGER || FIELD == SQL_TINYINT || FIELD == SQL_BIGINT

#define IS_SQL_FLOAT(FIELD)                                                    \
  FIELD == SQL_FLOAT || FIELD == SQL_DOUBLE || FIELD == SQL_REAL

static Int c_db_odbc_connect(USES_REGS1) {
  Term arg_driver = Deref(ARG1);
  Term arg_user = Deref(ARG2);
  Term arg_passwd = Deref(ARG3);
  Term arg_conn = Deref(ARG4);

  MYDDAS_UTIL_CONNECTION new = NULL;

  const char *driver = AtomName(AtomOfTerm(arg_driver));
  const char *user = AtomName(AtomOfTerm(arg_user));
  const char *passwd = AtomName(AtomOfTerm(arg_passwd));

  SQLHENV henv;
  SQLHDBC hdbc;

  /*Allocate environment handle */
  if (!SQLALLOCHANDLE(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv, "connect"))
    return FALSE;
  /* Set the ODBC version environment attribute */
  if (!SQLSETENVATTR(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0,
                     "connect"))
    return FALSE;
  /* Allocate connection handle */
  if (!SQLALLOCHANDLE(SQL_HANDLE_DBC, henv, &hdbc, "connect"))
    return FALSE;
  /* Set login timeout to 6 seconds. */
  if (!SQLSETCONNECTATTR(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)6, 0, "connect"))
    return FALSE;
  /* Connect to data source */
  if (!SQLCONNECT(hdbc, (SQLCHAR *)driver, (SQLCHAR *)user, (SQLCHAR *)passwd,
                  "connect"))
    return FALSE;
  if (!Yap_unify(arg_conn, MkIntegerTerm((Int)(hdbc))))
    return FALSE;
  else {
    /* Criar um novo no na lista de ligacoes*/
    // new = add_connection(&TOP,hdbc,henv);
    new = myddas_util_add_connection(hdbc, henv, MYDDAS_ODBC);
    if (new == NULL) {
      fprintf(stderr, "Error: could not allocate list memory\n");
      return FALSE;
    }
    return TRUE;
  }
}

/* db_query: SQLQuery x ResultSet x Arity x BindList x Connection */
static Int c_db_odbc_query(USES_REGS1) {
  Term arg_sql_query = Deref(ARG1);
  Term arg_result_set = Deref(ARG2);
  Term arg_arity = Deref(ARG3);
  Term arg_bind_list = Deref(ARG4);
  Term arg_conn = Deref(ARG5);

  SQLCHAR *sql = (SQLCHAR *)AtomName(AtomOfTerm(arg_sql_query));

  SQLHDBC hdbc = (SQLHDBC)(IntegerOfTerm(arg_conn));
  SQLHSTMT hstmt;
  SQLSMALLINT type;
  Int arity;
  Int i;

  /*Allocate an handle for the query*/
  if (!SQLALLOCHANDLE(SQL_HANDLE_STMT, hdbc, &hstmt, "db_query"))
    return FALSE;
  /* Executes the query*/
  if (!SQLEXECDIRECT(hstmt, sql, "db_query"))
    return FALSE;

  if (IsNonVarTerm(arg_arity)) {
    arity = IntegerOfTerm(arg_arity);

    char *bind_space = NULL;

    // const Int functor_arity=3;
    const Short functor_arity = 3;
    Functor functor = Yap_MkFunctor(Yap_LookupAtom("bind"), functor_arity);
    Term properties[functor_arity];

    Term head, list = arg_bind_list;

    SQLULEN ColumnSizePtr;
    SQLLEN *data_info = NULL;

    for (i = 1; i <= arity; i++) {
      head = HeadOfTerm(list);
      list = TailOfTerm(list);

      if (!SQLDESCRIBECOL(hstmt, i, NULL, 0, NULL, &type, &ColumnSizePtr, NULL,
                          NULL, "db_query"))
        return FALSE;

      /* +1 because of '\0' */
      bind_space = malloc(sizeof(char) * (ColumnSizePtr + 1));
      data_info = malloc(sizeof(SQLINTEGER));
      if (!SQLBINDCOL(hstmt, i, SQL_C_CHAR, bind_space, (ColumnSizePtr + 1),
                      data_info, "db_query")) {
        return FALSE;
      }

      properties[0] = MkIntegerTerm((Int)bind_space);
      properties[2] = MkIntegerTerm((Int)data_info);

      if (IS_SQL_INT(type))
        properties[1] = MkAtomTerm(Yap_LookupAtom("integer"));
      else if (IS_SQL_FLOAT(type))
        properties[1] = MkAtomTerm(Yap_LookupAtom("real"));
      else
        properties[1] = MkAtomTerm(Yap_LookupAtom("string"));

      Yap_unify(head, Yap_MkApplTerm(functor, functor_arity, properties));
      continue;
    }
  }

  if (!Yap_unify(arg_result_set, MkIntegerTerm((Int)hstmt))) {
    if (!SQLCLOSECURSOR(hstmt, "db_query"))
      return FALSE;
    if (!SQLFREEHANDLE(SQL_HANDLE_STMT, hstmt, "db_query"))
      return FALSE;
    return FALSE;
  }
  return TRUE;
}

static Int c_db_odbc_number_of_fields(USES_REGS1) {
  Term arg_relation = Deref(ARG1);
  Term arg_conn = Deref(ARG2);
  Term arg_fields = Deref(ARG3);

  const char *relation = AtomName(AtomOfTerm(arg_relation));

  SQLHDBC hdbc = (SQLHDBC)(IntegerOfTerm(arg_conn));
  SQLHSTMT hstmt;

  char sql[256];
  SQLSMALLINT number_fields;

  sprintf(sql, "SELECT column_name from INFORMATION_SCHEMA.COLUMNS where "
               "table_name = \'%s\' GROUP BY column_name, dtd_identifier ORDER "
               "BY CAST(dtd_identifier AS INTEGER)",
          relation);

  if (!SQLALLOCHANDLE(SQL_HANDLE_STMT, hdbc, &hstmt, "db_number_of_fields"))
    return FALSE;
  if (!SQLEXECDIRECT(hstmt, (SQLCHAR *)sql, "db_number_of_fields"))
    return FALSE;

  /* Calcula o numero de campos*/
  number_fields = 0;
  while (TRUE) {
    SQLFETCH(hstmt, "db_number_of_fields");
    number_fields++;
  }

  if (!SQLCLOSECURSOR(hstmt, "db_number_of_fields"))
    return FALSE;
  if (!SQLFREEHANDLE(SQL_HANDLE_STMT, hstmt, "db_number_of_fields"))
    return FALSE;

  if (!Yap_unify(arg_fields, MkIntegerTerm(number_fields)))
    return FALSE;
  return TRUE;
}

/* db_get_attributes_types: RelName x Connection -> TypesList */
static Int c_db_odbc_get_attributes_types(USES_REGS1) {
  Term arg_relation = Deref(ARG1);
  Term arg_conn = Deref(ARG2);
  Term arg_types_list = Deref(ARG3);

  const char *relation = AtomName(AtomOfTerm(arg_relation));
  SQLHDBC hdbc = (SQLHDBC)(IntegerOfTerm(arg_conn));
  SQLHSTMT hstmt;

  char sql[256];
  Term head, list;
  list = arg_types_list;

  sprintf(sql, "SELECT column_name,data_type from INFORMATION_SCHEMA.COLUMNS "
               "WHERE table_name = \'%s\' GROUP BY column_name, dtd_identifier "
               "ORDER BY CAST(dtd_identifier AS INTEGER)",
          relation);

  if (!SQLALLOCHANDLE(SQL_HANDLE_STMT, hdbc, &hstmt, "db_get_attributes_types"))
    return FALSE;
  if (!SQLEXECDIRECT(hstmt, (SQLCHAR *)sql, "db_get_attributes_types"))
    return FALSE;

  while (TRUE) {
    SQLFETCH(hstmt, "db_get_attributes_types");

    /* Tentar fazer de uma maneira que a gente consiga calcular o tamanho que o
     nome do campo vai ocupar, assim podemos alocar memoria dinamicamente*/
    sql[0] = '\0';
    if (!SQLGETDATA(hstmt, 1, SQL_C_CHAR, sql, 256, NULL,
                    "db_get_attributes_types"))
      return FALSE;

    head = HeadOfTerm(list);
    Yap_unify(head, MkAtomTerm(Yap_LookupAtom(sql)));
    list = TailOfTerm(list);
    head = HeadOfTerm(list);
    list = TailOfTerm(list);

    sql[0] = '\0';
    if (!SQLGETDATA(hstmt, 2, SQL_C_CHAR, sql, 256, NULL,
                    "db_get_attributes_types"))
      return FALSE;

    if (strncmp(sql, "smallint", 8) == 0 || strncmp(sql, "int", 3) == 0 ||
        strncmp(sql, "mediumint", 9) == 0 || strncmp(sql, "tinyint", 7) == 0 ||
        strncmp(sql, "bigint", 6) == 0 || strcmp(sql, "year") == 0)
      Yap_unify(head, MkAtomTerm(Yap_LookupAtom("integer")));
    else if (strcmp(sql, "float") == 0 || strncmp(sql, "double", 6) == 0 ||
             strcmp(sql, "real") == 0)
      Yap_unify(head, MkAtomTerm(Yap_LookupAtom("real")));
    else
      Yap_unify(head, MkAtomTerm(Yap_LookupAtom("string")));
  }

  if (!SQLCLOSECURSOR(hstmt, "db_get_attributes_types"))
    return FALSE;
  if (!SQLFREEHANDLE(SQL_HANDLE_STMT, hstmt, "db_get_attributes_types"))
    return FALSE;
  return TRUE;
}

/* db_disconnect */
static Int c_db_odbc_disconnect(USES_REGS1) {
  Term arg_conn = Deref(ARG1);

  SQLHDBC conn = (SQLHDBC)(IntegerOfTerm(arg_conn));
  SQLHENV henv = myddas_util_get_odbc_enviromment(conn);

  if ((myddas_util_search_connection(conn)) != NULL) {
    myddas_util_delete_connection(conn);
    /* More information about this process on
       msdn.microsoft.com*/
    if (!SQLDISCONNECT(conn, "db_disconnect"))
      return FALSE;
    if (!SQLFREEHANDLE(SQL_HANDLE_DBC, conn, "db_disconnect"))
      return FALSE;
    if (!SQLFREEHANDLE(SQL_HANDLE_ENV, henv, "db_disconnect"))
      return FALSE;

    return TRUE;
  } else
    return FALSE;
}

static Int c_db_odbc_row_cut(USES_REGS1) {

  SQLHSTMT hstmt = (SQLHSTMT)IntegerOfTerm(EXTRA_CBACK_CUT_ARG(Term, 1));

  if (!SQLCLOSECURSOR(hstmt, "db_row_cut"))
    return FALSE;
  if (!SQLFREEHANDLE(SQL_HANDLE_STMT, hstmt, "db_row_cut"))
    return FALSE;

  return TRUE;
}

static int release_list_args(Term arg_list_args, Term arg_bind_list,
                             const char *error_msg) {
  Term list = arg_list_args;
  Term list_bind = arg_bind_list;

  while (IsPairTerm(list_bind)) {
    Term head_bind = HeadOfTerm(list_bind);

    list = TailOfTerm(list);
    list_bind = TailOfTerm(list_bind);

    free((char *)IntegerOfTerm(ArgOfTerm(1, head_bind)));
    free((SQLINTEGER *)IntegerOfTerm(ArgOfTerm(3, head_bind)));
  }
  return TRUE;
}

/* db_row: ResultSet x BindList x ListOfArgs -> */
static Int c_db_odbc_row(USES_REGS1) {
  Term arg_result_set = Deref(ARG1);
  Term arg_bind_list = Deref(ARG2);
  Term arg_list_args = Deref(ARG3);

  SQLHSTMT hstmt = (SQLHSTMT)IntegerOfTerm(arg_result_set);

  /* EXTRA_CBACK_ARG(ARIDADE,LOCAL_ONDE_COLOCAR_VALOR)*/
  EXTRA_CBACK_ARG(3, 1) = (CELL)MkIntegerTerm((Int)hstmt);

  Term head, list, null_atom[1];
  Term head_bind, list_bind;

  SQLRETURN retcode = SQLFetch(hstmt);
  if (retcode == SQL_NO_DATA) {
    if (!SQLCLOSECURSOR(hstmt, "db_row"))
      return FALSE;
    if (!SQLFREEHANDLE(SQL_HANDLE_STMT, hstmt, "db_row"))
      return FALSE;
    if (!release_list_args(arg_list_args, arg_bind_list, "db_row")) {
      return FALSE;
    }

    cut_fail();
    return FALSE;
  }
  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO) {
    printf("erro no SQLFETCH number of fields\n");
    return FALSE;
  }

  char *bind_value = NULL;
  Term type;

  list = arg_list_args;
  list_bind = arg_bind_list;
  SQLINTEGER *data_info = NULL;

  while (IsPairTerm(list_bind)) {
    head = HeadOfTerm(list);
    list = TailOfTerm(list);

    head_bind = HeadOfTerm(list_bind);
    list_bind = TailOfTerm(list_bind);

    bind_value = (char *)IntegerOfTerm(ArgOfTerm(1, head_bind));
    type = ArgOfTerm(2, head_bind);
    data_info = (SQLINTEGER *)IntegerOfTerm(ArgOfTerm(3, head_bind));

    if ((*data_info) == SQL_NULL_DATA) {
      null_atom[0] = MkIntegerTerm(null_id++);
      if (!Yap_unify(head,
                     Yap_MkApplTerm(Yap_MkFunctor(Yap_LookupAtom("null"), 1), 1,
                                    null_atom)))
        continue;
    } else {

      if (!strcmp(AtomName(AtomOfTerm(type)), "integer")) {
        if (!Yap_unify(head, MkIntegerTerm(atol(bind_value))))
          continue;
      } else if (!strcmp(AtomName(AtomOfTerm(type)), "real")) {
        if (!Yap_unify(head, MkFloatTerm(atof(bind_value))))
          continue;
      } else if (!strcmp(AtomName(AtomOfTerm(type)), "string")) {
        if (!Yap_unify(head, MkAtomTerm(Yap_LookupAtom(bind_value))))
          continue;
      }
    }
  }
  return TRUE;
}

/* Mudar esta funcao de forma a nao fazer a consulta, pois
 no predicate db_sql_selet vai fazer duas vezes a mesma consutla*/
static Int c_db_odbc_number_of_fields_in_query(USES_REGS1) {
  Term arg_query = Deref(ARG1);
  Term arg_conn = Deref(ARG2);
  Term arg_fields = Deref(ARG3);

  const char *sql = AtomName(AtomOfTerm(arg_query));

  SQLHDBC hdbc = (SQLHDBC)(IntegerOfTerm(arg_conn));
  SQLHSTMT hstmt;
  SQLSMALLINT number_cols = 0;

  if (!SQLALLOCHANDLE(SQL_HANDLE_STMT, hdbc, &hstmt,
                      "db_number_of_fields_in_query"))
    return FALSE;
  if (!SQLEXECDIRECT(hstmt, (SQLCHAR *)sql, "db_number_of_fields_in_query"))
    return FALSE;

  if (!SQLNUMRESULTCOLS(hstmt, &number_cols, "db_number_of_fields_in_query"))
    return FALSE;

  if (!Yap_unify(arg_fields, MkIntegerTerm(number_cols))) {
    if (!SQLCLOSECURSOR(hstmt, "db_number_of_fields_in_query"))
      return FALSE;
    if (!SQLFREEHANDLE(SQL_HANDLE_STMT, hstmt, "db_number_of_fields_in_query"))
      return FALSE;

    return FALSE;
  }

  if (!SQLCLOSECURSOR(hstmt, "db_number_of_fields_in_query"))
    return FALSE;
  if (!SQLFREEHANDLE(SQL_HANDLE_STMT, hstmt, "db_number_of_fields_in_query"))
    return FALSE;

  return TRUE;
}

#ifdef MYDDAS_ODBC
/* This function searches the MYDDAS list for odbc connections
 If there isn't any, it returns NULL. This is a nice way to know
 if there is any odbc connections left on the list*/
SQLHENV
myddas_util_get_odbc_enviromment(SQLHDBC connection) {
  CACHE_REGS
  MYDDAS_UTIL_CONNECTION top =
      Yap_REGS.MYDDAS_GLOBAL_POINTER->myddas_top_connections;

  for (; top != NULL; top = top->next)
    if (top->connection == ((void *)connection))
      return top->odbc_enviromment;

  return NULL;
}
#endif

static Int c_db_odbc_get_fields_properties(USES_REGS1) {
  Term nome_relacao = Deref(ARG1);
  Term arg_conn = Deref(ARG2);
  Term fields_properties_list = Deref(ARG3);
  Term head, list;

  SQLCHAR *relacao = (SQLCHAR *)AtomName(AtomOfTerm(nome_relacao));
  char sql[256];
  char name[200];
  Int i;

  SQLSMALLINT num_fields = 0;
  SQLSMALLINT NullablePtr = 0;
  SQLLEN AutoIncrementPointer = 0;
  SQLHSTMT hstmt, hstmt2;
  SQLHDBC hdbc = (SQLHDBC)(IntegerOfTerm(arg_conn));

  /* LIMIT 0 -> We don't need the results of the query,
     only the information about the fields of the relation*/
  sprintf(sql, "SELECT * FROM `%s` LIMIT 0", relacao);

  /*Allocate an handle for the query*/
  if (!SQLALLOCHANDLE(SQL_HANDLE_STMT, hdbc, &hstmt,
                      "db_get_fields_properties"))
    return FALSE;
  /* Executes the query*/
  if (!SQLEXECDIRECT(hstmt, (SQLCHAR *)sql, "db_get_fields_properties"))
    return FALSE;

  Functor functor = Yap_MkFunctor(Yap_LookupAtom("property"), 4);
  Term properties[4];

  if (!SQLNUMRESULTCOLS(hstmt, &num_fields, "db_get_fields_properties"))
    return FALSE;

  list = fields_properties_list;

  SQLSMALLINT bind_prim_key;
  // rows in odbc start at 1 :)
  Short *null = (Short *)malloc(sizeof(Short) * (1 + num_fields));

  if (!SQLALLOCHANDLE(SQL_HANDLE_STMT, hdbc, &hstmt2,
                      "db_get_fields_properties"))
    return FALSE;
  /* Executes the query*/
  if (!SQLPRIMARYKEYS(hstmt2, NULL, 0, NULL, 0, relacao, SQL_NTS,
                      "db_get_fields_properties"))
    return FALSE;
  /* Associates bind value for the 5 column*/
  if (!SQLBINDCOL(hstmt2, 5, SQL_C_SSHORT, &bind_prim_key, sizeof(SQLSMALLINT),
                  NULL, "db_get_fields_properties"))
    return FALSE;

  while (1) {
    SQLFETCH(hstmt2, "db_get_fields_properties");
    null[bind_prim_key] = 1;
  }

  if (!SQLCLOSECURSOR(hstmt2, "db_get_fields_properties"))
    return FALSE;
  if (!SQLFREEHANDLE(SQL_HANDLE_STMT, hstmt2, "db_get_fields_properties"))
    return FALSE;

  for (i = 1; i <= num_fields; i++) {
    head = HeadOfTerm(list);
    name[0] = '\0';
    SQLDESCRIBECOL(hstmt, i, (SQLCHAR *)name, 200, NULL, NULL, NULL, NULL,
                   &NullablePtr, "db_get_fields_properties");

    if (!SQLCOLATTRIBUTE(hstmt, i, SQL_DESC_AUTO_UNIQUE_VALUE, NULL, 0, NULL,
                         &AutoIncrementPointer, "db_get_fields_properties"))
      return FALSE;

    properties[0] = MkAtomTerm(Yap_LookupAtom(name));

    if (NullablePtr & SQL_NULLABLE)
      properties[1] = MkIntegerTerm(1); // Can't be NULL
    else
      properties[1] = MkIntegerTerm(0);

    if (null[i] == 1)
      properties[2] = MkIntegerTerm(1); // It''s a primary key
    else
      properties[2] = MkIntegerTerm(0);

    if (AutoIncrementPointer & SQL_TRUE)
      properties[3] = MkIntegerTerm(1); // It's auto_incremented field
    else
      properties[3] = MkIntegerTerm(0);

    list = TailOfTerm(list);
    if (!Yap_unify(head, Yap_MkApplTerm(functor, 4, properties))) {
      return FALSE;
    }
  }

  if (!SQLCLOSECURSOR(hstmt, "db_get_fields_properties"))
    return FALSE;
  if (!SQLFREEHANDLE(SQL_HANDLE_STMT, hstmt2, "db_get_fields_properties"))
    return FALSE;
  return TRUE;
}

void Yap_InitMYDDAS_ODBCPreds(void) {
  /* db_connect: Host x User x Passwd x Database x Connection */
  Yap_InitCPred("c_db_odbc_connect", 4, c_db_odbc_connect, 0);

  /* db_number_of_fields: Relation x Connection x NumberOfFields */
  Yap_InitCPred("c_db_odbc_number_of_fields", 3, c_db_odbc_number_of_fields, 0);

  /* db_number_of_fields_in_query: SQLQuery x Connection x NumberOfFields */
  Yap_InitCPred("c_db_odbc_number_of_fields_in_query", 3,
                c_db_odbc_number_of_fields_in_query, 0);

  /* db_get_attributes_types: Relation x TypesList */
  Yap_InitCPred("c_db_odbc_get_attributes_types", 3,
                c_db_odbc_get_attributes_types, 0);

  /* db_query: SQLQuery x ResultSet x Connection */
  Yap_InitCPred("c_db_odbc_query", 5, c_db_odbc_query, 0);

  /* db_disconnect: Connection */
  Yap_InitCPred("c_db_odbc_disconnect", 1, c_db_odbc_disconnect, 0);

  /* db_get_fields_properties: PredName x Connnection x PropertiesList */
  Yap_InitCPred("c_db_odbc_get_fields_properties", 3,
                c_db_odbc_get_fields_properties, 0);
}

void Yap_InitBackMYDDAS_ODBCPreds(void) {

  /*  db_row: ResultSet x ListOfArgs */
  Yap_InitCPredBackCut("c_db_odbc_row", 3, sizeof(Int), c_db_odbc_row,
                       c_db_odbc_row, c_db_odbc_row_cut, 0);
}
#else

void Yap_InitMYDDAS_ODBCPreds(void) {}
void Yap_InitBackMYDDAS_ODBCPreds(void) {}

#endif

void init_odbc( void )
{
    Yap_InitMYDDAS_ODBCPreds();
    Yap_InitBackMYDDAS_ODBCPreds();
}


#ifdef _WIN32

#include <windows.h>

int WINAPI win_odbc(HANDLE hinst, DWORD reason, LPVOID reserved);

int WINAPI win_odbc(HANDLE hinst, DWORD reason, LPVOID reserved) {
  switch (reason) {
  case DLL_PROCESS_ATTACH:
    break;
  case DLL_PROCESS_DETACH:
    break;
  case DLL_THREAD_ATTACH:
    break;
  case DLL_THREAD_DETACH:
    break;
  }
  return 1;
}
#endif
/*MYDDAS_ODBC*/