/* 
  rl - Select a random line from stdin or file.
  
  Copyright (C) 2001 Arthur de Jong
  
  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 2, 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, write to the Free Software Foundation,
  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  
*/


#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <errno.h>

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
#include <unistd.h>
#endif

/* from system.h: */
#if ENABLE_NLS
# include <libintl.h>
# define _(Text) gettext (Text)
#else
# undef bindtextdomain
# define bindtextdomain(Domain, Directory)
# undef textdomain
# define textdomain(Domain)
# define _(Text) Text
#endif

#ifndef HAVE_GETOPT_LONG
#include "getopt_long.h"
#endif


/* the maximum length a line should have */
#define MAXLINELENGTH 1024


/* the maximum value for --count */
#define MAXCOUNT 1024


/* the name of the program */
static char *program_name;


/* flag to indicate output */
static int quiet=0;


/* Option flags and variables */
static struct option const long_options[] =
{
  {"count",required_argument,NULL,'c'},
  {"uniq",no_argument,NULL,'u'},
  {"quiet",no_argument,NULL,'q'},
  {"silent",no_argument,NULL,'q'},
  {"help",no_argument,NULL,'h'},
  {"version",no_argument,NULL,'V'},
  {NULL,0,NULL,0}
};
/* for adding options you should add to
    long_options[]  (directly above)
    OPTION_STRING   (directly below)
    display_usage() (below)
    main()          (for the handling of the option) */
#define OPTION_STRING "c:uqhV"


/* display usage information */
static void display_usage(FILE *fp)
{
  fprintf(fp,_("Usage: %s [OPTIONS]... [FILE]...\n"),program_name);
  fprintf(fp,_("Select a random line from stdin or a file.\n\n"),program_name);
  fprintf(fp,_("  -c, --count=N  output N lines (defualt is 1)\n"));
  fprintf(fp,_("  -u, --uniq     only one instance of each line is retured\n"));
  fprintf(fp,_("  -q, --quiet, --silent\n"
               "                 do not output any errors or warnings\n"));
  fprintf(fp,_("  -h, --help     display this help and exit\n"));
  fprintf(fp,_("  -V, --version  output version information and exit\n"));
}


/* display a use --help notice */
static void display_tryhelp(FILE *fp)
{
  fprintf(fp,_("Try `%s --help' for more information.\n"),program_name);
}


/* display version information */
static void display_version(FILE *fp)
{
  fprintf(fp,"rl %s\n",VERSION);
  fprintf(fp,_("Written by Arthur de Jong.\n\n"));
  fprintf(fp,_("Copyright (C) 2001 Arthur de Jong.\n"
               "This is free software; see the source for copying conditions.  There is NO\n"
               "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"));
}


/* allocate memory with error-checking */
void *xmalloc(size_t size)
{
  void *res;
  res=malloc(size);
  if (res==NULL)
  {
    if (!quiet)
      fprintf(stderr,_("Error allocating memory!\n"));
    exit(1);
  }
  return res;
}


/* read a single line from the file 
   this function adds error-checking to fgets() 
   and allso strips the newline */
char *getline(FILE *fp,char *buffer)
{
  int linelen;
  
  while (1)
  {
    /* read the line */
    if (fgets(buffer,MAXLINELENGTH,fp)==NULL)
      return NULL;
    
    /* check the length of the line */
    linelen=strlen(buffer);
    
    /* loose the trailing newline if any */
    if ( (linelen>0) && (buffer[--linelen]=='\n') )
    {
      buffer[linelen]='\0';
      return buffer;
    }
    else
    {
      /* too long line probably binairy data */
      if (!quiet)
          fprintf(stderr,_("Line too long... skipping...\n"));
      /* read until end of line */
      while ( (fgets(buffer,MAXLINELENGTH,fp)!=NULL) &&
              ((linelen=strlen(buffer))>0) &&
              (buffer[linelen-1]!='\n') )
      {}
      /* and try again */
    }
  }
}


/* read the whole file and pick out count random lines and dump to stdout
   count and in are assumed to be reasonable values 
   (maybe a better functionname, my English is not that good )
   any line has an equal chance of getting picked
   and lines may be selected multiple times */
void rl_withreplace(FILE *in,int count)
{
  char buffer[MAXLINELENGTH];   /* buffer for storing the read line */
  char **result;                /* containing the selected lines */
  int line=0;                   /* the number of the current line */
  int i;                        /* counter for loops */
  
  /* initialize the result */
  result=(char **)xmalloc(sizeof(char *)*count);
  for (i=0;i<count;i++)
  {
    result[i]=(char *)xmalloc(sizeof(char)*MAXLINELENGTH);
    result[i][0]='\0'; /* just in case */
  }
    
  /* read the lines one by one */
  while (getline(in,buffer)!=NULL)
  {
    line++;
    for (i=0;i<count;i++)
    {
       /* NOTE: this may fail if there are a LOT of lines in the file */
       if (rand()<=(RAND_MAX/line))
       {
         /* save a new line */
         strncpy(result[i],buffer,MAXLINELENGTH);
       }
    }
  }

  /* dump the result (if any lines were read) */
  if (line>0)
  {
    for (i=0;i<count;i++)
      printf("%s\n",result[i]);
  }

  /* free the memory! */
  for (i=0;i<count;i++)
  {
    free(result[i]);
  }
  free(result);
}


/* read the whole file and pick out count random lines and dump to stdout
   count and in are assumed to be reasonable values 
   (maybe a better functionname, my English is not that good )
   any line can only be picked once */
void rl_withoutreplace(FILE *in,int count)
{
  char buffer[MAXLINELENGTH];   /* buffer for storing the read line */
  char **result;                /* containing the selected lines */
  int line=0;                   /* the number of the current line */
  int i;                        /* counter for loops */
  
  /* initialize the result */
  result=(char **)xmalloc(sizeof(char *)*count);
  for (i=0;i<count;i++)
  {
    result[i]=(char *)xmalloc(sizeof(char)*MAXLINELENGTH);
    result[i][0]='\0'; /* just in case */
  }

  /* first fill the buffer */
  while ((line<count)&&(getline(in,result[line])!=NULL))
  {
    line++;
  }
  
  /* check if the buffer is filled */
  if (line<count)
  {
    if (!quiet)
      fprintf(stderr,_("Warning: less than %d lines were read.\n"),count);
  }
  else
  {
    /* read the lines one by one */
    while (getline(in,buffer)!=NULL)
    {
      line++;
      if ((rand()/count)<=(RAND_MAX/line))
      {
        i=rand()/(RAND_MAX/count);
        strncpy(result[i],buffer,MAXLINELENGTH);
      }
    }
  }

  /* dump the result (if any lines were read) */
  for (i=0;(i<line)&&(i<count);i++)
  {
    printf("%s\n",result[i]);
  }
  
  /* free the memory! */
  for (i=0;i<count;i++)
  {
    free(result[i]);
  }
  free(result);
}


/* the main program */
int main(int argc,char **argv)
{
  int c;        /* option charaters */
  FILE *fp;     /* for reading command-line specified files */
  int count=1;  /* the command-line parameter */
  char *endptr; /* used for command-line parsing */
  int uniq=0;   /* wether to return same lines or not */

  program_name=argv[0];
  
  /* parse command-line options */
  while ((c=getopt_long(argc,argv,OPTION_STRING,
                        long_options,(int *)NULL))!=-1)
  { 
    /* find out which option was specified */
    switch (c)
    {
    case 'V': /* -V, --version */
      display_version(stdout);
      exit(0);
    case 'h': /* -h, --help */
      display_usage(stdout);
      exit(0);
    case 'c': /* -c, --count=N */
      count=strtol(optarg,&endptr,0);
      if ( (optarg[0]=='\0') || (endptr[0]!='\0') || 
           (count<1) || (count>MAXCOUNT) )
      {
        if (!quiet)
        {
          fprintf(stderr,_("%s: invalid argument to count\n"),program_name);
          display_tryhelp(stderr);
        }
        exit(1);
      }
      break;
    case 'u': /* -u, --uniq */
      uniq=1;
      break;
    case 'q': /* -q, --quiet, --silent */
      quiet=1; /* true */
      opterr=0; /* disable error-reporting of getopt() */
      break;
    case ':': /* missing parameter of an option */
    case '?': /* unknown option */
    default:  /* undefined */
      if (!quiet)
        display_tryhelp(stderr);
      exit(1);
    }
  }

  /* intialize the random-number generator */
  randomize();
  
  /* rest of parameters are filenames */
  if (optind>=argc)
  {
    if (uniq)
      rl_withoutreplace(stdin,count);
    else
      rl_withreplace(stdin,count);
  }
  else
  {
    for (;optind<argc;optind++)
    {
      if (strncmp("-",argv[optind],2)==0)
      {
        if (uniq)
          rl_withoutreplace(stdin,count);
        else
          rl_withreplace(stdin,count);
      }
      else
      {
        if ((fp=fopen(argv[optind],"r"))==NULL)
        {
          if (!quiet)
            fprintf(stderr,_("Error opening %s: %s\n"),argv[optind],strerror(errno));
        }
        else
        {
          if (uniq)
            rl_withoutreplace(fp,count);
          else
            rl_withreplace(fp,count);
          fclose(fp);
        }
      }
    }
  }
    
  exit (0);
}
