/* rec2csv.c - rec to csv converter.  */

/* Copyright (C) 2011-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>
#define _(str) gettext (str)

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

/*
 * Global variables
 */

char             *rec2csv_record_type    = NULL;
rec_fex_t         rec2csv_sort_by_fields = NULL;
char              rec2csv_delim          = ',';

/*
 * Command line options management
 */

enum
  {
    COMMON_ARGS,
    DELIM_ARG,
    RECORD_TYPE_ARG,
    SORT_ARG
  };

static const struct option GNU_longOptions[] =
  {
    COMMON_LONG_ARGS,
    {"delim", required_argument, NULL, DELIM_ARG},
    {"type", required_argument, NULL, RECORD_TYPE_ARG},
    {"sort", required_argument, NULL, SORT_ARG},
    {NULL, 0, NULL, 0}
  };

/*
 * Functions.
 */

void
recutl_print_help (void)
{
  /* TRANSLATORS: --help output, rec2csv synopsis.
     no-wrap */
  printf (_("\
Usage: rec2csv [OPTIONS]... [REC_FILE]\n"));

  /* TRANSLATORS: --help output, rec2csv short description.
     no-wrap */
  fputs (_("\
Convert rec data into csv data.\n"), stdout);

  puts ("");
  /* TRANSLATORS: --help output, rec2csv options.
     no-wrap */
  fputs (_("\
  -d, --delim=char                    sets the deliminator (default ',')\n\
  -t, --type=TYPE                     record set to convert to csv; if this parameter\n\
                                        is omitted then the default record set is used\n\
  -S, --sort=FIELDS                   sort the output by the specified fields.\n"),
         stdout);

  recutl_print_help_common ();
  puts ("");
  recutl_print_help_footer ();
}

static void
rec2csv_parse_args (int argc,
                    char **argv)
{
  int ret;
  char c;

  while ((ret = getopt_long (argc,
                             argv,
                             "t:S:d:",
                             GNU_longOptions,
                             NULL)) != -1)
    {
      c = ret;
      switch (c)
        {
          COMMON_ARGS_CASES
        case DELIM_ARG:
        case 'd':
          rec2csv_delim = optarg[0];
          break;
        case RECORD_TYPE_ARG:
        case 't':
          rec2csv_record_type = xstrdup (optarg);
          break;
        case SORT_ARG:
        case 'S':
          if (rec2csv_sort_by_fields)
            recutl_fatal (_("only one list of fields can be specified\
 as a sorting criteria.\n"));

          /* Parse the field name.  */
          if (!rec_fex_check (optarg, REC_FEX_CSV))
            recutl_fatal (_("invalid field name list in -S.\n"));

          rec2csv_sort_by_fields = rec_fex_new (optarg, REC_FEX_CSV);
          if (!rec2csv_sort_by_fields)
            recutl_fatal (_("internal error creating fex.\n"));

          break;
        default:
          exit (EXIT_FAILURE);
        }
    }
}

static void
rec2csv_generate_csv (rec_rset_t rset,
                      rec_fex_t fex)
{
  rec_mset_iterator_t iter;
  rec_fex_elem_t fex_elem;
  rec_record_t record;
  rec_field_t field;
  char *field_name;
  char *tmp;
  size_t i;

  /* Generate the row with headers.  */
  for (i = 0; i < rec_fex_size (fex); i++)
    {
      if (i != 0)
        putc (rec2csv_delim, stdout);

      fex_elem = rec_fex_get (fex, i);
      field_name = xstrdup (rec_fex_elem_field_name (fex_elem));

      /* The header is FNAME or FNAME_N where N is the index starting
         at 1.  Note that we shall remove the trailing ':', if any. */
      if (field_name[strlen(field_name)-1] == ':')
        field_name[strlen(field_name)-1] = '\0';


      if (rec_fex_elem_min (fex_elem) != 0)
        {
          if (asprintf (&tmp, "%s_%d",
                        field_name,
                        rec_fex_elem_min (fex_elem) + 1) == -1)
            recutl_out_of_memory ();
        }
      else
        {
          if (asprintf (&tmp, "%s", field_name) == -1)
            recutl_out_of_memory ();
        }

      csv_fwrite (stdout, tmp, strlen(tmp));
      free (field_name);
      free (tmp);
    }

  putc ('\n', stdout);

  /* Generate the data rows.  */
  iter = rec_mset_iterator (rec_rset_mset (rset));
  while (rec_mset_iterator_next (&iter, MSET_RECORD, (const void**) &record, NULL))
    {
      for (i = 0; i < rec_fex_size (fex); i++)
        {
          if (i != 0)
            putc (rec2csv_delim, stdout);

          fex_elem = rec_fex_get (fex, i);
          field = rec_record_get_field_by_name (record,
                                                rec_fex_elem_field_name (fex_elem),
                                                rec_fex_elem_min (fex_elem));
          if (field)
            csv_fwrite (stdout,
                        rec_field_value (field),
                        strlen (rec_field_value (field)));
        }

      putc ('\n', stdout);
    }

  rec_mset_iterator_free (&iter);
}

static rec_fex_t
rec2csv_determine_fields (rec_rset_t rset)
{
  rec_fex_t fields;
  rec_mset_iterator_t iter_rset;
  rec_mset_iterator_t iter_record;
  rec_record_t record;
  rec_field_t field;
  int field_index;

  fields = rec_fex_new (NULL, REC_FEX_SIMPLE);

  iter_rset = rec_mset_iterator (rec_rset_mset (rset));
  while (rec_mset_iterator_next (&iter_rset, MSET_RECORD, (const void **) &record, NULL))
    {
      iter_record = rec_mset_iterator (rec_record_mset (record));
      while (rec_mset_iterator_next (&iter_record, MSET_FIELD, (const void **) &field, NULL))
        {
          field_index = rec_record_get_field_index_by_name (record, field);

          if (!rec_fex_member_p (fields,
                                 rec_field_name (field),
                                 field_index, field_index))
            rec_fex_append (fields,
                            rec_field_name (field),
                            field_index, field_index);
        }

      rec_mset_iterator_free (&iter_record);
    }

  rec_mset_iterator_free (&iter_rset);

  return fields;
}

static bool
rec2csv_process_data (rec_db_t db)
{
  bool ret;
  rec_fex_t row_fields;
  size_t i;
  rec_rset_t rset;

  ret = true;

  for (i = 0; i < rec_db_size (db); i++)
    {
      rset = rec_db_get_rset (db, i);
      if (((rec2csv_record_type)
           && rec_rset_type (rset)
           && (strcmp (rec_rset_type (rset),
                       rec2csv_record_type) == 0))
          || (!rec2csv_record_type
              && (!rec_rset_type (rset) ||
                  (rec_db_size (db) == 1))))
        {
          /* Process this record set.  */
          if (!rec_rset_sort (rset, rec2csv_sort_by_fields))
            recutl_out_of_memory ();

          /* Build the fields that will appear in the row. */
          row_fields = rec2csv_determine_fields (rset);

          /* Generate the csv data.  */
          rec2csv_generate_csv (rset, row_fields);

          /* Cleanup.  */
          rec_fex_destroy (row_fields);
        }
    }

  return ret;
}

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

  res = 0;

  recutl_init ("rec2csv");

  /* Parse arguments.  */
  rec2csv_parse_args (argc, argv);

  /* Get the input data.  */
  db = recutl_build_db (argc, argv);
  if (!db)
    res = 1;
  else
    {
      /* Process the data.  */
      if (!rec2csv_process_data (db))
        res = 1;
    }

  rec_db_destroy (db);

  return res;
}
