#include "pgsql.h"
#include "pgsql_wrapper.h"
#include "clog.h"
#include <safestring.h>
#include <string>
#include <iostream>
#include <cassert>
#include <cstdarg>
#include <cstring>
#include <cstdlib>

void load_db_record( const char * table, CVars &rec, const char * where, ... )
{
  logfmt( FLOG_MAX, "void load_db_record( const char * table, CVars &rec, const char * where, ... )");

  size_t  buf_size = 512;
  char   *buffer   = new char[buf_size];

  va_list argPtr;
  size_t length;
  std::string cmd;

  while (true) {
    if (!buffer) {
      assert(buffer); // to stop when debugging
      cmd = std::string("<alloc problem>", 16);
      break;
    }
    va_start(argPtr, where);
    length = vsnprintf(buffer, buf_size, where, argPtr);
    va_end(argPtr);

    if (length >= 0 && length < buf_size - 1) {
      // string fits into current buffer
      cmd = std::string(buffer, length);
      break;
    }
    // otherwise resize buffer :
    buf_size *= 2;
    //        fprintf(stderr, "Reallocating vstrf-buffer to size=%u\n", buf_size);
    delete [] buffer;
    buffer = new char[buf_size];
  }

  const PGresult* res = pgsql_query("SELECT * FROM %s WHERE %s", table, cmd.c_str() );
  
  if( buffer ) delete [] buffer;

  if (!res || pgsql_num_rows(res)!=1) return;

  map<const char*,const char*>f = pgsql_fetch_assoc(res, 0);
  logfmt( FLOG_MAX, "load_db_record(%s) num_cols=%d", table, f.size() );
  if (f.empty()) {
    return;
  }

  map<const char *,const char *>::iterator it;
  for(it = f.begin(); it != f.end(); it++) {
    rec[ it->first ] = it->second;
  }
}

// This function now supports binary data
long load_db_record( CVars &rec, const char * query, ... )
{
  logfmt( FLOG_MAX, "long load_db_record( CVars &rec, const char * query, ... )");

  size_t  buf_size = 512;
  char   *buffer   = new char[buf_size];

  va_list argPtr;
  size_t length;
  std::string cmd;

  while (true) {
    if (!buffer) {
      assert(buffer); // to stop when debugging
      cmd = std::string("<alloc problem>", 16);
      break;
    }
    va_start(argPtr, query);
    length = vsnprintf(buffer, buf_size, query, argPtr);
    va_end(argPtr);

    if (length >= 0 && length < buf_size - 1) {
      // string fits into current buffer
      cmd = std::string(buffer, length);
      break;
    }
    // otherwise resize buffer :
    buf_size *= 2;
    //        fprintf(stderr, "Reallocating vstrf-buffer to size=%u\n", buf_size);
    delete [] buffer;
    buffer = new char[buf_size];
  }

  const PGresult* res = pgsql_query("%s", cmd.c_str() );

  if( buffer ) delete [] buffer;

  if (!res || pgsql_num_rows(res)!=1) return -1l;

  map<const char*,const char*>f = pgsql_fetch_assoc(res, 0);
  logfmt( FLOG_MAX, "load_db_record(%s) num_cols=%d", cmd.c_str(), f.size() );
  if (f.empty()) {
    return 0l;
  }

  map<const char *,const char *>::iterator it;
  for(it = f.begin(); it != f.end(); it++) {
    rec[ it->first ] = it->second;
  }
  return 1l;
}


#define MAX_BINARY_PARS 20

long insert_db_record( int mode, const char *table, const char *seq, CVars &rec )
{
  CVars::const_iterator i;
  char cmd[128]; /*cols[1024], vals[1024]; cols[0] = 0; vals[0] = 0;*/
  bool first = true;
  string cols, vals;
  int bind_i = 0;
  long int sz[MAX_BINARY_PARS]; bzero( sz, sizeof(sz) );  // provides binding for all binary columns simultaneously
  int binary_i = 0;

  logfmt( FLOG_MAX, "insert_db_record()" );

  snprintf( cmd, sizeof(cmd), "INSERT INTO %s (%%s) VALUES (%%s)", table );

  for ( i=rec.begin(); i != rec.end(); i++ )
  {
    NEED_ESC;
//    logfmt( FLOG_MAX, "'%s': i->second.valid()=%d, db()=%d", i->first.c_str(), i->second.valid(), i->second.db() );

    if ( (DBS_VALID & mode) && !i->second.valid() ) continue;
    if ( (DBS_STREAM & mode ) && !i->second.db() ) continue;
    //logfmt( FLOG_MAX, "insert_db_record(): %s = %s", i->first.c_str(), i->second.c_str() );

    cols += string(first?"":",") + i->first;
    if ( !first ) vals += ",";

    if ( i->second.binary() )
    {
/*
      vals += '?';
      sz[binary_i] = i->second.size();
      char *data = (char*)i->second.data();

      //logfmt( FLOG_MAX, "saving binary parameter '%s', len = %d", i->first.c_str(), i->second.size() );

      SQLBindParameter( st.hstmt, ++bind_i, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_VARBINARY,  sz[binary_i], 0, data, sz[binary_i], &sz[binary_i] );

      if ( binary_i++ == MAX_BINARY_PARS )
      {
        logfmt( FLOG_MAX, "Exceeded maximum allowed binary parameters" );
        return 0;
      }
*/
      throw err("Binary parameters are not supported");
    }
    else
      vals += string("'") + esc(i->second.c_str()) + "'";

    /*strcatf( cols, sizeof(cols), "%s%s", first?"":",", i->first.c_str() );
    strcatf( vals, sizeof(vals), "%s'%s'", first?"":",", i->second.c_str() );*/

    first = false;
  }

  int res = pgsql_exec( cmd, cols.c_str(), vals.c_str() );

  return curr_val( seq );
}


void update_db_record( int mode, const char * table, CVars &rec, long id, const char * where, ... ) throw ( bad_parameter )
{
  va_list ap;
  va_start( ap, where );
  v_update_db_record( mode, table, rec, "id", id, where, ap );
  va_end( ap );
}


void update_db_record( int mode, const char * table, CVars &rec, const char *index, long id, const char * where, ... ) throw ( bad_parameter )
{
  va_list ap;
  va_start( ap, where );
  v_update_db_record( mode, table, rec, index, id, where, ap );
  va_end( ap );
}

void v_update_db_record( int mode, const char * table, CVars &rec, const char *index, long id, const char * where, va_list ap ) throw ( bad_parameter )
{
  logfmt( FLOG_MAX, "update_db_record()" );
  int bind_i = 0;

  if ( !id ) throw bad_parameter( rec, "id" );

  CVars::const_iterator i;
  char cmd[128];
  bool first = true;
  string cols;
  char * wherestr;

  snprintf( cmd, sizeof(cmd), "UPDATE %s SET %%s WHERE %s=%lu AND %%s", table, index, id );

  for ( i=rec.begin(); i != rec.end(); i++ )
  {
    NEED_ESC;
    if ( !i->second.valid() ) continue;
    if ( (DBS_STREAM & mode) && !i->second.db() ) continue;

    cols += string(first?"":",") + i->first + "=";

    if ( ! i->second.binary() )
      cols += string("'") + esc(i->second.c_str()) + "'";
    else
    {
/*
      cols += "?";
      long int sz = i->second.size();
      char *data = (char*)i->second.data();

      SQLBindParameter( st.hstmt, ++bind_i, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_VARBINARY,  sz, 0, data, sz, &sz );
*/
      throw err("Binary data is not supported");
    }

    first = false;
  }

  if ( where )
  {
    int size = vsnprintf( NULL, 0, where, ap ) + 1;
    wherestr = (char*)malloc( size );
    if ( !wherestr ) throw err("malloc() error");
    vsnprintf( wherestr, size, where, ap );
  }

  int rows = pgsql_exec( cmd, cols.c_str(), where?wherestr:"true" );
  if ( where )
    free( wherestr );

  if ( rows!=1 ) throw bad_parameter( rec, "id" );
}

long curr_val( const char *seq )
{
  long size = 20+strlen(seq);
  char cmd[size];
  snprintf( cmd, size, "SELECT currval('%s')", seq );
  logfmt( FLOG_MAX, "%s", cmd);
  const PGresult* res = pgsql_query( cmd );
  if (res && pgsql_num_rows(res)>0) {
    vector<const char *>f = pgsql_fetch_row(res,0);
    if (!f.empty() && f.size()>0) {
      return atol(f[0]);
    } 
  }
  logfmt( FLOG_MAX, "SQL error" );
  return -1l;
}

//----------------------------  class CEscape ----------------

char * CEscape::New( int sz )
{
  if ( ESCAPE_MAX_VARS==n )
  {
    logfmt( FLOG_MAX, "CEscape::New() Maximum number of variables reached" );
    return NULL;
  }

  vars[n] = new char[sz];
  n++;

  return vars[n-1];
}

CEscape::CEscape( char * st )
{
  n = 0;
  this->st = st;
}

CEscape::~CEscape()
{
  for ( int i=0; i<n; i++ )
    delete vars[i];
  n = 0;
}

char * CEscape::Escape( const char * param )
{
  if ( !param ) return NULL;
  int len = this->EscapeLength(param);
  return this->EscapeReal( param, New(len), len );
}

int CEscape::EscapeLength( const char * par )
{
  if ( !par ) return 0;

  int i, len = strlen(par), extra = 0;

  for ( i=0; i<len; i++ )
    if ( par[i]=='\'' || par[i]=='\\' )
      extra++;

  return len+extra+1;
}

char * CEscape::EscapeReal( const char * cmd, char * _esc, int sz )
{
  char * c, * c1, * d, *dend;
  char * esc = _esc?_esc:this->esc;

  if (!sz) sz = sizeof(this->esc);

  c = (char*)cmd; esc[0] = 0; d = esc; dend = d+sz;

  if ( !cmd ) return esc;

//  LOG( cmd, LOG_MAX );

  while ( c<cmd+strlen(cmd) )
  {
    c1 = c + strcspn( c, "'\\" ); // apostrophe and backslash

    if ( !*c1 )
    {
      strsafecpy( d, c, dend-d );
      break;
    }

    strnsafecpy( d, c, c1-c, dend-d );
    d += strlen(d);
    strcatf( d, dend-d, "\\%c", *c1 );
    d += strlen(d);
    c = c1+1;
  }

//  LOG( esc, LOG_MAX );

  return esc;
}
//---------------------------------------------------------------------

