#include <mysql.h>
#include <NdbApi.hpp>
// Used for cout
#include <stdio.h>
#include <iostream.h>

namespace std {} using namespace std;

static void run_application(MYSQL &, Ndb_cluster_connection &);

#define PRINT_ERROR(code,msg) \
  cout << "Error in " << __FILE__ << ", line: " << __LINE__ \
            << ", code: " << code \
            << ", msg: " << msg << "." << endl
#define MYSQLERROR(mysql) { \
  PRINT_ERROR(mysql_errno(&mysql),mysql_error(&mysql)); \
  exit(-1); }
#define APIERROR(error) { \
  PRINT_ERROR(error.code,error.message); \
  exit(-1); }

int main()
{
  // ndb_init must be called first
  ndb_init();

  // connect to mysql server and cluster and run application
  {
    // Object representing the cluster
    Ndb_cluster_connection cluster_connection;

    // Connect to cluster management server (ndb_mgmd)
    if (cluster_connection.connect(4 /* retries               */,
				   5 /* delay between retries */,
				   1 /* verbose               */))
    {
      cout << "Cluster management server was not ready within 30 secs.\n";
      exit(-1);
    }

    // Optionally connect and wait for the storage nodes (ndbd's)
    if (cluster_connection.wait_until_ready(30,0) < 0)
    {
      cout << "Cluster was not ready within 30 secs.\n";
      exit(-1);
    }

    // connect to mysql server
    MYSQL mysql;
    if ( !mysql_init(&mysql) ) {
      cout << "mysql_init failed\n";
      exit(-1);
    }
    if ( !mysql_real_connect(&mysql, "localhost", "root", "", "",
			     3306, "/tmp/mysql.sock", 0) )
      MYSQLERROR(mysql);
    
    // run the application code
    run_application(mysql, cluster_connection);
  }

  ndb_end(0);

  cout << "\nTo drop created table use:\n"
	    << "echo \"drop table mytablename\" | mysql test_db_1 -u root\n";

  return 0;
}

static void create_table(MYSQL &);

static void run_application(MYSQL &mysql,
			    Ndb_cluster_connection &cluster_connection)
{
  /********************************************
   * Connect to database via mysql-c          *
   ********************************************/
  mysql_query(&mysql, "CREATE DATABASE test_db_1");
  if (mysql_query(&mysql, "USE test_db_1") != 0) MYSQLERROR(mysql);
  create_table(mysql);

  /********************************************
   * Connect to database via NdbApi           *
   ********************************************/
  // Object representing the database
  Ndb myNdb( &cluster_connection, "test_db_1" );
  if (myNdb.init()) APIERROR(myNdb.getNdbError());

  const NdbDictionary::Dictionary* myDict= myNdb.getDictionary();
  const NdbDictionary::Table *myTable= myDict->getTable("mytablename");

  if (myTable == NULL) 
    APIERROR(myDict->getNdbError());

  // Populate table
  for (int i = 0; i < 10; i++)
  {
    NdbTransaction *myTransaction= myNdb.startTransaction();
    if (myTransaction == NULL) APIERROR(myNdb.getNdbError());
    
    NdbOperation *myOperation= myTransaction->getNdbOperation(myTable);
    if (myOperation == NULL) APIERROR(myTransaction->getNdbError());
    
    myOperation->insertTuple();
    myOperation->equal("ATTR1", i);
    myOperation->setValue("ATTR2", 0);

    if (myTransaction->execute(NdbTransaction::Commit) == -1)
      APIERROR(myTransaction->getNdbError());
    
    myNdb.closeTransaction(myTransaction);
  }

  // Do interpretedUpdateTuple with interpret_exit_nok operation
  {
      for (int i = 0; i < 10; i++)
      {
          NdbTransaction *updateTrans = myNdb.startTransaction();
          if (updateTrans == NULL) APIERROR(myNdb.getNdbError());

          NdbOperation *updateOp = updateTrans->getNdbOperation(myTable);
          if (updateTrans == NULL) APIERROR(updateTrans->getNdbError());

          if (updateOp->interpretedUpdateTuple() < 0)
              APIERROR(updateOp->getNdbError());
          if (updateOp->equal("ATTR1", i) < 0)
              APIERROR(updateOp->getNdbError());
          if (updateOp->interpret_exit_nok() < 0)
              APIERROR(updateOp->getNdbError());

          // The following execute always returns -1 because of the interpret_exit_nok, so, do not check the return code here.
          updateTrans->execute(NdbTransaction::Commit);
          //if (updateTrans->execute(NdbTransaction::Commit) == -1)
          //    APIERROR(updateTrans->getNdbError());

          myNdb.closeTransaction(updateTrans);
      }
  }

  // Clean table
  {
      for (int i = 0; i < 10; i++)
      {
          NdbTransaction *myTransaction= myNdb.startTransaction();
          if (myTransaction == NULL) APIERROR(myNdb.getNdbError());
          
          NdbOperation *myOperation= myTransaction->getNdbOperation(myTable);
          if (myOperation == NULL) APIERROR(myTransaction->getNdbError());
    
          myOperation->deleteTuple();
          myOperation->equal("ATTR1", i);

          if (myTransaction->execute(NdbTransaction::Commit) == -1)
              APIERROR(myTransaction->getNdbError());
    
          myNdb.closeTransaction(myTransaction);
      }
  }

}

/*********************************************************
 * Create a table named mytablename if it does not exist *
 *********************************************************/
static void create_table(MYSQL &mysql)
{
  if (mysql_query(&mysql, 
		  "CREATE TABLE"
		  "  mytablename"
		  "    (ATTR1 INT UNSIGNED NOT NULL PRIMARY KEY,"
		  "     ATTR2 INT UNSIGNED NOT NULL)"
		  "  ENGINE=NDB"))
    MYSQLERROR(mysql);
}
