// ------------------------------------------------------------------------
// libkvutils_tester.cpp: Runs a set of libkvutils unit tests.
// Copyright (C) 2002-2004,2009 Kai Vehmanen
//
// Attributes:
//     eca-style-version: 2
//
// 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
// ------------------------------------------------------------------------

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string>

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <stddef.h>  /* ANSI-C: size_t */
#include <stdio.h>   /* for AIX */
#include <time.h>    /* ANSI-C: clock() */

#include "kvu_dbc.h"
#include "kvu_locks.h"
#include "kvu_numtostr.h"
#include "kvu_rtcaps.h"
#include "kvu_timestamp.h"
#include "kvu_utils.h"
#include "kvu_value_queue.h"
#include "kvu_message_queue.h"

using namespace std;

/* --------------------------------------------------------------------- 
 * Options
 */

#define VERBOSE

/* --------------------------------------------------------------------- 
 * Test util macros
 */

#ifdef VERBOSE
#define ECA_TEST_ENTRY()   do { printf("\n%s:%d - Test started\n", __KVU_FUNCTION, __LINE__); } while(0)
#define ECA_TEST_SUCCESS() do { printf("%s:%d - Test passed\n", __KVU_FUNCTION, __LINE__); return 0; } while(0)
#define ECA_TEST_FAIL(x,y) do { printf("\n%s:%d - Test failed: \"%s\"\n", __KVU_FUNCTION, __LINE__, y); return x; } while(0)
#define ECA_TEST_NOTE(x)   do { printf("%s:%d - %s\n", __KVU_FUNCTION, __LINE__, x); fflush(stdout); } while(0)
#else
#define ECA_TEST_ENTRY()   ((void) 0)
#define ECA_TEST_SUCCESS() return 0
#define ECA_TEST_FAIL(x,y) return x
#define ECA_TEST_NOTE(x)   ((void) 0)
#endif

/* --------------------------------------------------------------------- 
 * Type definitions
 */

typedef int (*kvu_test_t)(void);

/* --------------------------------------------------------------------- 
 * Test case declarations
 */

static int kvu_test_1(void);
static int kvu_test_2(void);
static int kvu_test_3(void);
static int kvu_test_4(void);
static int kvu_test_5_timestamp(void);
static int kvu_test_6_msgqueue(void);

static kvu_test_t kvu_funcs[] = { 
  kvu_test_1,  /* kvu_locks.h: ATOMIC_INTEGER */
  kvu_test_2,  /* kvu_utils.h: string handling */
  kvu_test_3,  /* kvu_utils.h: float2str */
  kvu_test_4,  /* kvu_value_queue.h */
  kvu_test_5_timestamp, /* kvu_timestamp.h */
  kvu_test_6_msgqueue,  /* kvu_message_queue.h */
  NULL 
};

/* --------------------------------------------------------------------- 
 * Funtion definitions
 */

int main(int argc, char *argv[])
{
  int n, failed = 0;

  if (argc > 1) {
    /* run just a single test */
    size_t m = std::atoi(argv[1]);
    if (m > 0 && m < (sizeof(kvu_funcs) / sizeof(kvu_test_t))) {
      if (kvu_funcs[m - 1]() != 0) {
	++failed;
      }
    }
  }
  else {
    /* run all tests */
    for(n = 0; kvu_funcs[n] != NULL; n++) {
      int ret = kvu_funcs[n]();
      if (ret != 0) {
	++failed;
      }
    }
  }

  return failed;
}

#define KVU_TEST_1_ROUNDS 5
// #define KVU_TEST_1_ROUNDS 60

static void* kvu_test_1_helper(void* ptr)
{
  ATOMIC_INTEGER* i = (ATOMIC_INTEGER*)ptr;

  int stop_after = KVU_TEST_1_ROUNDS * CLOCKS_PER_SEC / 5;
  clock_t prev, now = clock();

  for(int n = 0, m = 0; n < stop_after;) {
    // if (!(m & 0xffff)) fprintf(stderr, "S");
    ++m;
    int j = i->get();
    if (j < 0) {
      ++j;
      i->set(j);
    }
    j = i->get();
    if (j > 0) { ECA_TEST_FAIL((void*)1, "kvu_test_1_helper access error (1)"); }
    if (j < -1) { ECA_TEST_FAIL((void*)1, "kvu_test_1_helper access error (2)"); }

    prev = now;
    now = clock();
    if (prev > now) 
      n += prev - now;
    else 
      n += now - prev;
  }

  return 0;
}

/**
 * Tests the ATOMIC_INTEGER class defined 
 * in kvu_locks.h. 
 */
static int kvu_test_1(void)
{
  ECA_TEST_ENTRY();

  ATOMIC_INTEGER i (0);

  pthread_t thread;
  pthread_create(&thread, NULL, kvu_test_1_helper, (void*)&i);

  int stop_after = KVU_TEST_1_ROUNDS * CLOCKS_PER_SEC;
  clock_t prev, now = clock();
  
  for(int n = 0, m = 0; n < stop_after;) {
    // if (!(m & 0xffff)) fprintf(stderr, "M");
    ++m;
    int j = i.get();
    if (j < 0) {
      ++j;
      i.set(j);
    }
    else {
      --j;
      i.set(j);
    }

    j = i.get();
    if (j > 0) { ECA_TEST_FAIL(1, "kvu_test_1 access error (3)"); }
    if (j < -1) { ECA_TEST_FAIL(1, "kvu_test_1 access error (4)"); }

    prev = now;
    now = clock();
    if (prev > now) 
      n += prev - now;
    else 
      n += now - prev;
  }

  ECA_TEST_SUCCESS();
}

/**
 * Tests the string handling functions defined 
 * in kvu_utils.h. 
 */
static int kvu_test_2(void)
{
  ECA_TEST_ENTRY();

  /* string comparison: */

  if (kvu_string_icmp(" foo ", " fOo ") != true) {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_icmp"); 
  }

  /* vectorization: */

  vector<string> vec = kvu_string_to_tokens(" a foo string ");
  if (vec.size() != 3) {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_to_tokens (1)");
  }
  if (vec[2] != "string") {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_to_tokens (2)"); 
  }

  vec = kvu_string_to_tokens_quoted("a foo\\ string");
  if (vec.size() != 2) {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_to_tokens_quoted (1)");
  }
  if (vec[1] != "foo string") {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_to_tokens_quoted (2)"); 
  }

  vec = kvu_string_to_tokens_quoted("another\\ foo \"with substring\" \\\\slashes");
  if (vec.size() != 3) {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_to_tokens_quoted (3)");
  }
  if (vec[1] != "\"with substring\"") {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_to_tokens_quoted (4)"); 
  }
  if (vec[2] != "\\slashes") {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_to_tokens_quoted (5)"); 
  }

  /* de-vectorization: */
  vector<string> test;
  test.push_back(" foo");
  test.push_back("bar ");
  if (kvu_vector_to_string(test, "") != " foobar ") {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_vector_to_string (1)"); 
  }

  /* argument string parsing: */

  const string test_arg1 ("-efoobarsouNd:arg1,arg2,arg3,long\\,arg\\:4,arg5,\"arg,6,comma1,comma2\"");

  if (kvu_get_argument_prefix(test_arg1) != "efoobarsouNd") {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_argument_prefix"); 
  }

  if (kvu_get_argument_number(5, test_arg1) != "arg5") {
    // fprintf(stderr, "vec2: '%s'.\n", kvu_get_argument_number(5, test_arg1).c_str());
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_argument_number (1)"); 
  }

  if (kvu_get_argument_number(6, test_arg1) != "arg,6,comma1,comma2") {
    fprintf(stderr, "vec2: '%s'.\n", kvu_get_argument_number(6, test_arg1).c_str());
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_argument_number (2)"); 
  }

  /* request for non-existant arg should return an empty string */
  if (kvu_get_argument_number(7, test_arg1) != "") {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_argument_number (3)"); 
  }

  vec = kvu_get_arguments(test_arg1);
  if (vec.size() != 6) {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_arguments (1)"); 
  }

  if (vec[2] != "arg3" || vec[0] != "arg1" || vec[3] != "long,arg:4") {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_arguments (2)"); 
  }

  if (kvu_get_number_of_arguments(test_arg1) != 6) {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_number_of_arguments"); 
  }

  const string test_arg2 ("-e:\"\\arg1,arg2,arg3");

  if (kvu_get_number_of_arguments(test_arg2) != 3) {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_number_of_arguments (arg2-2)"); 
  }

  if (kvu_get_argument_number(1, test_arg2) != "\"\\arg1") {
    fprintf(stderr, "vec2: '%s'.\n", kvu_get_argument_number(1, test_arg2).c_str());
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_argument_number (arg2-1)"); 
  }

  if (kvu_get_number_of_arguments("-f:,,") != 3)
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_number_of_arguments (empty args)"); 
  if (kvu_get_number_of_arguments("-f:a,") != 2)
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_number_of_arguments (valid+null)"); 
  if (kvu_get_number_of_arguments("-f:,a") != 2)
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_number_of_arguments (null+valid)"); 
  if (kvu_get_number_of_arguments("-f:") != 0)
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_get_number_of_arguments (no args)"); 


  /* search and replace: */

  if (kvu_string_search_and_replace("foo bar", 'f', 'b')
      != "boo bar") {
    ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_search_and_replace"); 
  }

  /* meta-char espacing: */

  if (kvu_string_shell_meta_escape("foo\"bar") != "foo\\\"bar") ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_meta_escape");
  if (kvu_string_shell_meta_escape("foo'bar") != "foo\\'bar") ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_meta_escape");
  if (kvu_string_shell_meta_escape("foo|bar") != "foo\\|bar") ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_meta_escape");
  if (kvu_string_shell_meta_escape("foo&bar&&&") != "foo\\&bar\\&\\&\\&") ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_meta_escape");
  if (kvu_string_shell_meta_escape("foo bar ") != "foo\\ bar\\ ") ECA_TEST_FAIL(1, "kvu_test_2 kvu_string_meta_escape");

  ECA_TEST_SUCCESS();
}

/**
 * Tests the floating point to text conversion functions defined
 * in kvu_numtostr.h.
 */
static int kvu_test_3(void)
{
  ECA_TEST_ENTRY();

  /* 17 digits after decimal point */
  double foo = 0.12345678912345678;
  string foostr = kvu_numtostr(foo, 17);
  if (foostr != "0.12345678912345678") {
    // fprintf(stderr, "foo=%.17lf, res=%s.\n", foo, foostr.c_str());
    ECA_TEST_FAIL(1, "kvu_test_3 kvu_numtostr double"); 
  }

  /* 8 digits after decimal point */
  float bar = 0.12345678;
  string barstr = kvu_numtostr(bar, 8);
  if (barstr != "0.12345678") {
    // fprintf(stderr, "bar=%.8f, res=%s.\n", bar, barstr.c_str());
    ECA_TEST_FAIL(1, "kvu_test_3 kvu_numtostr float"); 
  }

  ECA_TEST_SUCCESS();
}

static const int kvu_test_4_iterations_const = 16384;
static int kvu_test_4_retval = 0;

static void* kvu_test_4_helper(void* ptr);

/**
 * Tests the VALUE_QUEUE_RT_C class implementation.
 */
static int kvu_test_4(void)
{
  ECA_TEST_ENTRY();

  /* guarantee bounded execution time only upto 16 items */
  VALUE_QUEUE_RT_C rqueue (16);

  ECA_TEST_NOTE("start-test");

  pthread_t thread;
  pthread_create(&thread, NULL, kvu_test_4_helper, (void*)&rqueue);

  kvu_sleep(1, 0);

  ECA_TEST_NOTE("start-item-push");

  for(int iter = 0; iter < kvu_test_4_iterations_const; iter++) {
    // fprintf(stderr, "%s:%d push.\n", __FUNCTION__, __LINE__);
    rqueue.push_back(iter, 1.0f);
    // kvu_sleep(1, 0);
  }

  void *res_ptr = 0;
  pthread_join(thread, (void**)&res_ptr);
  if (*(int*)res_ptr != 0) {
    ECA_TEST_FAIL(1, "kvu_test_4 slave-thread-failed"); 
  }

  ECA_TEST_NOTE("end-test.");

  ECA_TEST_SUCCESS();
}

/**
 * The real-time consumer thread used in testing VALUE_QUEUE_RT_C.
 */
static void* kvu_test_4_helper(void* ptr)
{
  VALUE_QUEUE_RT_C *rqueue = (VALUE_QUEUE_RT_C*)ptr;
  kvu_test_4_retval = 0;

  ECA_TEST_NOTE("start-thread.");

  int res = kvu_set_thread_scheduling(SCHED_FIFO, 1);
  if (res == 0) {
    ECA_TEST_NOTE("schedfifo-scheduling-enabled");
  }
  else {
    ECA_TEST_NOTE("schedfifo-scheduling-disabled");
  }

  int last_received_v = 0;
  for(int received = 0; received < kvu_test_4_iterations_const; ) {
    if (rqueue->is_empty() != true) {
      const pair<int, double>* ref = rqueue->front();
      if (ref != rqueue->invalid_item()) {
	// fprintf(stderr, "%s:%d front-success.\n", __FUNCTION__, __LINE__);
	++received;
	last_received_v = ref->first + 1;
	if (received != last_received_v) {
	  ECA_TEST_NOTE("queue-sync-error"); 
	  kvu_test_4_retval = -1;
	}
	rqueue->pop_front();
      }
      else {
	ECA_TEST_NOTE("corner-case-queue-busy");
      }
    }
  }

  if (last_received_v != kvu_test_4_iterations_const) {
    ECA_TEST_NOTE("end-of-queue-sync-error"); 
    kvu_test_4_retval = -1;
  }

  ECA_TEST_NOTE("exit-thread");

  pthread_exit(&kvu_test_4_retval);
  /* never reached */
  return 0;
}

/**
 * Tests the kvu_timestamp.h interface
 */
static int kvu_test_5_timestamp(void)
{
  ECA_TEST_ENTRY();

  struct timespec stamp;
  int res;
  int monotonic =
    kvu_clock_is_monotonic();
  double start, now, prev, end;

  res = kvu_clock_gettime(&stamp);
  if (res)
    ECA_TEST_FAIL(1, "kvu_test_5-1 clock_gettime failed"); 

  start = kvu_timespec_seconds(&stamp);
  end = start + 3.0;
  now = prev = start;

  ECA_TEST_NOTE("3s timer loop starts");

  while (now < end) {

    /* 5ms sleeps */
    kvu_sleep(0, 5000000);

    res = kvu_clock_gettime(&stamp);
    if (res)
      ECA_TEST_FAIL(1, "kvu_test_5-2 clock_gettime failed"); 
    now = kvu_timespec_seconds(&stamp);

#ifdef VERY_VERBOSE
    fprintf(stderr, "now=%.09f delta=%.09f end=%.03f\n",
	    now - start, now - prev, end - start);
#endif

    if (now < prev) {
      if (monotonic)
	ECA_TEST_FAIL(1, "kvu_test_5-3 clock goes backwards"); 
      else
	ECA_TEST_NOTE("kvu_test_5 - clock went backwards");
    }
    
    prev = now;
  }
  
  ECA_TEST_SUCCESS();
}

/* note: around 50ms per iteration */
static const int kvu_test_6_iterations_const = 100;
static int kvu_test_6_retval = 0;

static void* kvu_test_6_helper(void* ptr);

/**
 * Tests the MESSAGE_QUEUE_RT_C class implementation.
 */
static int kvu_test_6_msgqueue(void)
{
  ECA_TEST_ENTRY();

  std::srand(std::time(0));

  /* guarantee bounded execution time only upto 16 items */
  MESSAGE_QUEUE_RT_C<std::string> rqueue (16);

  ECA_TEST_NOTE("start-test");

  pthread_t thread;
  pthread_create(&thread, NULL, kvu_test_6_helper, (void*)&rqueue);

  kvu_sleep(1, 0);

  ECA_TEST_NOTE("start-item-push");

  for(int iter = 0; iter < kvu_test_6_iterations_const; iter++) {
    // fprintf(stderr, "%s:%d push.\n", __FUNCTION__, __LINE__);
    std::string msg = kvu_numtostr(iter + 1);
    //std::fprintf(stdout, "%s:%d pushed '%s'\n", __FUNCTION__, __LINE__, msg.c_str());
    rqueue.push_back(msg);
    int sleep_ns = std::rand() % 100;

    kvu_sleep(0, sleep_ns * 1000000); /* [0,100]ms */
  }

  void *res_ptr = 0;
  pthread_join(thread, (void**)&res_ptr);
  if (*(int*)res_ptr != 0) {
    ECA_TEST_FAIL(1, "kvu_test_6 slave-thread-failed"); 
  }

  ECA_TEST_NOTE("end-test.");

  ECA_TEST_SUCCESS();
}

/**
 * The real-time consumer thread.
 */
static void* kvu_test_6_helper(void* ptr)
{
  MESSAGE_QUEUE_RT_C<std::string> *rqueue = 
    static_cast<MESSAGE_QUEUE_RT_C<std::string>*>(ptr);

  kvu_test_6_retval = 0;

  ECA_TEST_NOTE("start-thread.");

  int res = kvu_set_thread_scheduling(SCHED_FIFO, 1);
  if (res == 0) {
    ECA_TEST_NOTE("schedfifo-scheduling-enabled");
  }
  else {
    ECA_TEST_NOTE("schedfifo-scheduling-disabled");
  }

  std::string last_received_v;
  for(int received = 0; received < kvu_test_6_iterations_const; ) {
    int sleep_ns = std::rand() % 100;

    kvu_sleep(0, sleep_ns * 1000000); /* [0,100]ms */

    if (rqueue->is_empty() != true) {
      std::string ref;
      int popres = rqueue->pop_front(&ref);
      if (popres > 0) {
	// std::fprintf(stdout, "%s:%d popped '%s'\n", __FUNCTION__, __LINE__, ref.c_str());
	++received;
	if (last_received_v == ref) {
	  ECA_TEST_NOTE("queue-sync-error"); 
	  kvu_test_6_retval = -1;
	  break;
	}
	last_received_v = ref;
      }
      else {
	ECA_TEST_NOTE("corner-case-queue-busy");
      }
    }
  }

  if (std::atoi(last_received_v.c_str()) != kvu_test_6_iterations_const) {
    // std::fprintf(stdout, "%s:%d last item '%s'\n", __FUNCTION__, __LINE__, last_received_v.c_str());
    ECA_TEST_NOTE("end-of-queue-sync-error"); 
    kvu_test_6_retval = -1;
  }

  ECA_TEST_NOTE("exit-thread");

  pthread_exit(&kvu_test_6_retval);
  /* never reached */
  return 0;
}