/* recins.c - Inserting records.  */

/* Copyright (C) 2009-2022 Jose E. Marchesi */

/* 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <getopt.h>
#include <string.h>
#include <stdlib.h>
#include <xalloc.h>
#include <gettext.h>
#include <errno.h>
#include <time.h>
#include <locale.h>
#define _(str) gettext (str)
#include <base64.h>

#include <rec.h>
#include <recutl.h>

/*
 * Global variables
 */

char         *recutl_type      = NULL;
rec_sex_t     recutl_sex       = NULL;
char         *recutl_sex_str   = NULL;
char         *recutl_quick_str = NULL;
bool          recutl_insensitive = false;
rec_record_t  recins_record    = NULL;
char         *recins_file      = NULL;
bool          recins_force     = false;
bool          recins_verbose   = false;
bool          recins_external  = true;
bool          recins_auto      = true;
char         *recins_password  = NULL;
size_t        recutl_random    = 0;

/*
 * Command line options management
 */

enum
{
  COMMON_ARGS,
  RECORD_SELECTION_ARGS,
  NAME_ARG,
  VALUE_ARG,
  FORCE_ARG,
  VERBOSE_ARG,
  NO_EXTERNAL_ARG,
  RECORD_ARG,
#if defined REC_CRYPT_SUPPORT
  PASSWORD_ARG,
#endif
  NO_AUTO_ARG
};

static const struct option GNU_longOptions[] =
  {
    COMMON_LONG_ARGS,
    RECORD_SELECTION_LONG_ARGS,
    {"type", required_argument, NULL, TYPE_ARG},
    {"name", required_argument, NULL, NAME_ARG},
    {"value", required_argument, NULL, VALUE_ARG},
    {"force", no_argument, NULL, FORCE_ARG},
    {"verbose", no_argument, NULL, VERBOSE_ARG},
    {"no-external", no_argument, NULL, NO_EXTERNAL_ARG},
    {"record", required_argument, NULL, RECORD_ARG},
    {"no-auto", no_argument, NULL, NO_AUTO_ARG},
#if defined REC_CRYPT_SUPPORT
    {"password", required_argument, NULL, PASSWORD_ARG},
#endif
    {NULL, 0, NULL, 0}
  };

/*
 * Functions.
 */

void
recutl_print_help (void)
{
  /* TRANSLATORS: --help output, recins synopsis.
     no-wrap */
  printf (_("\
Usage: recins [OPTION]... [-t TYPE] [-n NUM | -e RECORD_EXPR | -q STR | -m NUM] [(-f NAME -v STR) | -r RECDATA]... [FILE]\n"));

  /* TRANSLATORS: --help output, recins short description.
     no-wrap */
  fputs (_("\
Insert new records in a rec database.\n"), stdout);

  puts ("");
  /* TRANSLATORS: --help output, recins arguments.
     no-wrap */
  fputs (_("\
  -f, --field=STR                     field name; should be followed by a -v.\n\
  -v, --value=STR                     field value; should be preceded by an -f.\n\
  -r, --record=STR                    record that will be inserted in the file.\n\
      --force                         insert the record even if it is violating\n\
                                        record restrictions.\n\
      --no-external                   don't use external descriptors.\n\
      --no-auto                       don't insert auto generated fields.\n\
      --verbose                       give a detailed report if the integrity check\n\
                                        fails.\n"), stdout);

#if defined REC_CRYPT_SUPPORT
  /* TRANSLATORS: --help output, encryption related options.
     no-wrap */
  fputs (_("\
  -s, --password=STR                  encrypt confidential fields with the given password.\n"),
         stdout);
#endif

  recutl_print_help_common ();

  puts ("");
  recutl_print_help_record_selection ();

  puts ("");
  /* TRANSLATORS: --help output, notes on recins.
     no-wrap */
  fputs (_("\
If no FILE is specified then the command acts like a filter, getting\n\
the data from standard input and writing the result to standard output.\n"), stdout);

  puts ("");
  recutl_print_help_footer ();
}


void recins_parse_args (int argc,
                        char **argv)
{
  int ret;
  char c;
  rec_field_t field = NULL;
  char *field_name = NULL;
  rec_record_t provided_record;

  while ((ret = getopt_long (argc,
                             argv,
                             RECORD_SELECTION_SHORT_ARGS
                             ENCRYPTION_SHORT_ARGS
                             "f:v:r:",
                             GNU_longOptions,
                             NULL)) != -1)
    {
      c = ret;
      switch (c)
        {
          COMMON_ARGS_CASES
          RECORD_SELECTION_ARGS_CASES
        case FORCE_ARG:
          recins_force = true;
          break;
        case VERBOSE_ARG:
          recins_verbose = true;
          break;
        case NAME_ARG:
        case 'f':
          if (field != NULL)
            {
              recutl_fatal (_("a -f should be followed by a -v\n"));
              exit (EXIT_FAILURE);
            }

          if (recins_record == NULL)
            {
              recins_record = rec_record_new ();
              rec_record_set_source (recins_record, "cmdli");
              rec_record_set_location (recins_record, 0);
            }

          if (!rec_field_name_p (optarg))
            recutl_fatal (_("invalid field name %s.\n"), optarg);

          field = rec_field_new (optarg, "foo");
          break;
        case VALUE_ARG:
        case 'v':
          if (field == NULL)
            recutl_fatal (_("a -v should be preceded by a -f\n"));

          rec_field_set_value (field, optarg);
          rec_mset_append (rec_record_mset (recins_record), MSET_FIELD, (void *) field, MSET_ANY);
          field = NULL;
          break;
        case NO_EXTERNAL_ARG:
          recins_external = false;
          break;
        case NO_AUTO_ARG:
          recins_auto = false;
          break;
#if defined REC_CRYPT_SUPPORT
        case PASSWORD_ARG:
        case 's':
          if (recins_password != NULL)
            recutl_fatal (_("more than one password was specified\n"));

          recins_password = xstrdup (optarg);
          break;
#endif
        case RECORD_ARG:
        case 'r':
          /* Parse the provided record and put it in recins_record.  */
          provided_record = rec_parse_record_str (optarg);
          if (!provided_record)
            recutl_fatal (_("error while parsing the record provided by -r\n"));

          if (recins_record)
            {
              /* Append the fields in provided_record into
                 recins_record.  */

              rec_mset_iterator_t iter = rec_mset_iterator (rec_record_mset (provided_record));
              while (rec_mset_iterator_next (&iter, MSET_FIELD,
                                             (const void **) &field, NULL))
                  {
                    rec_mset_append (rec_record_mset (recins_record),
                                     MSET_FIELD,
                                     (void *) rec_field_dup (field),
                                     MSET_ANY);
                    field = NULL;
                  }
                rec_mset_iterator_free (&iter);

                rec_record_destroy (provided_record);
                provided_record = NULL;
              }
            else
              recins_record = provided_record;

          break;
        default:
          exit (EXIT_FAILURE);
        }
    }

  if (field != NULL)
    recutl_fatal (_("please provide a value for the field %s\n"), field_name);

  /* Read the name of the file where to make the insertions.  */
  if (optind < argc)
    {
      if ((argc - optind) != 1)
        {
          recutl_print_help ();
          exit (EXIT_FAILURE);
        }

      recins_file = argv[optind++];
    }
}

void
recins_add_new_record (rec_db_t db)
{
  int flags = 0;

#if defined REC_CRYPT_SUPPORT

  /* Get the password interactively from the user if some field is
     declared as confidential in the requested record set.  */

  {
    rec_rset_t rset;
    rec_fex_t confidential_fields;

    if (recutl_type)
      {
        rset = rec_db_get_rset_by_type (db, recutl_type);

        if (rset)
          {
            confidential_fields = rec_rset_confidential (rset);
            if (!confidential_fields)
              recutl_out_of_memory ();

            if (rec_fex_size (confidential_fields) > 0)
              {
                if (!recins_password && recutl_interactive ())
                  {
                    recins_password = recutl_getpass (true);
                    if (!recins_password)
                      recutl_fatal ("not in an interactive terminal.\n");
                  }

                /* Passwords can't be empty.  */

                if (recins_password && (strlen (recins_password) == 0))
                  {
                    free (recins_password);
                    recins_password = NULL;
                  }
              }
          }
      }
  }

#endif /* REC_CRYPT_SUPPORT */

  /* Set flags flags and call the library to perform the
     requested insertion/replacement operation.  */

  if (recutl_insensitive)
    flags = flags | REC_F_ICASE;

  if (!recins_auto)
    flags = flags | REC_F_NOAUTO;

  if (!rec_db_insert (db,
                      recutl_type,
                      recutl_index (),
                      recutl_sex,
                      recutl_quick_str,
                      recutl_random,
                      recins_password,
                      recins_record,
                      flags))
    recutl_out_of_memory ();

  /* Check for the integrity of the resulting database.  */

  if (!recins_force && db)
    recutl_check_integrity (db, recins_verbose, recins_external);
}

int
main (int argc, char *argv[])
{
  rec_db_t db;

  recutl_init ("recins");

  recins_parse_args (argc, argv);

  db = recutl_read_db_from_file (recins_file);
  if (!db)
    /* Create an empty database.  */
    db = rec_db_new ();
  recins_add_new_record (db);

  if (!recutl_file_is_writable (recins_file))
    {
      recutl_error (_("file %s is not writable.\n"), recins_file);
      return EXIT_FAILURE;
    }
  recutl_write_db_to_file (db, recins_file);

  return EXIT_SUCCESS;
}
