/*
  This file is part of TALER
  Copyright (C) 2014-2020 Taler Systems SA
  TALER 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 3, or
  (at your option) any later version.
  TALER 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 TALER; see the file COPYING.  If not, see
  
*/
/**
 * @file testing/testing_api_cmd_rewind.c
 * @brief command to rewind the instruction pointer.
 * @author Marcello Stanisci
 * @author Christian Grothoff
 */
#include "platform.h"
#include 
#include 
#include "taler_testing_lib.h"
/**
 * State for a "rewind" CMD.
 */
struct RewindIpState
{
  /**
   * Instruction pointer to set into the interpreter.
   */
  const char *target_label;
  /**
   * How many times this set should take place.  However, this value lives at
   * the calling process, and this CMD is only in charge of checking and
   * decremeting it.
   */
  unsigned int counter;
};
/**
 * Only defined to respect the API.
 */
static void
rewind_ip_cleanup (void *cls,
                   const struct TALER_TESTING_Command *cmd)
{
  (void) cls;
  (void) cmd;
}
/**
 * Seek for the @a target command in @a batch (and rewind to it
 * if successful).
 *
 * @param is the interpreter state (for failures)
 * @param cmd batch to search for @a target
 * @param target command to search for
 * @return #GNUNET_OK on success, #GNUNET_NO if target was not found,
 *         #GNUNET_SYSERR if target is in the future and we failed
 */
static int
seek_batch (struct TALER_TESTING_Interpreter *is,
            const struct TALER_TESTING_Command *cmd,
            const struct TALER_TESTING_Command *target)
{
  unsigned int new_ip;
#define BATCH_INDEX 1
  struct TALER_TESTING_Command *batch;
  struct TALER_TESTING_Command *current;
  struct TALER_TESTING_Command *icmd;
  const struct TALER_TESTING_Command *match;
  current = TALER_TESTING_cmd_batch_get_current (cmd);
  GNUNET_assert (GNUNET_OK ==
                 TALER_TESTING_get_trait_cmd (cmd,
                                              BATCH_INDEX,
                                              &batch));
  match = NULL;
  for (new_ip = 0;
       NULL != (icmd = &batch[new_ip]);
       new_ip++)
  {
    if (current == target)
      current = NULL;
    if (icmd == target)
    {
      match = icmd;
      break;
    }
    if (TALER_TESTING_cmd_is_batch (icmd))
    {
      int ret = seek_batch (is,
                            icmd,
                            target);
      if (GNUNET_SYSERR == ret)
        return GNUNET_SYSERR; /* failure! */
      if (GNUNET_OK == ret)
      {
        match = icmd;
        break;
      }
    }
  }
  if (NULL == current)
  {
    /* refuse to jump forward */
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (is);
    return GNUNET_SYSERR;
  }
  if (NULL == match)
    return GNUNET_NO; /* not found */
  TALER_TESTING_cmd_batch_set_current (cmd,
                                       new_ip);
  return GNUNET_OK;
}
/**
 * Run the "rewind" CMD.
 *
 * @param cls closure.
 * @param cmd command being executed now.
 * @param is the interpreter state.
 */
static void
rewind_ip_run (void *cls,
               const struct TALER_TESTING_Command *cmd,
               struct TALER_TESTING_Interpreter *is)
{
  struct RewindIpState *ris = cls;
  const struct TALER_TESTING_Command *target;
  unsigned int new_ip;
  (void) cmd;
  if (0 == ris->counter)
  {
    TALER_TESTING_interpreter_next (is);
    return;
  }
  target
    = TALER_TESTING_interpreter_lookup_command (is,
                                                ris->target_label);
  if (NULL == target)
  {
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (is);
    return;
  }
  ris->counter--;
  for (new_ip = 0;
       NULL != is->commands[new_ip].label;
       new_ip++)
  {
    const struct TALER_TESTING_Command *cmd = &is->commands[new_ip];
    if (cmd == target)
      break;
    if (TALER_TESTING_cmd_is_batch (cmd))
    {
      int ret = seek_batch (is,
                            cmd,
                            target);
      if (GNUNET_SYSERR == ret)
        return;   /* failure! */
      if (GNUNET_OK == ret)
        break;
    }
  }
  if (new_ip > is->ip)
  {
    /* refuse to jump forward */
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (is);
    return;
  }
  is->ip = new_ip - 1; /* -1 because the next function will advance by one */
  TALER_TESTING_interpreter_next (is);
}
/**
 * Make the instruction pointer point to @a new_ip
 * only if @a counter is greater than zero.
 *
 * @param label command label
 * @param target_label label of the new instruction pointer's destination after the jump;
 *                     must be before the current instruction
 * @param counter counts how many times the rewinding is to happen.
 */
struct TALER_TESTING_Command
TALER_TESTING_cmd_rewind_ip (const char *label,
                             const char *target_label,
                             unsigned int counter)
{
  struct RewindIpState *ris;
  ris = GNUNET_new (struct RewindIpState);
  ris->target_label = target_label;
  ris->counter = counter;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = ris,
      .label = label,
      .run = &rewind_ip_run,
      .cleanup = &rewind_ip_cleanup
    };
    return cmd;
  }
}