/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            database.cc
 *
 *  Thu Sep  6 10:59:07 CEST 2007
 *  Copyright 2007 Bent Bisballe Nyeng
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of Pracro.
 *
 *  Pracro 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 of the License, or
 *  (at your option) any later version.
 *
 *  Pracro 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 Pracro; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "database.h"

#include <config.h>

std::string protect(std::string in)
{
  std::string out;

  for(size_t i = 0; i < in.size(); i++) {
    switch(in[i]) {
    case '\'':
    case '\\':
      out.append(2, in[i]);
      break;

    case '\0':
      out.append(1, '0');
      break;
      
    default:
      out.append(1, in[i]);
      break;
    }
  }

  return out;
}

Database::Database(std::string hostname, std::string user, std::string password)
#ifndef WITHOUT_DB
  : c("host=" + hostname + " user=" + user + " password=" + password + " dbname=pracro")
#endif/*WITHOUT_DB*/
{
}

Database::~Database()
{
}

void Database::commit(std::string user,
                      std::string cpr,
                      Macro &_macro,
                      Fields &fields,
                      time_t now)
{
  //  / Create transaction ID (transaction OID?)
  // {
  //  \ Commit transaction data

  // Commit all field values using transaction ID.

  // INSERT INTO transactions VALUES('cpr', 'macro', 'version', 'timestamp', 'user')
  // Returns INSERT oid count
  // count == 1, oid is oid of newly inserted transaction.
  
  // INSERT INTO fields VALUES('oid', 'field', 'value')

  std::string version = _macro.attributes["version"];
  std::string macro = _macro.attributes["name"];
  std::stringstream timestamp; timestamp << now;

#ifndef WITHOUT_DB
  pqxx::work W(c);
#endif/*WITHOUT_DB*/

  std::string ts =
    "INSERT INTO transactions"
    " VALUES('"+protect(cpr)+"', '"+protect(macro)+"', '"+protect(version)+
    "', '"+protect(timestamp.str())+"', '"+protect(user)+"')";

  std::stringstream oid;

#ifndef WITHOUT_DB
  pqxx::result R = W.exec(ts);
  oid << R.inserted_oid();
#else
  oid << "###GENERATED_OID###";
#endif/*WITHOUT_DB*/

#ifdef WITH_DEBUG
  printf("%s\n", ts.c_str());
#endif/*WITH_DEBUG*/

  std::map< std::string, std::string >::iterator i = fields.begin();
  while(i != fields.end()) {

    std::string fs =
      "INSERT INTO fields"
      " VALUES('"+protect(oid.str())+"', '"+protect(i->first)+"', '"+protect(i->second)+"')";
    
#ifndef WITHOUT_DB
    W.exec(fs);
#endif/*WITHOUT_DB*/

#ifdef WITH_DEBUG
    printf("%s\n", fs.c_str());
#endif/*WITH_DEBUG*/
    
    i++;
  }
  
#ifndef WITHOUT_DB
  W.commit();
#endif/*WITHOUT_DB*/

#if 0
	try {
		pqxx::work W(c);
    
    // ...
    
		W.commit();
	}	catch(const std::exception &e) {
    //		throw PostgreSQLException(e.what());
	}
#endif/*0*/  
}

void Database::putJournal(std::string user,
                          std::string cpr,
                          Macro &_macro,
                          std::string resume,
                          time_t now)
{
  std::string version = _macro.attributes["version"];
  std::string macro = _macro.attributes["name"];
  std::stringstream timestamp; timestamp << now;

#ifndef WITHOUT_DB
  pqxx::work W(c);
#endif/*WITHOUT_DB*/

  std::string ts =
    "INSERT INTO journal"
    " VALUES('"+protect(cpr)+"', '"+protect(macro)+"', '"+protect(version)+
    "', '"+protect(timestamp.str())+"', '"+protect(user)+"', '"+protect(resume)+"')";

  std::stringstream oid;

#ifndef WITHOUT_DB
  pqxx::result R = W.exec(ts);
#endif/*WITHOUT_DB*/

#ifdef WITH_DEBUG
  printf("%s\n", ts.c_str());
#endif/*WITH_DEBUG*/

#ifndef WITHOUT_DB
  W.commit();
#endif/*WITHOUT_DB*/
}


std::string Database::getResume(std::string cpr,
                                std::string macro,
                                time_t oldest)
{
  std::string resume;

#ifndef WITHOUT_DB
  pqxx::work W(c);
#endif/*WITHOUT_DB*/

  std::stringstream query;
  query << "SELECT journal";
  query << " FROM journal";
  query << " WHERE cpr = '" << protect(cpr) << "'";
  query << " AND macro = '" << protect(macro) << "'";
  query << " ORDER BY timestamp;"; 

#ifdef WITH_DEBUG
  printf("%s\n", query.str().c_str());
#endif/*WITH_DEBUG*/

#ifndef WITHOUT_DB
  pqxx::result R = W.exec(query.str());
  
  pqxx::result::const_iterator ri = R.begin();
  while(ri != R.end()) {
    pqxx::result::tuple t = *ri;
    resume = t[0].c_str();
    ri++;
  }
#endif/*WITHOUT_DB*/
  
  return resume;
}


Values Database::getValues(std::string cpr,
                           Fieldnames &fields,
                           time_t oldest)
{
  Values values;
#ifndef WITHOUT_DB
  pqxx::work W(c);
#endif/*WITHOUT_DB*/

  std::stringstream query;
  query << "SELECT fields.name, fields.value, transactions.timestamp";
  query << " FROM fields, transactions";
  query << " WHERE transactions.cpr = '" << protect(cpr) << "'";
  query << " AND transactions.oid = fields.transaction";
  query << " AND transactions.timestamp >= " << oldest;

  std::vector< std::string >::iterator i = fields.begin();
  bool first = true;
  while(i != fields.end()) {
    if(first) query << " AND ( fields.name = '" << protect(*i) << "'";
    else query << " OR fields.name = '" << protect(*i) << "'";
    first = false;
    i++;
  }
  if(fields.size() > 0) query << ")";

  query << " ORDER BY transactions.timestamp"; 

#ifdef WITH_DEBUG
  printf("%s\n", query.str().c_str());
#endif/*WITH_DEBUG*/

#ifndef WITHOUT_DB
  pqxx::result R = W.exec(query.str());
  
  pqxx::result::const_iterator ri = R.begin();
  while(ri != R.end()) {
    pqxx::result::tuple t = *ri;
    
    Value v;
    v.value = t[1].c_str();
    v.timestamp = atol(t[2].c_str());
    
    if(values.find(t[0].c_str()) == values.end() ||
       (values.find(t[0].c_str()) != values.end() &&
        values[t[0].c_str()].timestamp <= v.timestamp) ) {
      values[t[0].c_str()] = v;
    }
    
    ri++;
  }
#endif/*WITHOUT_DB*/
  
  return values;
}

bool Database::checkMacro(std::string cpr,
                          std::string macro,
                          time_t oldest)
{
#ifndef WITHOUT_DB
  pqxx::work W(c);
#endif/*WITHOUT_DB*/

  std::stringstream query;
  query << "SELECT oid";
  query << " FROM transactions";
  query << " WHERE cpr = '" << protect(cpr) << "'";
  query << " AND macro = '" << protect(macro) << "'";
  query << " AND timestamp >= " << oldest;
  query << " ORDER BY timestamp"; 

#ifdef WITH_DEBUG
  printf("%s\n", query.str().c_str());
#endif/*WITH_DEBUG*/

#ifndef WITHOUT_DB
  try {
    pqxx::result R = W.exec(query.str());

    return R.size() != 0;
  } catch( ... ) {
    return false;
  }
#else
  return false;
#endif/*WITHOUT_DB*/
}

/*
-- As root:
-- #createuser -P -h localhost -U postgres
-- #createdb -U postgres -h localhost pracro

DROP DATABASE pracro;

CREATE DATABASE pracro
  WITH OWNER = pracro
       ENCODING = 'UNICODE'
       TABLESPACE = pg_default;

DROP TABLE transactions;

CREATE TABLE transactions
(
  "cpr" varchar(11),
  "macro" text,
  "version" text,
  "timestamp" bigint,
  "user" text
)
WITH OIDS;
ALTER TABLE transactions OWNER TO pracro;

DROP TABLE fields;

CREATE TABLE fields
(
  "transaction" oid,
  "name" text,
  "value" text
) 
WITH OIDS;
ALTER TABLE fields OWNER TO pracro;

-- primary key(oid) ??
*/

#ifdef TEST_DATABASE

int main()
{
  Database db("localhost", "pracro", "pracro");

  time_t now = time(NULL);
  /*
  Macro macro;
  macro.attributes["name"] = "testmacro";
  macro.attributes["version"] = "1.0";
  
  Fields fields;
  fields["themeaning"] = "42";
  fields["microsoft"] = "waste of money";

  db.commit("testuser", "1505050505", macro, fields, now);

  std::vector< std::string > fieldnames;
  fieldnames.push_back("microsoft");
  fieldnames.push_back("themeaning");

  Values results = db.getValues("1505050505", fieldnames, now);
  Values::iterator i = results.begin();
  while(i != results.end()) {
    Value v = i->second;
    printf("%s -> %s (%u)\n", i->first.c_str(), v.value.c_str(), (unsigned int)v.timestamp);
    i++;
  }
  */
  printf(db.getResume("1505050505", "B.2.5", now - 60 * 60 * 24).c_str());
}

#endif/*TEST_DATABASE*/