/*
      This file is part of GNUnet
      Copyright (C) 2008--2013 GNUnet e.V.

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

      GNUnet 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
      Affero General Public License for more details.

      You should have received a copy of the GNU Affero General Public License
      along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */

/**
 * @file testbed/test_testbed_api_operations.c
 * @brief tests cases for testbed_api_operations.c
 * @author Sree Harsha Totakura
 */

#include "platform.h"
#include "gnunet_util_lib.h"
#include "testbed_api_operations.h"

/**
 * Generic logging shortcut
 */
#define LOG(kind, ...)                           \
  GNUNET_log (kind, __VA_ARGS__)

/**
 * Delay to start step task
 */
#define STEP_DELAY                                                      \
  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 500)

/**
 * Queue A. Initially the max active is set to 2 and then reduced to 0 - this
 * should block op2 even after op1 has finished. Later the max active is set to
 * 2 and this should start op2
 */
struct OperationQueue *q1;

/**
 * Queue B. Max active set to 2 is not changed throughout the test
 */
struct OperationQueue *q2;

/**
 * This operation should go into both queues and block op2 until it is done
 */
struct GNUNET_TESTBED_Operation *op1;

/**
 * This operation should go into q1 and q2
 */
struct GNUNET_TESTBED_Operation *op2;

/**
 * This operation should go into both queues and should consume 2 units of
 * resources on both queues. Since op2 needs a resource from both queues and is
 * queues before this operation, it will be blocked until op2 is released even
 * though q1 has enough free resources
 */
struct GNUNET_TESTBED_Operation *op3;

/**
 * Just like op3, this operation also consumes 2 units of resources on both
 * queues. Since this is queued after op3 and both queues are at max active
 * 2. This will be blocked until op3 is done.
 */
struct GNUNET_TESTBED_Operation *op4;

/**
 * This operation is started after op4 is released and should consume only 1
 * resource on queue q1. It should be started along with op6 and op7
 */
struct GNUNET_TESTBED_Operation *op5;

/**
 * This operation is started after op4 is released and should consume only 1
 * resource on q2. It should be started along with op5 and op7
 */
struct GNUNET_TESTBED_Operation *op6;

/**
 * This operation is started after op4 is released and should consume 1 resource
 * on both queues q1 and q1. It should be started along with op5 and op6.  It is
 * then inactivated when op6 is released.  op8's start should release this
 * operation implicitly.
 */
struct GNUNET_TESTBED_Operation *op7;

/**
 * This operation is started after op6 is finished in step task.  It consumes 2
 * resources on both queues q1 and q2.  This operation should evict op7.  After
 * starting, it should be made inactive, active and inactive again in the step task.
 */
struct GNUNET_TESTBED_Operation *op8;

/**
 * This operation is started after activating op8.  It should consume a resource
 * on queues q1 and q2.  It should not be started until op8 is again made
 * inactive at which point it should be released.  It can be released as soon as
 * it begins.
 */
struct GNUNET_TESTBED_Operation *op9;

/**
 * The delay task identifier
 */
struct GNUNET_SCHEDULER_Task *step_task;


/**
 * Enumeration of test stages
 */
enum Test
{
  /**
   * Initial stage
   */
  TEST_INIT,

  /**
   * op1 has been started
   */
  TEST_OP1_STARTED,

  /**
   * op1 has been released
   */
  TEST_OP1_RELEASED,

  /**
   * Temporary pause where no operations should start as we set max active in q1
   * to 0 in stage TEST_OP1_STARTED
   */
  TEST_PAUSE,

  /**
   * op2 has started
   */
  TEST_OP2_STARTED,

  /**
   * op2 released
   */
  TEST_OP2_RELEASED,

  /**
   * op3 has started
   */
  TEST_OP3_STARTED,

  /**
   * op3 has finished
   */
  TEST_OP3_RELEASED,

  /**
   * op4 has started
   */
  TEST_OP4_STARTED,

  /**
   * op4 has released
   */
  TEST_OP4_RELEASED,

  /**
   * op5, op6, op7 started
   */
  TEST_OP5_6_7_STARTED,

  /**
   * op5 has released
   */
  TEST_OP5_RELEASED,

  /**
   * op6 has released
   */
  TEST_OP6_RELEASED,

  /**
   * op8 has began waiting
   */
  TEST_OP8_WAITING,

  /**
   * op7 has released
   */
  TEST_OP7_RELEASED,

  /**
   * op8 has started
   */
  TEST_OP8_STARTED,

  /**
   * op8 is inactive
   */
  TEST_OP8_INACTIVE_1,

  /**
   * op8 is active
   */
  TEST_OP8_ACTIVE,

  /**
   * op8 has been released
   */
  TEST_OP8_RELEASED,

  /**
   * op9 has started
   */
  TEST_OP9_STARTED,

  /**
   * op9 has been released
   */
  TEST_OP9_RELEASED
};

/**
 * The test result
 */
enum Test result;


/**
 * Function to call to start an operation once all
 * queues the operation is part of declare that the
 * operation can be activated.
 */
static void
start_cb (void *cls);


/**
 * Function to cancel an operation (release all associated resources).  This can
 * be because of a call to "GNUNET_TESTBED_operation_cancel" (before the
 * operation generated an event) or AFTER the operation generated an event due
 * to a call to "GNUNET_TESTBED_operation_done".  Thus it is not guaranteed that
 * a callback to the 'OperationStart' precedes the call to 'OperationRelease'.
 * Implementations of this function are expected to clean up whatever state is
 * in 'cls' and release all resources associated with the operation.
 */
static void
release_cb (void *cls);


/**
 * Task to simulate artificial delay and change the test stage
 *
 * @param cls NULL
 */
static void
step (void *cls)
{
  GNUNET_assert (NULL != step_task);
  step_task = NULL;
  switch (result)
  {
  case TEST_OP1_STARTED:
    GNUNET_TESTBED_operation_release_ (op1);
    GNUNET_TESTBED_operation_queue_reset_max_active_ (q1, 0);
    op3 = GNUNET_TESTBED_operation_create_ (&op3, &start_cb, &release_cb);
    GNUNET_TESTBED_operation_queue_insert2_ (q1, op3, 2);
    GNUNET_TESTBED_operation_queue_insert2_ (q2, op3, 2);
    GNUNET_TESTBED_operation_begin_wait_ (op3);
    op4 = GNUNET_TESTBED_operation_create_ (&op4, &start_cb, &release_cb);
    GNUNET_TESTBED_operation_queue_insert2_ (q1, op4, 2);
    GNUNET_TESTBED_operation_queue_insert2_ (q2, op4, 2);
    GNUNET_TESTBED_operation_begin_wait_ (op4);
    break;

  case TEST_OP1_RELEASED:
    result = TEST_PAUSE;
    GNUNET_TESTBED_operation_queue_reset_max_active_ (q1, 2);
    break;

  case TEST_OP2_STARTED:
    GNUNET_TESTBED_operation_release_ (op2);
    break;

  case TEST_OP3_STARTED:
    GNUNET_TESTBED_operation_release_ (op3);
    break;

  case TEST_OP4_STARTED:
    GNUNET_TESTBED_operation_release_ (op4);
    break;

  case TEST_OP6_RELEASED:
    op8 = GNUNET_TESTBED_operation_create_ (&op8, &start_cb, &release_cb);
    GNUNET_TESTBED_operation_queue_insert2_ (q1, op8, 2);
    GNUNET_TESTBED_operation_queue_insert2_ (q2, op8, 2);
    result = TEST_OP8_WAITING;
    GNUNET_TESTBED_operation_begin_wait_ (op8);
    break;

  case TEST_OP8_STARTED:
    GNUNET_TESTBED_operation_inactivate_ (op8);
    result = TEST_OP8_INACTIVE_1;
    step_task = GNUNET_SCHEDULER_add_delayed (STEP_DELAY, &step, NULL);
    break;

  case TEST_OP8_INACTIVE_1:
    GNUNET_TESTBED_operation_activate_ (op8);
    result = TEST_OP8_ACTIVE;
    op9 = GNUNET_TESTBED_operation_create_ (&op9, &start_cb, &release_cb);
    GNUNET_TESTBED_operation_queue_insert2_ (q1, op9, 1);
    GNUNET_TESTBED_operation_queue_insert2_ (q2, op9, 1);
    GNUNET_TESTBED_operation_begin_wait_ (op9);
    step_task = GNUNET_SCHEDULER_add_delayed (STEP_DELAY, &step, NULL);
    break;

  case TEST_OP8_ACTIVE:
    GNUNET_TESTBED_operation_inactivate_ (op8);
    /* op8 should be released by now due to above call */
    GNUNET_assert (TEST_OP8_RELEASED == result);
    break;

  case TEST_OP9_STARTED:
    GNUNET_TESTBED_operation_release_ (op9);
    break;

  default:
    GNUNET_assert (0);
  }
}


/**
 * Function to call to start an operation once all
 * queues the operation is part of declare that the
 * operation can be activated.
 */
static void
start_cb (void *cls)
{
  switch (result)
  {
  case TEST_INIT:
    GNUNET_assert (&op1 == cls);
    result = TEST_OP1_STARTED;
    GNUNET_assert (NULL == step_task);
    step_task =
      GNUNET_SCHEDULER_add_delayed (STEP_DELAY, &step, NULL);
    break;

  case TEST_PAUSE:
    GNUNET_assert (&op2 == cls);
    result = TEST_OP2_STARTED;
    GNUNET_assert (NULL == step_task);
    step_task =
      GNUNET_SCHEDULER_add_delayed (STEP_DELAY, &step, NULL);
    break;

  case TEST_OP2_RELEASED:
    GNUNET_assert (&op3 == cls);
    result = TEST_OP3_STARTED;
    GNUNET_assert (NULL == step_task);
    step_task =
      GNUNET_SCHEDULER_add_delayed (STEP_DELAY, &step, NULL);
    break;

  case TEST_OP3_RELEASED:
    GNUNET_assert (&op4 == cls);
    result = TEST_OP4_STARTED;
    GNUNET_assert (NULL == step_task);
    step_task =
      GNUNET_SCHEDULER_add_delayed (STEP_DELAY, &step, NULL);
    break;

  case TEST_OP4_RELEASED:
    {
      static int nops;

      nops++;
      if (nops == 3)
      {
        result = TEST_OP5_6_7_STARTED;
        GNUNET_TESTBED_operation_release_ (op5);
        op5 = NULL;
      }
    }
    break;

  case TEST_OP7_RELEASED:
    GNUNET_assert (&op8 == cls);
    result = TEST_OP8_STARTED;
    step_task = GNUNET_SCHEDULER_add_delayed (STEP_DELAY, &step, NULL);
    break;

  case TEST_OP8_RELEASED:
    GNUNET_assert (&op9 == cls);
    result = TEST_OP9_STARTED;
    step_task = GNUNET_SCHEDULER_add_delayed (STEP_DELAY, &step, NULL);
    break;

  default:
    GNUNET_assert (0);
  }
}


/**
 * Function to cancel an operation (release all associated resources).  This can
 * be because of a call to "GNUNET_TESTBED_operation_cancel" (before the
 * operation generated an event) or AFTER the operation generated an event due
 * to a call to "GNUNET_TESTBED_operation_done".  Thus it is not guaranteed that
 * a callback to the 'OperationStart' precedes the call to 'OperationRelease'.
 * Implementations of this function are expected to clean up whatever state is
 * in 'cls' and release all resources associated with the operation.
 */
static void
release_cb (void *cls)
{
  switch (result)
  {
  case TEST_OP1_STARTED:
    GNUNET_assert (&op1 == cls);
    result = TEST_OP1_RELEASED;
    op1 = NULL;
    step_task =
      GNUNET_SCHEDULER_add_delayed (STEP_DELAY, &step, NULL);
    break;

  case TEST_OP2_STARTED:
    GNUNET_assert (&op2 == cls);
    result = TEST_OP2_RELEASED;
    GNUNET_assert (NULL == step_task);
    break;

  case TEST_OP3_STARTED:
    GNUNET_assert (&op3 == cls);
    result = TEST_OP3_RELEASED;
    GNUNET_assert (NULL == step_task);
    break;

  case TEST_OP4_STARTED:
    GNUNET_assert (&op4 == cls);
    result = TEST_OP4_RELEASED;
    GNUNET_assert (NULL == step_task);
    op5 = GNUNET_TESTBED_operation_create_ (&op5, &start_cb, &release_cb);
    GNUNET_TESTBED_operation_queue_insert2_ (q1, op5, 1);
    GNUNET_TESTBED_operation_begin_wait_ (op5);
    op6 = GNUNET_TESTBED_operation_create_ (&op6, &start_cb, &release_cb);
    GNUNET_TESTBED_operation_queue_insert2_ (q2, op6, 1);
    GNUNET_TESTBED_operation_begin_wait_ (op6);
    op7 = GNUNET_TESTBED_operation_create_ (&op7, &start_cb, &release_cb);
    GNUNET_TESTBED_operation_queue_insert2_ (q1, op7, 1);
    GNUNET_TESTBED_operation_queue_insert2_ (q2, op7, 1);
    GNUNET_TESTBED_operation_begin_wait_ (op7);
    break;

  case TEST_OP5_6_7_STARTED:
    result = TEST_OP5_RELEASED;
    op5 = NULL;
    GNUNET_TESTBED_operation_release_ (op6);
    break;

  case TEST_OP5_RELEASED:
    op6 = NULL;
    result = TEST_OP6_RELEASED;
    GNUNET_TESTBED_operation_inactivate_ (op7);
    step_task = GNUNET_SCHEDULER_add_now (&step, NULL);
    break;

  case TEST_OP8_WAITING:
    GNUNET_assert (&op7 == cls);
    op7 = NULL;
    result = TEST_OP7_RELEASED;
    break;

  case TEST_OP8_ACTIVE:
    result = TEST_OP8_RELEASED;
    op8 = NULL;
    break;

  case TEST_OP9_STARTED:
    GNUNET_assert (&op9 == cls);
    result = TEST_OP9_RELEASED;
    GNUNET_TESTBED_operation_queue_destroy_ (q1);
    GNUNET_TESTBED_operation_queue_destroy_ (q2);
    q1 = NULL;
    q2 = NULL;
    break;

  default:
    GNUNET_assert (0);
  }
}


/**
 * Main run function.
 *
 * @param cls NULL
 * @param args arguments passed to GNUNET_PROGRAM_run
 * @param cfgfile the path to configuration file
 * @param cfg the configuration file handle
 */
static void
run (void *cls, char *const *args, const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *config)
{
  q1 = GNUNET_TESTBED_operation_queue_create_ (OPERATION_QUEUE_TYPE_FIXED, 1);
  GNUNET_assert (NULL != q1);
  q2 = GNUNET_TESTBED_operation_queue_create_ (OPERATION_QUEUE_TYPE_FIXED, 2);
  GNUNET_assert (NULL != q2);
  op1 = GNUNET_TESTBED_operation_create_ (&op1, start_cb, release_cb);
  GNUNET_assert (NULL != op1);
  op2 = GNUNET_TESTBED_operation_create_ (&op2, start_cb, release_cb);
  GNUNET_TESTBED_operation_queue_insert_ (q1, op1);
  GNUNET_TESTBED_operation_queue_insert_ (q2, op1);
  GNUNET_TESTBED_operation_begin_wait_ (op1);
  GNUNET_TESTBED_operation_queue_insert_ (q1, op2);
  GNUNET_TESTBED_operation_queue_insert_ (q2, op2);
  GNUNET_TESTBED_operation_begin_wait_ (op2);
  result = TEST_INIT;
}


/**
 * Main function
 */
int
main (int argc, char **argv)
{
  int ret;
  char *const argv2[] =
  { "test_testbed_api_operations", "-c", "test_testbed_api.conf", NULL };
  struct GNUNET_GETOPT_CommandLineOption options[] =
  { GNUNET_GETOPT_OPTION_END };

  ret =
    GNUNET_PROGRAM_run ((sizeof(argv2) / sizeof(char *)) - 1, argv2,
                        "test_testbed_api_operations", "nohelp", options,
                        &run, NULL);
  if ((GNUNET_OK != ret) || (TEST_OP9_RELEASED != result))
    return 1;
  op1 = NULL;
  op2 = NULL;
  op3 = NULL;
  op4 = NULL;
  op5 = NULL;
  op6 = NULL;
  op7 = NULL;
  op8 = NULL;
  op9 = NULL;
  q1 = NULL;
  q2 = NULL;
  return 0;
}


/* end of test_testbed_api_operations.c */
