/**
 * sort.c -- functions related to sort functionality
 *    ______      ___
 *   / ____/___  /   | _____________  __________
 *  / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
 * / /_/ / /_/ / ___ / /__/ /__/  __(__  |__  )
 * \____/\____/_/  |_\___/\___/\___/____/____/
 *
 * The MIT License (MIT)
 * Copyright (c) 2009-2016 Gerardo Orellana <hello @ goaccess.io>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>

#include "error.h"
#include "settings.h"
#include "util.h"

#include "sort.h"

/* *INDENT-OFF* */
const int sort_choices[][SORT_MAX_OPTS] = {
  /* VISITORS */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
  /* REQUESTS */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, SORT_BY_PROT, SORT_BY_MTHD, -1},
  /* REQUESTS_STATIC */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, SORT_BY_PROT, SORT_BY_MTHD, -1},
  /* NOT_FOUND */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, SORT_BY_PROT, SORT_BY_MTHD, -1},
  /* HOSTS */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
  /* OS */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
  /* BROWSERS */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
  /* VISIT_TIMES */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
  /* VIRTUAL_HOSTS */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
  /* REFERRERS */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
  /* REFERRING_SITES */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
  /* KEYPHRASES */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
  /* STATUS_CODES */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
  /* REMOTE_USER */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
#ifdef HAVE_GEOLOCATION
  /* GEO_LOCATION */
  {SORT_BY_HITS, SORT_BY_VISITORS, SORT_BY_DATA, SORT_BY_BW, SORT_BY_AVGTS, SORT_BY_CUMTS, SORT_BY_MAXTS, -1},
#endif
};

static GEnum FIELD[] = {
  {"BY_HITS"     , SORT_BY_HITS     } ,
  {"BY_VISITORS" , SORT_BY_VISITORS } ,
  {"BY_DATA"     , SORT_BY_DATA     } ,
  {"BY_BW"       , SORT_BY_BW       } ,
  {"BY_AVGTS"    , SORT_BY_AVGTS    } ,
  {"BY_CUMTS"    , SORT_BY_CUMTS    } ,
  {"BY_MAXTS"    , SORT_BY_MAXTS    } ,
  {"BY_PROT"     , SORT_BY_PROT     } ,
  {"BY_MTHD"     , SORT_BY_MTHD     } ,
};

static GEnum ORDER[] = {
  {"ASC"  , SORT_ASC  } ,
  {"DESC" , SORT_DESC } ,
};

GSort module_sort[TOTAL_MODULES] = {
  {VISITORS            , SORT_BY_DATA , SORT_DESC } ,
  {REQUESTS            , SORT_BY_HITS , SORT_DESC } ,
  {REQUESTS_STATIC     , SORT_BY_HITS , SORT_DESC } ,
  {NOT_FOUND           , SORT_BY_HITS , SORT_DESC } ,
  {HOSTS               , SORT_BY_HITS , SORT_DESC } ,
  {OS                  , SORT_BY_HITS , SORT_DESC } ,
  {BROWSERS            , SORT_BY_HITS , SORT_DESC } ,
  {VISIT_TIMES         , SORT_BY_DATA , SORT_ASC  } ,
  {VIRTUAL_HOSTS       , SORT_BY_HITS , SORT_DESC } ,
  {REFERRERS           , SORT_BY_HITS , SORT_DESC } ,
  {REFERRING_SITES     , SORT_BY_HITS , SORT_DESC } ,
  {KEYPHRASES          , SORT_BY_HITS , SORT_DESC } ,
  {STATUS_CODES        , SORT_BY_HITS , SORT_DESC } ,
  {REMOTE_USER         , SORT_BY_HITS , SORT_DESC } ,
#ifdef HAVE_GEOLOCATION
  {GEO_LOCATION        , SORT_BY_HITS , SORT_DESC } ,
#endif
};
/* *INDENT-ON* */

/* Sort an array of strings ascending */
int
strcmp_asc (const void *a, const void *b)
{
  return strcmp (*((char **) a), *((char **) b));
}

/* Sort 'data' metric ascending */
static int
cmp_data_asc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;
  return strcmp (ia->metrics->data, ib->metrics->data);
}

/* Sort 'data' metric descending */
static int
cmp_data_desc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;
  return strcmp (ib->metrics->data, ia->metrics->data);
}

/* Sort 'hits' metric descending */
static int
cmp_num_desc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  int va = ia->metrics->hits;
  int vb = ib->metrics->hits;

  return (va < vb) - (va > vb);
}

/* Sort 'hits' metric ascending */
static int
cmp_num_asc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  int va = ia->metrics->hits;
  int vb = ib->metrics->hits;

  return (va > vb) - (va < vb);
}

/* Sort 'visitors' metric descending */
static int
cmp_vis_desc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  int va = ia->metrics->visitors;
  int vb = ib->metrics->visitors;

  return (va < vb) - (va > vb);
}

/* Sort 'visitors' metric ascending */
static int
cmp_vis_asc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  int va = ia->metrics->visitors;
  int vb = ib->metrics->visitors;

  return (va > vb) - (va < vb);
}

/* Sort GRawDataItem value descending */
static int
cmp_raw_num_desc (const void *a, const void *b)
{
  const GRawDataItem *ia = a;
  const GRawDataItem *ib = b;

  int va = ia->value.ivalue;
  int vb = ib->value.ivalue;

  return (va < vb) - (va > vb);
}

/* Sort GRawDataItem value descending */
static int
cmp_raw_str_desc (const void *a, const void *b)
{
  const GRawDataItem *ia = a;
  const GRawDataItem *ib = b;

  return strcmp (ib->value.svalue, ia->value.svalue);
}

/* Sort 'bandwidth' metric descending */
static int
cmp_bw_desc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  uint64_t va = ia->metrics->bw.nbw;
  uint64_t vb = ib->metrics->bw.nbw;

  return (va < vb) - (va > vb);
}

/* Sort 'bandwidth' metric ascending */
static int
cmp_bw_asc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  uint64_t va = ia->metrics->bw.nbw;
  uint64_t vb = ib->metrics->bw.nbw;

  return (va > vb) - (va < vb);
}

/* Sort 'avgts' metric descending */
static int
cmp_avgts_desc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  uint64_t va = ia->metrics->avgts.nts;
  uint64_t vb = ib->metrics->avgts.nts;

  return (va < vb) - (va > vb);
}

/* Sort 'avgts' metric ascending */
static int
cmp_avgts_asc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  uint64_t va = ia->metrics->avgts.nts;
  uint64_t vb = ib->metrics->avgts.nts;

  return (va > vb) - (va < vb);
}

/* Sort 'cumts' metric descending */
static int
cmp_cumts_desc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  uint64_t va = ia->metrics->cumts.nts;
  uint64_t vb = ib->metrics->cumts.nts;

  return (va < vb) - (va > vb);
}

/* Sort 'cumts' metric ascending */
static int
cmp_cumts_asc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  uint64_t va = ia->metrics->cumts.nts;
  uint64_t vb = ib->metrics->cumts.nts;

  return (va > vb) - (va < vb);
}

/* Sort 'maxts' metric descending */
static int
cmp_maxts_desc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  uint64_t va = ia->metrics->maxts.nts;
  uint64_t vb = ib->metrics->maxts.nts;

  return (va < vb) - (va > vb);
}

/* Sort 'maxts' metric ascending */
static int
cmp_maxts_asc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;

  uint64_t va = ia->metrics->maxts.nts;
  uint64_t vb = ib->metrics->maxts.nts;

  return (va > vb) - (va < vb);
}

/* Sort 'protocol' metric ascending */
static int
cmp_proto_asc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;
  return strcmp (ia->metrics->protocol, ib->metrics->protocol);
}

/* Sort 'protocol' metric descending */
static int
cmp_proto_desc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;
  return strcmp (ib->metrics->protocol, ia->metrics->protocol);
}

/* Sort 'method' metric ascending */
static int
cmp_mthd_asc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;
  return strcmp (ia->metrics->method, ib->metrics->method);
}

/* Sort 'method' metric descending */
static int
cmp_mthd_desc (const void *a, const void *b)
{
  const GHolderItem *ia = a;
  const GHolderItem *ib = b;
  return strcmp (ib->metrics->method, ia->metrics->method);
}

/* Given a string sort field, get the enum field value.
 *
 * On error, -1 is returned.
 * On success, the enumerated field value is returned. */
int
get_sort_field_enum (const char *str)
{
  return str2enum (FIELD, ARRAY_SIZE (FIELD), str);
}

/* Given a string sort order, get the enum order value.
 *
 * On error, -1 is returned.
 * On success, the enumerated order value is returned. */
int
get_sort_order_enum (const char *str)
{
  return str2enum (ORDER, ARRAY_SIZE (ORDER), str);
}

/* Given a GSortOrder enum value, return the corresponding string.
 *
 * The string corresponding to the enumerated order value is returned. */
const char *
get_sort_order_str (GSortOrder order)
{
  return ORDER[order].str;
}

/* Given a GSortField enum value, return the corresponding string.
 *
 * The string corresponding to the enumerated field value is returned. */
const char *
get_sort_field_str (GSortField field)
{
  return FIELD[field].str;
}

/* Given a GSortField enum value, return the corresponding key.
 *
 * The key corresponding to the enumerated field value is returned. */
const char *
get_sort_field_key (GSortField field)
{
  static const char *field2key[][2] = {
    {"BY_HITS", "hits"},
    {"BY_VISITORS", "visitors"},
    {"BY_DATA", "data"},
    {"BY_BW", "bytes"},
    {"BY_AVGTS", "avgts"},
    {"BY_CUMTS", "cumts"},
    {"BY_MAXTS", "maxts"},
    {"BY_PROT", "protocol"},
    {"BY_MTHD", "method"},
  };

  return field2key[field][1];
}

/* Set the initial metric sort per module/panel.
 *
 * On error, function returns.
 * On success, panel metrics are sorted. */
void
set_initial_sort (const char *smod, const char *sfield, const char *ssort)
{
  int module, field, order;
  if ((module = get_module_enum (smod)) == -1)
    return;

  if ((field = get_sort_field_enum (sfield)) == -1)
    return;
  if ((order = get_sort_order_enum (ssort)) == -1)
    return;
  if (!can_sort_module (module, field))
    return;

  module_sort[module].field = field;
  module_sort[module].sort = order;
}

/* Determine if module/panel metric can be sorted.
 *
 * On error or if metric can't be sorted, 0 is returned.
 * On success, 1 is returned. */
int
can_sort_module (GModule module, int field)
{
  int i, can_sort = 0;
  for (i = 0; -1 != sort_choices[module][i]; i++) {
    if (sort_choices[module][i] != field)
      continue;
    if (SORT_BY_AVGTS == field && !conf.serve_usecs)
      continue;
    if (SORT_BY_CUMTS == field && !conf.serve_usecs)
      continue;
    if (SORT_BY_MAXTS == field && !conf.serve_usecs)
      continue;
    else if (SORT_BY_BW == field && !conf.bandwidth)
      continue;
    else if (SORT_BY_PROT == field && !conf.append_protocol)
      continue;
    else if (SORT_BY_MTHD == field && !conf.append_method)
      continue;

    can_sort = 1;
    break;
  }

  return can_sort;
}

/* Parse all initial sort options from the config file.
 *
 * On error, function returns.
 * On success, panel metrics are sorted. */
void
parse_initial_sort (void)
{
  int i;
  char module[SORT_MODULE_LEN], field[SORT_FIELD_LEN], order[SORT_ORDER_LEN];
  for (i = 0; i < conf.sort_panel_idx; ++i) {
    if (sscanf
        (conf.sort_panels[i], "%15[^','],%11[^','],%4s", module, field,
         order) != 3)
      continue;
    set_initial_sort (module, field, order);
  }
}

/* Apply user defined sort */
void
sort_holder_items (GHolderItem * items, int size, GSort sort)
{
  switch (sort.field) {
  case SORT_BY_HITS:
    if (sort.sort == SORT_DESC)
      qsort (items, size, sizeof (GHolderItem), cmp_num_desc);
    else
      qsort (items, size, sizeof (GHolderItem), cmp_num_asc);
    break;
  case SORT_BY_VISITORS:
    if (sort.sort == SORT_DESC)
      qsort (items, size, sizeof (GHolderItem), cmp_vis_desc);
    else
      qsort (items, size, sizeof (GHolderItem), cmp_vis_asc);
    break;
  case SORT_BY_DATA:
    if (sort.sort == SORT_DESC)
      qsort (items, size, sizeof (GHolderItem), cmp_data_desc);
    else
      qsort (items, size, sizeof (GHolderItem), cmp_data_asc);
    break;
  case SORT_BY_BW:
    if (sort.sort == SORT_DESC)
      qsort (items, size, sizeof (GHolderItem), cmp_bw_desc);
    else
      qsort (items, size, sizeof (GHolderItem), cmp_bw_asc);
    break;
  case SORT_BY_AVGTS:
    if (sort.sort == SORT_DESC)
      qsort (items, size, sizeof (GHolderItem), cmp_avgts_desc);
    else
      qsort (items, size, sizeof (GHolderItem), cmp_avgts_asc);
    break;
  case SORT_BY_CUMTS:
    if (sort.sort == SORT_DESC)
      qsort (items, size, sizeof (GHolderItem), cmp_cumts_desc);
    else
      qsort (items, size, sizeof (GHolderItem), cmp_cumts_asc);
    break;
  case SORT_BY_MAXTS:
    if (sort.sort == SORT_DESC)
      qsort (items, size, sizeof (GHolderItem), cmp_maxts_desc);
    else
      qsort (items, size, sizeof (GHolderItem), cmp_maxts_asc);
    break;
  case SORT_BY_PROT:
    if (sort.sort == SORT_DESC)
      qsort (items, size, sizeof (GHolderItem), cmp_proto_desc);
    else
      qsort (items, size, sizeof (GHolderItem), cmp_proto_asc);
    break;
  case SORT_BY_MTHD:
    if (sort.sort == SORT_DESC)
      qsort (items, size, sizeof (GHolderItem), cmp_mthd_desc);
    else
      qsort (items, size, sizeof (GHolderItem), cmp_mthd_asc);
    break;
  }
}

/* Sort raw numeric data in a descending order for the first run
 * (default sort)
 *
 * On success, raw data sorted in a descending order. */
GRawData *
sort_raw_num_data (GRawData * raw_data, int ht_size)
{
  qsort (raw_data->items, ht_size, sizeof *(raw_data->items), cmp_raw_num_desc);
  return raw_data;
}

/* Sort raw string data in a descending order for the first run.
 *
 * On success, raw data sorted in a descending order. */
GRawData *
sort_raw_str_data (GRawData * raw_data, int ht_size)
{
  qsort (raw_data->items, ht_size, sizeof *(raw_data->items), cmp_raw_str_desc);
  return raw_data;
}
