/***************************************************************************

  dataset.cpp

  (c) 2000-2017 Benoît Minisini <benoit.minisini@gambas-basic.org>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.

***************************************************************************/

/**********************************************************************
 * Copyright (c) 2002, Leo Seib, Hannover
 *
 * Project: C++ Dynamic Library
 * Module: Dataset abstraction later realisation file
 * Author: Leo Seib      E-Mail: lev@almaty.pointstrike.net
 * Begin: 5/04/2002
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 **********************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
#include <time.h>
#include <iostream>
#include <string.h>

#include "dataset.h"


extern "C" {

//************* Database implementation ***************

Database::Database() {
  active = false;	// No connection yet
  error = "";//S_NO_CONNECTION;
  host = "";
  port = "";
  db = "";
  login = "";
  passwd = "";
  sequence_table = "db_sequence";
}

Database::~Database() {
  disconnect();		// Disconnect if connected to database
}

int Database::connectFull(const char *newHost, const char *newPort, const char *newDb, const char *newLogin, const char *newPasswd) {
  host = newHost;
  port = newPort;
  db = newDb;
  login = newLogin;
  passwd = newPasswd;
  return connect();
}

}


//************* Dataset implementation ***************

Dataset::Dataset() {

  db = NULL;
  haveError = active = false;
  frecno = 0;
  fbof = feof = true;
  autocommit = true;

  select_sql = "";

  fields_object = new Fields();
  
  edit_object = new Fields();
}



Dataset::Dataset(Database *newDb) {

  db = newDb;
  haveError = active = false;
  frecno = 0;
  fbof = feof = true;
  autocommit = true;

  select_sql = "";
 
  fields_object = new Fields();

  edit_object = new Fields();

}


Dataset::~Dataset() {
  update_sql.clear();
  insert_sql.clear();
  delete_sql.clear();


  delete fields_object;
  delete edit_object;
  
}


void Dataset::setSqlParams(const char *sqlFrmt, sqlType t, ...) {
  va_list ap;
  char sqlCmd[DB_BUFF_MAX+1];

  va_start(ap, t);
  vsnprintf(sqlCmd, DB_BUFF_MAX-1, sqlFrmt, ap);
  va_end(ap);
  
   switch (t) {
       case sqlSelect: set_select_sql(sqlCmd);
                       break;
       case sqlUpdate: add_update_sql(sqlCmd);
                       break;
       case sqlInsert: add_insert_sql(sqlCmd);
                       break;
       case sqlDelete: add_delete_sql(sqlCmd);
                       break;
       case sqlExec: sql = sqlCmd;
             	    break;
		    
  }    
}



void Dataset::set_select_sql(const char *sel_sql) {
 select_sql = sel_sql;
}

void Dataset::set_select_sql(const string &sel_sql) {
 select_sql = sel_sql;
}


void Dataset::parse_sql(string &sql) {
  string fpattern,by_what;
  pars.set_str(sql.c_str());
  for (uint i=0;i< fields_object->size();i++) {
    fpattern = ":OLD_"+(*fields_object)[i].props.name;
    by_what = "'"+(*fields_object)[i].val.get_asString()+"'";
    //cout << "parsing " << fpattern <<by_what<<"\n\n";
    sql = pars.replace(fpattern,by_what);
    }

  for (uint i=0;i< edit_object->size();i++) {
    fpattern = ":NEW_"+(*edit_object)[i].props.name;
    by_what = "'"+(*edit_object)[i].val.get_asString()+"'";
    sql = pars.replace(fpattern,by_what);
  }
  
//   StringList before_array, after_array;
//   int tag = 0;
//   bool eol_reached = false,
//        was_changed = false,
//        flag = false;
//   ExtString which_before, which_after;
//   ExtString bef, aft, prev_before, right_side, which_field, changed_field, f_value;

//   before_array.add(":NEW_", tag);
//   before_array.add(":OLD_", tag);

//   after_array.add(")", tag);
//   after_array.add(",", tag);
//   after_array.add(" ", tag);

//   sq.squish();
//   bef = sq.before_arr(before_array, which_before);

//   while (!(bef == prev_before)) {
// 	right_side = sq.after(which_before, flag);
// 	right_side.squish();

// 	aft = right_side.after_arr(after_array, which_after);
//         aft.squish();
//  	which_field = right_side.before(which_after);

// 	// checking whather we reach end of line
// 	if ((which_field == "\0") && (which_before != "\0")) {    			
// 		which_field = right_side;
// 		eol_reached = true;
// 		}

// 	// If new field and is in insert or edit mode - looks in edit_object
// 	if ((which_before == ":NEW_") && (which_field != "\0")) {
// 		which_field.squish();
//                 f_value.assign(fv(which_field.getChars()));
// 		f_value.addslashes();
// 		changed_field.assign("'");
// 		changed_field + f_value + "'";
// 	}
// 	else
// 	// else looking old value in the current result set
// 	   if ((which_before == ":OLD_") && (which_field != "\0")) {
// 		which_field.squish();
//                 f_value.assign(f_old(which_field.getChars()));
// 		f_value.addslashes();
// 		changed_field.assign("'");
// 		changed_field + f_value + "'";
// 		}

// 	if (!eol_reached)  {

// 		sq.assign(bef + changed_field + which_after + aft);	
// 		}
// 	else {
// 		if (!was_changed && (which_field != "\0")) {

// 			sq.assign(bef + changed_field + which_after + aft);
// 			was_changed = true;
// 			}
// 		}


// 	prev_before = bef;
// 	bef = sq.before_arr(before_array, which_before);

//   }

}


void Dataset::close(void) {

  haveError  = false;
  frecno = 0;
  fbof = feof = true;
  active = false;
}


//bool Dataset::seek(int pos=0) {
bool Dataset::seek(int pos) {
  frecno = (pos<num_rows()-1)? pos: num_rows()-1;
  frecno = (frecno<0)? 0: frecno;
  fbof = feof = (num_rows()==0)? true: false;
  return frecno;
}


void Dataset::refresh() {
  int row = frecno;				
  if ((row != 0) && active) {
    close();
    open();
    seek(row);
  }
  else open();		
}


void Dataset::first() {
  if (ds_state == dsSelect) {
    frecno = 0;
    feof = fbof = (num_rows()>0)? false : true;
  }
}

void Dataset::next() {
  if (ds_state == dsSelect) {
    fbof = false;
    if (frecno<num_rows()-1) {
      frecno++;
      feof = false;
    } else feof = true;
    if (num_rows()<=0) fbof = feof = true;
  }
}

void Dataset::prev() {
  if (ds_state == dsSelect) {
    feof = false;
    if (frecno) {
      frecno--;
      fbof = false;
    } else fbof = true;
    if (num_rows()<=0) fbof = feof = true;
  }
}

void Dataset::last() {
  if (ds_state == dsSelect) {
    frecno = (num_rows()>0)? num_rows()-1: 0;
    feof = fbof = (num_rows()>0)? false : true;
  }
}

//bool Dataset::goto_rec(int pos=1) {
bool Dataset::goto_rec(int pos) {
  if (ds_state == dsSelect) {
    return seek(pos - 1);
  }
  return false;
}


/*void Dataset::insert() {
  //cout << "insert\n\n";
   for (int i=0; i<field_count(); i++) {
     (*fields_object)[i].val = "";
     (*edit_object)[i].val = "";
     //cout <<"Insert:"<<i<<"\n\n";
   }
  ds_state = dsInsert;
}*/


void Dataset::edit() {
  if (ds_state != dsSelect) {
    cerr<<"Editing is possible only when query exists!";
    return;
  }
  for (uint i=0; i<fields_object->size(); i++) {
       (*edit_object)[i].val = (*fields_object)[i].val; 
  }
  ds_state = dsEdit;
}


void Dataset::post() {
  if (ds_state == dsInsert) make_insert();
  else if (ds_state == dsEdit) make_edit();
}


void Dataset::deletion() {
  if (ds_state == dsSelect) make_deletion();
}


bool Dataset::set_field_value(const char *f_name, const field_value &value) {
  bool found = false;
  if ((ds_state == dsInsert) || (ds_state == dsEdit)) {
      for (uint i=0; i < fields_object->size(); i++) 
	if ((*edit_object)[i].props.name == f_name) {
			     (*edit_object)[i].val = value;
			     found = true;
	}
      if (!found){
	      GB.Error("Field not found: &1",f_name);
      }
    return found;
  }
  GB.Error("Not in Insert or Edit state");
  return found;
}


const field_value & Dataset::get_field_value(const char *f_name)
{
	static field_value fv;

	if (ds_state != dsInactive)
	{
		if (ds_state == dsEdit || ds_state == dsInsert)
		{
			for (uint i = 0; i < edit_object->size(); i++)
				if ((*edit_object)[i].props.name == f_name)
					return (*edit_object)[i].val;
			GB.Error("Field not found: %s", f_name);
		}
		else
			for (uint i = 0; i < fields_object->size(); i++)
				if ((*fields_object)[i].props.name == f_name)
					return (*fields_object)[i].val;
		GB.Error("Field not found: %s", f_name);
	}

	GB.Error("Dataset state is Inactive");
	return fv;
}


const field_value & Dataset::get_field_value(int index) //const char *f_name)
{
	static field_value fv;

	if (ds_state != dsInactive)
	{
		if (ds_state == dsEdit || ds_state == dsInsert)
			return (*edit_object)[index].val;
		else
			return (*fields_object)[index].val;
	}

	GB.Error("Dataset state is Inactive");
	return fv;
}


const field_value Dataset::f_old(const char *f_name) {
  if (ds_state != dsInactive)
    for (uint i=0; i < fields_object->size(); i++) 
      if ((*fields_object)[i].props.name == f_name)
	return (*fields_object)[i].val;
  field_value fv;
  return fv;
}



void Dataset::setParamList(const ParamList &params){
  plist = params;
}


bool Dataset::locate(){
  bool result;
  if (plist.empty()) return false;

  std::map<string,field_value>::const_iterator i;
  first();
  while (!eof()) {
    result = true;
    for (i=plist.begin();i!=plist.end();++i)
      if (fv(i->first.c_str()).get_asString() == i->second.get_asString()) {
	continue;
      }
      else {result = false; break;}
    if (result) { return result;}
    next();
  }
  return false;
}

bool Dataset::locate(const ParamList &params) {
  plist = params;
  return locate();
}

bool Dataset::findNext(void) {
  bool result;
  if (plist.empty()) return false;

  std::map<string,field_value>::const_iterator i;
  while (!eof()) {
    result = true;
    for (i=plist.begin();i!=plist.end();++i)
      if (fv(i->first.c_str()).get_asString() == i->second.get_asString()) {
	continue;
      }
      else {result = false; break;}
    if (result) { return result;}
    next();
  }
  return false;
}


void Dataset::add_update_sql(const char *upd_sql){
  string s = upd_sql;
  update_sql.push_back(s);
}


void Dataset::add_update_sql(const string &upd_sql){
  update_sql.push_back(upd_sql);
}

void Dataset::add_insert_sql(const char *ins_sql){
  string s = ins_sql;
  insert_sql.push_back(s);
}


void Dataset::add_insert_sql(const string &ins_sql){
  insert_sql.push_back(ins_sql);
}

void Dataset::add_delete_sql(const char *del_sql){
  string s = del_sql;
  delete_sql.push_back(s);
}


void Dataset::add_delete_sql(const string &del_sql){
  delete_sql.push_back(del_sql);
}

void Dataset::clear_update_sql(){
  update_sql.clear();
}

void Dataset::clear_insert_sql(){
  insert_sql.clear();
}

void Dataset::clear_delete_sql(){
  delete_sql.clear();
}

int Dataset::field_count() { return fields_object->size();}
int Dataset::fieldCount() { return fields_object->size();}

const char *Dataset::fieldName(int n) {
  if ( n < field_count() && n >= 0)
    return (*fields_object)[n].props.name.c_str();
  else
    return NULL;
}

int Dataset::fieldSize(int n) {
  if ( n < field_count() && n >= 0)
    return (*fields_object)[n].props.field_len;
  else
    return 0;
}

int Dataset::fieldIndex(const char *fn) {
  int index, length;

  if (strchr(fn, (int)'.')){
     /* table name has been supplied */
     for (uint i=0; i < fields_object->size(); i++) {
      //if ((*fields_object)[i].props.name == fn)
      if (strcmp((*fields_object)[i].props.name.c_str(),fn) == 0)
	return i;
     }
  }
  else {
     for (uint i=0; i < fields_object->size(); i++) {
      index = (*fields_object)[i].props.name.find('.') + 1;
      length = (*fields_object)[i].props.name.length();
      /*printf("Field name [%s] find [%s] fn [%s]\n", 
		      (*fields_object)[i].props.name.c_str(),
		      (*fields_object)[i].props.name.substr(index, length).c_str(), fn);*/

      //if ((*fields_object)[i].props.name.substr(index, length) == fn){
      if (strcmp((*fields_object)[i].props.name.substr(index, length).c_str(),fn) == 0){
	return i;
      }
     }
  }
  return -1;
}


int Dataset::fieldType(int n) {
  if ( n < field_count() && n >= 0){
    //cout << (*fields_object)[n].val.gft();
    return (*fields_object)[n].val.get_fType();
    //return (*fields_object)[n].props.type;
  }
  else
    return 0;
}
