/* PSPP - a program for statistical analysis.
   Copyright (C) 2016 Free Software Foundation, Inc.

   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 <stdlib.h>

#include "data/attributes.h"
#include "data/dataset.h"
#include "data/dictionary.h"
#include "data/format.h"
#include "data/variable.h"
#include "language/command.h"
#include "language/lexer/lexer.h"
#include "libpspp/array.h"
#include "libpspp/assertion.h"
#include "libpspp/i18n.h"
#include "libpspp/message.h"
#include "libpspp/str.h"

#include "gl/xalloc.h"

#include "gettext.h"
#define _(msgid) gettext (msgid)

enum key
  {
    K_NAME,
    K_TYPE,
    K_FORMAT,
    K_VAR_LABEL,
    K_VALUE_LABELS,
    K_MISSING_VALUES,
    K_MEASURE,
    K_ROLE,
    K_COLUMNS,
    K_ALIGNMENT,
    K_ATTRIBUTE,
  };

struct criterion
  {
    enum key key;
    char *attr_name;
    bool descending;
  };

static int
compare_ints (int a, int b)
{
  return a < b ? -1 : a > b;
}

static int
compare_formats (struct fmt_spec a, struct fmt_spec b)
{
  int retval = compare_ints (fmt_to_io (a.type), fmt_to_io (b.type));
  if (!retval)
    retval = compare_ints (a.w, b.w);
  if (!retval)
    retval = compare_ints (a.d, b.d);
  return retval;
}

static int
compare_var_labels (const struct variable *a, const struct variable *b)
{
  const char *a_label = var_get_label (a);
  const char *b_label = var_get_label (b);
  return utf8_strcasecmp (a_label ? a_label : "",
                          b_label ? b_label : "");
}

static int
map_measure (enum measure m)
{
  return (m == MEASURE_NOMINAL ? 0
          : m == MEASURE_ORDINAL ? 1
          : 2);
}

static int
map_role (enum var_role r)
{
  return (r == ROLE_INPUT ? 0
          : r == ROLE_TARGET ? 1
          : r == ROLE_BOTH ? 2
          : r == ROLE_NONE ? 3
          : r == ROLE_PARTITION ? 4
          : 5);
}

static const char *
get_attribute (const struct variable *v, const char *name)
{
  const struct attrset *set = var_get_attributes (v);
  const struct attribute *attr = attrset_lookup (set, name);
  const char *value = attr ? attribute_get_value (attr, 0) : NULL;
  return value ? value : "";
}

static int
map_alignment (enum alignment a)
{
  return (a == ALIGN_LEFT ? 0
          : a == ALIGN_RIGHT ? 1
          : 2);
}

static int
compare_vars (const void *a_, const void *b_, const void *c_)
{
  const struct variable *const *ap = a_;
  const struct variable *const *bp = b_;
  const struct variable *a = *ap;
  const struct variable *b = *bp;
  const struct criterion *c = c_;

  int retval;
  switch (c->key)
    {
    case K_NAME:
      retval = utf8_strverscasecmp (var_get_name (a), var_get_name (b));
      break;

    case K_TYPE:
      retval = compare_ints (var_get_width (a), var_get_width (b));
      break;

    case K_FORMAT:
      retval = compare_formats (var_get_print_format (a),
                                var_get_print_format (b));
      break;

    case K_VAR_LABEL:
      retval = compare_var_labels (a, b);
      break;

    case K_VALUE_LABELS:
      retval = compare_ints (var_has_value_labels (a),
                             var_has_value_labels (b));
      break;

    case K_MISSING_VALUES:
      retval = compare_ints (var_has_missing_values (a),
                             var_has_missing_values (b));
      break;

    case K_MEASURE:
      retval = compare_ints (map_measure (var_get_measure (a)),
                             map_measure (var_get_measure (b)));
      break;

    case K_ROLE:
      retval = compare_ints (map_role (var_get_role (a)),
                             map_role (var_get_role (b)));
      break;

    case K_COLUMNS:
      retval = compare_ints (var_get_display_width (a),
                             var_get_display_width (b));
      break;

    case K_ALIGNMENT:
      retval = compare_ints (map_alignment (var_get_alignment (a)),
                             map_alignment (var_get_alignment (b)));
      break;

    case K_ATTRIBUTE:
      retval = utf8_strcasecmp (get_attribute (a, c->attr_name),
                                get_attribute (b, c->attr_name));
      break;

    default:
      NOT_REACHED ();
    }

  /* Make this a stable sort. */
  if (!retval)
    {
      size_t a_index = var_get_dict_index (a);
      size_t b_index = var_get_dict_index (b);
      retval = a_index < b_index ? -1 : a_index > b_index;
    }

  if (c->descending)
    retval = -retval;

  return retval;
}

/* Performs SORT VARIABLES command. */
int
cmd_sort_variables (struct lexer *lexer, struct dataset *ds)
{
  enum cmd_result result = CMD_FAILURE;

  lex_match (lexer, T_BY);

  /* Parse sort key. */
  struct criterion c = { .attr_name = NULL };
  if (lex_match_id (lexer, "NAME"))
    c.key = K_NAME;
  else if (lex_match_id (lexer, "TYPE"))
    c.key = K_TYPE;
  else if (lex_match_id (lexer, "FORMAT"))
    c.key = K_FORMAT;
  else if (lex_match_id (lexer, "LABEL"))
    c.key = K_VAR_LABEL;
  else if (lex_match_id (lexer, "VALUES"))
    c.key = K_VALUE_LABELS;
  else if (lex_match_id (lexer, "MISSING"))
    c.key = K_MISSING_VALUES;
  else if (lex_match_id (lexer, "MEASURE"))
    c.key = K_MEASURE;
  else if (lex_match_id (lexer, "ROLE"))
    c.key = K_ROLE;
  else if (lex_match_id (lexer, "COLUMNS"))
    c.key = K_COLUMNS;
  else if (lex_match_id (lexer, "ALIGNMENT"))
    c.key = K_ALIGNMENT;
  else if (lex_match_id (lexer, "ATTRIBUTE"))
    {
      if (!lex_force_id (lexer))
        goto exit;
      c.key = K_ATTRIBUTE;
      c.attr_name = xstrdup (lex_tokcstr (lexer));
      lex_get (lexer);
    }
  else
    {
      lex_error_expecting (lexer, "NAME", "TYPE", "FORMAT", "LABEL",
                           "VALUES", "MISSING", "MEASURE", "ROLE",
                           "COLUMNS", "ALIGNMENT", "ATTRIBUTE");
      return CMD_FAILURE;
    }

  /* Parse sort direction. */
  if (lex_match (lexer, T_LPAREN))
    {
      if (lex_match_id (lexer, "A") || lex_match_id (lexer, "UP"))
        c.descending = false;
      else if (lex_match_id (lexer, "D") || lex_match_id (lexer, "DOWN"))
        c.descending = true;
      else
        {
          lex_error_expecting (lexer, "A", "D");
          goto exit;
        }
      if (!lex_force_match (lexer, T_RPAREN))
        goto exit;
    }
  else
    c.descending = false;

  /* Sort variables. */
  struct dictionary *d = dataset_dict (ds);
  struct variable **vars;
  size_t n_vars;
  dict_get_vars_mutable (d, &vars, &n_vars, 0);
  sort (vars, n_vars, sizeof *vars, compare_vars, &c);
  proc_execute (ds);
  dataset_reorder_vars (ds, vars, n_vars);
  free (vars);

  result = CMD_SUCCESS;

exit:
  free (c.attr_name);
  return result;
}
