/* datagen - Program to generate random data.
   Rick Smereka, Copyright (C) 1990-2005.

   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 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, get a copy via the Internet at
   http://gnu.org/copyleft/gpl.html or write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
   MA 02111-1307 USA

   You can contact the author via email at rsmereka@future-lab.com

   Program syntax is:

   datagen -Snnn -N -Fnnn -Rnnn -L -V mxrec mxkey_char char_range
           outfile

   Where 'mxrec' is the number of records to generate,
   'mxkey_char' is the number of 5 character numbers to
   generate, 'char_range' is the maximum number generated
   per field and 'outfile' is the name and path of the
   output file. Each data record will be preceeded with a
   5 digit sequence number. The sequence number will start
   at one by default, but can be altered by using the 'S'
   switch. The 'N' switch will suppress the generation
   of sequence numbers.

   Original DOS code Feb/90, Rick Smereka

   Added switches 'F' which is the field separator in ASCII
   decimal and 'R' which is the record separator. The default
   field separator is the space character and the default record
   separator is the carriage return/line feed combination.
   Jun/95, Rick Smereka

   Added 'L' switch to generate record length as first field.
   Removed 'mxkey_int' and 'int_range'. Jul/95, Rick Smereka

   Changed length field to 11 digits instead of 10 and
   added an extra byte on the beginning for a status
   indicator which will allways be 'A'. Jul/95, Rick Smereka

   Added 'V' switch for variable length fields.
   Jul/95, Rick Smereka

   Changed size of record header to 12 bytes (1st byte status,
   10 bytes record length and the record mark) from 13 bytes
   (1st byte status, 11 bytes record length and the record
   mark). Jul/95, Rick Smereka

   Port to QNX V4.23a. Jan/97, Rick Smereka

   Changed 'record' character array to be dynamically
   allocated so that records larger than 2k can be
   built. Size of 'record' is calculated as: (maxkey_char + 1) *
   6 + 1. Six is composed of a maximum of five digits plus the
   field mark. The extra byte is the record mark. Note that five
   bytes per field data is always assumed even though (with
   'is_var' 'TRUE') the data could be variable length.
   Jan/99, Rick Smereka

   Port to 32bit Windows under CodeWarrior V4.
   Port to HP-UX under GNU C 2.8.1.
   Feb/99, Rick Smereka

   Port to Red Hat Linux 5.2, Jul/99, Rick Smereka

   Fixed a bug that caused an infinite loop in the switch
   parse function. Changed 'usage' function to display
   correct switch character based on platform. Sep/99,
   Rick Smereka
   
   Removed log statements and unecessary 'printf' statements.
   Apr/2000, Rick Smereka

   Ported to Debian Linux. Jan/2003, Rick Smereka

   Fixed calculation of record size which was too short.
   Jan/2005, Rick Smereka */

#include "stdhead.h"

#define VERSION "1.94.01-2005.01.01"

int parse_comm_line(int, char **);
int do_switch(char *);
void usage(void);

/* option or switch structure */

struct
   {
   long seqn;           /* starting sequence number */
   int do_seq;          /* perform sequence number flag */
   int do_len;          /* place length as first field */
   int is_var;          /* are fields variable length */
   int field_sep;       /* field separator */
   int rec_sep;         /* record separator */
   long maxrec;         /* number of records to generate */
   int maxkey_char;     /* number of character key to generate */
   int char_range;      /* upper range of char number to generate */
   char outfile[200];   /* name of output file */
   } opt;

int main(int argc, char **argv)
{
   long i;
   int j;
   long now;
   int rnd;
   int len;
   char *record, key[200], *tmp;
   FILE *out;

      /* display logo based on platform */
#ifdef OS_UNIX
   printf("datagen for %s Version %s\n", SUB_PLATFORM_STRING, VERSION);
#else
   printf("datagen for %s Version %s\n", PLATFORM_STRING, VERSION);
#endif

   printf("by Rick Smereka, Copyright (c) 1990-2005\n");
   printf("datagen comes with ABSOLUTELY NO WARRANTY\n");
   printf("This is free software, and you are welcome to redistribute it\n");
   printf("under certain conditions; see 'gpl.txt' for information.\n");

   /* check number of parameters passed */

   if (argc == 1)
      {
      usage();
      return(0);
      }

   /* initialize */

   opt.seqn = 1L;
   opt.do_seq = TRUE;
   opt.do_len = FALSE;
   opt.is_var = FALSE;
   opt.field_sep = ' ';
   opt.rec_sep = 0;
   opt.maxrec = 0L;
   opt.maxkey_char = 0;
   opt.char_range = 0;
   opt.outfile[0] = EOS;

   /* parse switches */

   if (!parse_comm_line(argc, argv))
      exit(0);

   srand(time(&now) % 37);    /* seed random generator */

   if ((out = fopen(opt.outfile, "wb")) == NULL)
      {
      printf("datagen:Unable to open %s for writing, "
             "program abort\n", opt.outfile);
      exit(0);
      }

   /* allocate 'record' and tmp based on calculated
      length */

   len = (opt.maxkey_char + 1) * 6 + 16;

   if ((record = (char *)malloc(len + 1)) == (char *)NULL)
      {
      printf("datagen:alloc fail[record], program abort\n");
      exit(0);
      }

   if ((tmp = (char *)malloc(len + 1)) == (char *)NULL)
      {
      printf("datagen:alloc fail[tmp], program abort\n");
      free(record);
      exit(0);
      }

   for(i = 0L; i < opt.maxrec; i++)
      {
      record[0] = EOS;

      if (opt.do_seq)
         sprintf(record, "%05ld%c", opt.seqn++, opt.field_sep);

      for(j = 0; j < opt.maxkey_char; j++)
         {
         rnd = rand() % opt.char_range;

         if (opt.is_var)
            sprintf(key, "%d%c", rnd, opt.field_sep);
         else
            sprintf(key, "%05d%c", rnd, opt.field_sep);

         strcat(record, key);

         if (j == (opt.maxkey_char - 1))
            {
            len = strlen(record);
            record[len - 1] = EOS;
            }
         }

      len = strlen(record);

      /* if record delimter is zero, use CR/LF */

      if (!opt.rec_sep)
         {
         record[len] = (char)0x0d;
         record[len + 1] = (char)0x0a;
         record[len + 2] = EOS;
         }
      else
         {
         record[len] = opt.rec_sep;
         record[len + 1] = EOS;
         }

      if (opt.do_len)
         {
         len = strlen(record);
         sprintf(key, "A%010d%c", len, opt.field_sep);
         sprintf(tmp, "%s%s", key, record);
         strcpy(record, tmp);
         }

      fprintf(out, "%s", record);

      if (((i + 1L) % 1000) == 0L)
         printf("datagen:processed record %ld\r", i + 1L);
      }

   fclose(out);
   free(record);
   free(tmp);
   printf("datagen:done, created %ld records\n", opt.maxrec);
   return(0);
}

int parse_comm_line(int gc, char **gv)
{
   /* Parse entire command line. Function returns 'TRUE' upon success,
      'FALSE' otherwise. */

   char prm[255];
   int curparm = 1;
   int man_parms = 1;
   int req_man_parms = 4;

   while(curparm < gc)
      {
      strcpy(prm, gv[curparm]);

      if (prm[0] == SWITCH_CHAR)
         {
         if (!do_switch(prm))
            return(FALSE);

         curparm++;
         continue;
         }

      switch(man_parms)
         {
         case 1:
            if ((opt.maxrec = atol(prm)) <= 0L)
               {
               printf("datagen:illegal value[maxrec][%ld]\n", opt.maxrec);
               return(FALSE);
               }

            man_parms++;
            break;

         case 2:
            if ((opt.maxkey_char = atoi(prm)) < 0)
               {
               printf("datagen:illegal value[maxkey_char]\n");
               return(FALSE);
               }

            man_parms++;
            break;

         case 3:
            opt.char_range = atoi(prm);

            if (opt.maxkey_char > 0)
               {
               if (opt.char_range <= 0)
                  {
                  printf("datagen:illegal value[char_range]\n");
                  return(FALSE);
                  }
               }
            else
               if (opt.char_range < 0)
                  {
                  printf("datagen:illegal value[char_range]\n");
                  return(FALSE);
                  }

            man_parms++;
            break;

         case 4:
            strcpy(opt.outfile, prm);

            if (!strlen(opt.outfile))
               {
               printf("datagen:name of output file is null\n");
               return(FALSE);
               }

            man_parms++;
            break;
         };

      curparm++;
      }

   if (man_parms < (req_man_parms + 1))
      {
      printf("datagen:incorrect number of parameters\n");
      usage();
      return(FALSE);
      }

   return(TRUE);
}

int do_switch(char *parm)
{
   /* Parse and set one option based on a switch. */

   switch(toupper(parm[1]))
      {
      case 'S':
         if ((opt.seqn = atol(&parm[2])) <= 1L)
            {
            printf("datagen:illegal value on 'S' parameter\n");
            return(FALSE);
            }

         break;

      case 'N':
         opt.do_seq = FALSE;
         break;

      case 'F':
         if ((opt.field_sep = atoi(&parm[2])) < 1)
            {
            printf("datagen:illegal value on 'F' parameter\n");
            return(FALSE);
            }

         if (opt.field_sep > 255)
            {
            printf("datagen:out of range value on 'F' parameter\n");
            return(FALSE);
            }

         break;

      case 'R':
         if ((opt.rec_sep = atoi(&parm[2])) <= 1)
            {
            printf("datagen:illegal value on 'R' parameter\n");
            return(FALSE);
            }

         if (opt.rec_sep > 255)
            {
            printf("datagen:out of range value on 'R' parameter\n");
            return(FALSE);
            }

         break;

      case 'L':
         opt.do_len = TRUE;
         break;

      case 'V':
         opt.is_var = TRUE;
         break;

      default:
         printf("datagen:unknown parameter %s\n", parm);
      };

   return(TRUE);
}

void usage(void)
{
   /* Display program usage. */

   printf("DATAGEN: A program to generate numeric ASCII data.\n\n");
   printf("usage: datagen %cN %cSnnn %cFnnn %cRnnn %cL %cV maxrec\n", SWITCH_CHAR,
           SWITCH_CHAR, SWITCH_CHAR, SWITCH_CHAR, SWITCH_CHAR, SWITCH_CHAR);
   printf("               maxkey_char char_range outfile\n\n");
   printf("Where:\n");
   printf("'maxrec' is the number of records to generate\n");
   printf("'maxkey_char' is the number of fields in each record\n");
   printf("'char_range' is the largest ASCII number to be generated in a field\n");
   printf("'outfile' is the output file name\n");
   printf("'%cFnnn' is the field mark separator char (default is space %cF32)\n",
          SWITCH_CHAR, SWITCH_CHAR);
   printf("'%cL' is generate record length as first field\n", SWITCH_CHAR);
   printf("'%cN' is do not generate sequence numbers\n", SWITCH_CHAR);
   printf("'%cRnnn' is the record mark separator char (default is CRLF)\n", SWITCH_CHAR);
   printf("'%cSnnn' is the starting sequence number (default is 1 %cS1)\n",
          SWITCH_CHAR, SWITCH_CHAR);
   printf("'%cV' is generate variable length fields (instead of all being 5 char)\n",
          SWITCH_CHAR);
}
