/*
 Copyright (c) 2011, Oracle and/or its affiliates. All rights
 reserved.
 
 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; version 2 of
 the License.
 
 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 St, Fifth Floor, Boston, MA
 02110-1301  USA
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <memcached/extension_loggers.h>

#define HARNESS 1
#include "all_tests.h"

/***  These tests require a running cluster. 
      Some of them require the ndbmemcache.demo_table to exist.  
      If a particular test-id is supplied, run one test verbosely;
      otherwise run all tests and indicate pass or fail.
***/      

char *connect_string;   /* exported to tests */

EXTENSION_LOGGER_DESCRIPTOR *logger;
                       
Ndb_cluster_connection * connect(const char *);

int list_tests(void);
int usage(char *);

int main(int argc, char *argv[]) {
  connect_string = NULL;
  int test_number = -1;
  char * test_name = 0;
  int optc;
  int req_level;
  int npass = 0, nfail = 0;

  Ndb_cluster_connection * conn = NULL;
  QueryPlan *plan = NULL;
  Ndb *db = NULL;
  
  /* Options */
  while((optc = getopt(argc, argv, "hlc:t:")) != -1) {
    switch(optc) {
      case 'c':
        connect_string = optarg;
        break;
      case 't':
        test_number = atoi(optarg);
        if(test_number > 0) test_number -= 1;
        else test_name = optarg;
        break;
      case 'l':
        return list_tests();
        break;
      case 'h':
      default:
        return usage(argv[0]);
        break;
    } 
  }
    
  /* If a test name was given, find it by number */
  if(test_name) {
    for(int i = 0; all_tests[i].name; i++) {
      if(! strcmp(test_name, all_tests[i].name)) {
        test_number = i;
        break;
      }
    }
    if(test_number == -1) {
      printf("Test \"%s\" not found.\n", test_name);
      exit(1);
    }  
  }
  
  /* Determine requirements level for this test */
  if(test_number >= 0)
    req_level = all_tests[test_number].requires;
  else
    req_level = REQ_DEMO_TABLE;  // highest level

  ndb_init();
  DEBUG_INIT(NULL, 0);
  
  if(req_level >= REQ_NDB_CONNECTION) { 
    printf("Connecting to cluster (%s)\n", connect_string);
    conn = connect(connect_string);
    db = new Ndb(conn);
    db->init(4);
  }
  
  if(req_level >= REQ_DEMO_TABLE) {    
    TableSpec spec("ndbmemcache.demo_table", "mkey", "string_value");
    spec.cas_column = "cas_value";
    spec.math_column = "math_value";
    plan = new QueryPlan(db, &spec);
  }

  if(test_number >= 0) {   /* Run a particular test */
    printf("%s\n", all_tests[test_number].name);
    int r = all_tests[test_number].function(plan, db, 1);  //verbose
    if(r) {
      printf(" [FAIL] at line %d\n", r); nfail++;
    } else {
      printf(" [PASS]\n"); npass++; 
    }
  }
  else {                  /* Run all tests */
    for(int i = 0; all_tests[i].name; i++) {
      if(all_tests[i].enabled) {
        printf("%-30s", all_tests[i].name);
        int r = all_tests[i].function(plan, db, 0);   // quiet
        printf(" %s\n", r ? "[FAIL]" : "[PASS]");
        if(r) nfail++; 
        else npass++;
      }
    }
    printf("\nTotals:  %d pass        ...    %d fail\n", npass, nfail);
  }
  
  exit((nfail > 0));
}


int list_tests() {
  printf("\n");
  printf("No. %-30s %-20s %-10s\n", "Name", "Requires","Enabled");
  printf("----------------------------------------------------------------\n");
  for(int i = 0; all_tests[i].name; i++)
    printf("%d   %-30s %-20s %-10s\n", i+1, all_tests[i].name,
           requirements[all_tests[i].requires],
           all_tests[i].enabled ? "Yes" : "No");
  printf("\n");
  return 0;
}


int usage(char *prog) {
  printf("\n");
  printf("usage %s [options]\n", prog);
  printf("options: \n");
  printf("  -c connectstring  : specify NDB connect-string\n");
  printf("  -t test-id        : run a particular test by number or name\n");
  printf("  -l                : list tests\n");
  printf("  -h                : help\n");
  printf("\n");
  
  return 0;
}


Ndb_cluster_connection * connect(const char *connectstring) {
  int conn_retries = 0;
  Ndb_cluster_connection *c = new Ndb_cluster_connection(connectstring);
  
  /* Set name that appears in the cluster log file */
  c->set_name("unit_test");
  
  while(1) {
    conn_retries++;
    int r = c->connect(2,1,0);
    if(r == 0)         // success 
      break;
    else if(r == -1)   // unrecoverable error
      return NULL;
    else if (r == 1) { // recoverable error
      if(conn_retries == 5)
        return NULL;
      else 
        sleep(1);
    }
  }
  
  int ready_nodes = c->wait_until_ready(5, 5);
  if(ready_nodes < 0) {
    printf("Timeout waiting for cluster \"%s\" to become ready (%d).\n", 
            connectstring, ready_nodes);
    return NULL;
  }
  
  printf("Connected to \"%s\" as node id %d.\n", connectstring, c->node_id());
  if(ready_nodes > 0) printf("Only %d storage nodes are ready.\n", ready_nodes);
  
  return c;
}


void delete_row(QueryPlan *plan, Ndb *db, const char * key, int verbose) {
  char ndbkeybuffer[300];
    
  Operation op(plan, OP_DELETE, ndbkeybuffer);  

  op.clearKeyNullBits();
  op.setKeyPart(COL_STORE_KEY, key, strlen(key));
  
  NdbTransaction * tx = op.startTransaction(db);
  op.deleteTuple(tx);
  tx->execute(NdbTransaction::Commit);  

  detail(verbose, "delete \"%s\": %d \n", key, tx->RESULT);
  tx->close();
}
