/*
  This file is part of Taler
  Copyright (C) 2008--2014, 2016 GNUnet e.V.
  Copyright (C) 2018 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,
  write to the Free Software Foundation, Inc., 51 Franklin
  Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/**
 * @file taler-twister.c
 * @brief command line utility to batch commands to the twister.
 * @author Marcello Stanisci
 */

#include "platform.h"
#include "taler_twister_service.h"

/**
 * The handle to the Twister service
 */
static struct TALER_TWISTER_Handle *tth;

/**
 * The program status; 0 for success.
 */
static int status;

/**
 * Response code to substitute to the genuine one.  0 for no
 * substitution.
 */
static unsigned int hack_response_code;

/**
 * Path to a object to be deleted.  The format is "object-like":
 * path "field0.field1.2.field3" will delete the following object:
 *
 * {"field1": {"field2": [0, 1, {"field3": "TO DELETE!"}]}}.
 */
static char *delete_path;

/**
 * If true, will randomly truncate the proxied response.
 */
static int malform_response;

/**
 * If true, will randomly truncate the proxied request.
 */
static int malform_upload;

/**
 * Path to the string-object to flip, belonging to the
 * response JSON object.
 */
static char *flip_path_dl;


/**
 * Path to the string-object to flip, belonging to the
 * request JSON object.
 */
static char *flip_path_ul;

/**
 * Path to the field to modify, from the response JSON object.
 */
static char *modify_path_dl;

/*
 * Path to the field to modify, from the request JSON object.
 */
static char *modify_path_ul;

/**
 * Name of the header to modify in the HTTP request.
 */
static char *modify_header_dl;

/**
 * New value for the object pointed by `modify_path_(ul|dl)`.
 */
static char *modify_value;

/**
 * This option is used to check whether the twister can accept
 * connections over the unix domain socket interface.  Used when
 * launching it to see if everything (?) is okay.
 */
static int check_alive;

/**
 * Number of operations we batched.
 */
static unsigned int num_ops;


/**
 * Task to shutdown and clean up all state
 *
 * @param cls NULL
 */
static void
do_shutdown (void *cls)
{
  if (NULL != tth)
  {
    TALER_TWISTER_disconnect (tth);
    tth = NULL;
  }
}


/**
 * Callback invoked after the twister batched one operation.
 *
 * @param cls NULL
 */
static void
handle_acknowledgement (void *cls)
{
  num_ops--;
  if (0 != num_ops)
    return;
  status = 0;
  GNUNET_SCHEDULER_shutdown ();
}


/**
 * Method that batches operations into the twister.
 *
 * @param cls unused
 * @param args remaining args, unused
 * @param cfgfile name of the configuration
 * @param cfg configuration handle
 */
static void
run (void *cls,
     char *const *args,
     const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  tth = TALER_TWISTER_connect (cfg);
  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
                                 NULL);
  if (NULL == tth)
  {
    GNUNET_break (0);
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  if ( (0 != malform_upload) &&
       (NULL != TALER_TWISTER_malform_upload
          (tth,
          &handle_acknowledgement,
          NULL)))
    num_ops++;

  if ( (0 != malform_response) &&
       (NULL != TALER_TWISTER_malform
          (tth,
          &handle_acknowledgement,
          NULL)))
    num_ops++;

  if (0 != check_alive)
  {
    fprintf (stderr, "Could connect to twister via IPC socket.\n");
    status = 0;
  }

  if ( (0 != hack_response_code) &&
       (NULL != TALER_TWISTER_change_response_code
          (tth,
          hack_response_code,
          &handle_acknowledgement,
          NULL)) )
    num_ops++;

  if ( (NULL != flip_path_ul) &&
       (NULL != TALER_TWISTER_flip_upload
          (tth,
          flip_path_ul,
          &handle_acknowledgement,
          NULL)) )
    num_ops++;

  if ( (NULL != flip_path_dl) &&
       (NULL != TALER_TWISTER_flip_download
          (tth,
          flip_path_dl,
          &handle_acknowledgement,
          NULL)) )
    num_ops++;

  if ( (NULL != delete_path) &&
       (NULL != TALER_TWISTER_delete_path
          (tth,
          delete_path,
          &handle_acknowledgement,
          NULL)) )
    num_ops++;

  if (NULL != modify_path_ul)
  {
    if (NULL == modify_value)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "New value not given, give -V|--value also\n");
      GNUNET_SCHEDULER_shutdown ();
      return;
    }

    if (NULL != TALER_TWISTER_modify_path_ul
          (tth,
          modify_path_ul,
          modify_value,
          &handle_acknowledgement,
          NULL))
      num_ops++;
  }

  if (NULL != modify_path_dl)
  {
    if (NULL == modify_value)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "New value not given, give -V|--value also\n");
      GNUNET_SCHEDULER_shutdown ();
      return;
    }

    if (NULL != TALER_TWISTER_modify_path_dl
          (tth,
          modify_path_dl,
          modify_value,
          &handle_acknowledgement,
          NULL))
      num_ops++;
  }

  if (NULL != modify_header_dl)
  {
    if (NULL == modify_value)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "New value not given, give -V|--value also\n");
      GNUNET_SCHEDULER_shutdown ();
      return;
    }

    if (NULL != TALER_TWISTER_modify_header_dl
          (tth,
          modify_header_dl,
          modify_value,
          &handle_acknowledgement,
          NULL))
      num_ops++;
  }

  if (0 == num_ops)
  {
    fprintf (stderr, "No valid hacks specified!\n");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
}


/**
 * Main function.
 *
 * @param argc number of CLI arguments given + 1.
 * @param argv array of CLI arguments given, + the binary name.
 * @return 0 on success
 */
int
main (int argc,
      char *const *argv)
{
  struct GNUNET_GETOPT_CommandLineOption options[] = {

    GNUNET_GETOPT_option_string
      ('X',
      "modify-ul",
      "PATH",
      gettext_noop
        ("Modify upload object pointed by PATH,"
        " require --value.\n"),
      &modify_path_ul),

    GNUNET_GETOPT_option_string
      ('m',
      "modify-dl",
      "PATH",
      gettext_noop
        ("Modify download object pointed by PATH,"
        " require --value.\n"),
      &modify_path_dl),

    GNUNET_GETOPT_option_string
      ('H',
      "modify-header-dl",
      "HEADER",
      gettext_noop
        ("Modify download HTTP header HEADER,"
        " require --value.\n"),
      &modify_header_dl),

    GNUNET_GETOPT_option_string
      ('F',
      "flip-ul",
      "PATH",
      gettext_noop
        ("Flip a char in the *string* upload object"
        " pointed by PATH.\n"),
      &flip_path_ul),

    GNUNET_GETOPT_option_string
      ('f',
      "flip-dl",
      "PATH",
      gettext_noop
        ("Flip a char in the *string* download"
        " object pointed by PATH.\n"),
      &flip_path_dl),

    GNUNET_GETOPT_option_string
      ('V',
      "value",
      "VALUE",
      gettext_noop
        ("Make VALUE the new value of the field"
        " pointed by PATH.  Note: in this version,"
        " numbers will be always parsed and _set_"
        " as strings."),
      &modify_value),

    GNUNET_GETOPT_option_string
      ('d',
      "deleteobject",
      "PATH",
      gettext_noop
        ("Delete the object pointed by PATH.\n"),
      &delete_path),

    GNUNET_GETOPT_option_flag
      ('a',
      "checkalive",
      gettext_noop
        ("Check if twister accepts IPC connections\n"),
      &check_alive),

    GNUNET_GETOPT_option_flag
      ('U',
      "malformupload",
      gettext_noop
        ("Randomly truncate proxied request"),
      &malform_upload),

    GNUNET_GETOPT_option_flag
      ('M',
      "malform",
      gettext_noop
        ("Randomly truncate proxied response"),
      &malform_response),

    GNUNET_GETOPT_option_uint
      ('r',
      "responsecode",
      "STATUS",
      gettext_noop
        ("Set the next response code to STATUS"),
      &hack_response_code),
    GNUNET_GETOPT_OPTION_END
  };
  enum GNUNET_GenericReturnValue ret;

  status = 1;
  ret = GNUNET_PROGRAM_run (
    TWISTER_project_data (),
    argc,
    argv,
    "taler-twister",
    gettext_noop ("Control taler-twister service."),
    options,
    &run, NULL);
  if (GNUNET_SYSERR == ret)
    return 3;
  if (GNUNET_NO == ret)
    return 0;
  return status;
}
