/*
Copyright (c) 2016, Assaf Gordon <assafgordon@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.



build with:
  cc -o strcoll-test strcoll-test.c

See 'strcoll-test -h' for usage information.

test with:
  LC_ALL=C ./strcoll-test -svl "ab" "a\!b" "a\!c" "a.b" "a.c"
  LC_ALL=en_US.UTF-8 ./strcoll-test -svl "ab" "a\!b" "a\!c" "a.b" "a.c"
  LC_ALL=C ./strcoll-test -svl -M
  LC_ALL=en_US.UTF-8 ./strcoll-test -svl -M

*/

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <unistd.h>
#include <locale.h>
#include <string.h>
#include <stdbool.h>
#include <err.h>
#include <getopt.h>
#include <stddef.h>
#include <sys/utsname.h>

#ifdef __GLIBC__
#include <gnu/libc-version.h>
#endif

static bool debug_strcoll = false;
static bool debug_versions = false;
static bool debug_locale = false;


static void print_sys_versions(void)
{
  struct utsname b;
  int i = uname(&b);
  if (i != 0)
    err (1, "uname failed");
  fprintf(stderr,"%s %s", b.sysname, b.release);

  #ifdef __GLIBC__
  /* Ugly Hack */
  fprintf(stderr," glibc %s %s\n",
         gnu_get_libc_version(),
         gnu_get_libc_release());
  #else
  fprintf(stderr," not-glibc\n");
  #endif
}


static int compare(const void *p1, const void *p2)
{
  int i = strcoll(* (char * const *) p1, * (char * const *) p2);
  if (debug_strcoll)
    fprintf (stderr, "strcoll('%s', '%s') = %d\n",
             *(char * const*)p1, *(char * const*)p2, i);
  return i;
}


static void usage(const char* progname)
{
  printf("\
strcoll(3) tester\n\
By Assaf Gordon <assafgordon@gmail.com>\n\
License: Simplified BSD (BSD 2-Clause)\n\
\n\
Usage: %s [-svl] [[-KM] | TEXT1 TEXT2 TEXTn...] \n\
\n\
Sorts TEXT1 TEXT2 TEXTn... according to the currently\n\
active locale using strcoll(3) call.\n\
If no parameters are specified, assumes '-M' instead.\n\
\n\
Options:\n\
 -s   print result of each strcoll(3) call\n\
 -v   print uname/glibc version information (if available)\n\
 -l   print active local name\n\
\n\
 -K   use input from https://debbugs.gnu.org/23677\n\
 -M   use input from https://debbugs.gnu.org/24601 (default)\n\
\n\
Use LC_ALL to set locale.\n\
\n\
Examples:\n\
\n\
  $ %s -ls '!a' '$z' '#c'\n\
  active locale: en_US.UTF-8\n\
  strcoll('$z', '#c') = 23\n\
  strcoll('!a', '#c') = -2\n\
  !a\n\
  #c\n\
  $z\n\
\n\
\n\
  $ %s -svlK\n\
  Linux 3.13.0-88-generic glibc 2.19 stable\n\
  active locale: en_US.UTF-8\n\
  strcoll('/z', '/a') = 25\n\
  strcoll('!a', '!z') = -25\n\
  strcoll('/a', '!a') = 4\n\
  strcoll('/a', '!z') = -25\n\
  strcoll('/z', '!z') = 4\n\
  !a\n\
  /a\n\
  !z\n\
  /z\n\
\n",progname, progname, progname);

  exit (0);
}


int main (int argc, char* argv[])
{
  const char* locale;

  // https://debbugs.gnu.org/24601
  // reported by Mathew
  char* data_24601[] = {
    "+00",
    "-0c",
    "+02",
    "-02"
  };
  int opt;
  size_t i;

  // https://debbugs.gnu.org/23677
  // reported by Karl Berry
  char* data_23677[] = {
    "/z",
    "/a",
    "!a",
    "!z",
  };
  char **data = data_24601;
  size_t datalen = sizeof(data_24601)/sizeof(data_24601[0]);


  while ((opt = getopt(argc, argv, "hslvKM")) != -1)
    {
      switch (opt)
        {
        case 'h':
          usage(argv[0]); // does not return

        case 'v':
          debug_versions = true;
          break;

        case 'l':
          debug_locale = true;
          break;

        case 's':
          debug_strcoll = true;
          break;

        case 'K':
          data = data_23677;
          datalen = sizeof(data_23677)/sizeof(data_23677[0]);
          break;

        case 'M':
          data = data_24601;
          datalen = sizeof(data_24601)/sizeof(data_24601[0]);
          break;

        default: /* '?' */
          fprintf(stderr, "see -h for usage information");
          exit(EXIT_FAILURE);
        }
    }


  /* Use text strings from command line, if specfiied */
  if (optind < argc)
    {
      data = &argv[optind];
      datalen = argc - optind;
    }


  if (debug_versions)
    print_sys_versions ();


  /* Set locale and print if requested */
  locale = setlocale (LC_ALL, "");
  if (!locale)
    err (1, "setlocale failed");
  if (debug_locale)
    printf ("active locale: %s\n", locale);


  /* Sort the strings using strcoll */
  qsort (data, datalen, sizeof(char*), compare);


  /* Print the sorted output */
  for (i = 0 ; i<datalen; ++i)
    puts (data[i]);

  return 0;
}
