# datagen - Program to generate random data.
# Rick Smereka, Copyright (C) 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 rick_smereka@yahoo.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.

# Initial tcl version. Apr/2005, Rick Smereka

# required path to library modules
set TCLDIR /home/rick/gpl/tcl

# required modules
source $TCLDIR/lib/stdhead.tcl

const APNAME "datagen.tcl"
const VERSION "1.94.01-2005.04.03"

# global option or switch array
set opt(seqn) 1
set opt(do_seq) 1
set opt(do_len) 0
set opt(is_var) 0
set opt(field_sep) 32
set opt(rec_sep) 0
set opt(maxrec) 0
set opt(maxkey_char) 0
set opt(char_range) 0
set opt(outfile) ""

global opt

proc parse_comm_line {gc gv} {
   # Parse entire command line. Returns 1 upon success,
   # 0 otherwise.
   global SWITCH_CHAR opt
   const req_man_parms 4
   set curparm 0
   set man_parms 1

   while {$curparm < $gc} {
      set prm [lindex $gv $curparm]

      if {[string index $prm 0] == $SWITCH_CHAR} {
         if {![do_switch $prm]} {
            return 0
         }

         incr curparm
         continue
      }

      switch $man_parms {
         1 {
            if {![num $prm]} {
               puts "non numeric(maxrec)"
               return 0
            }

            if {$prm <= 0} {
               puts "illegal value(maxrec) must be >0"
               return 0
            }

            set opt(maxrec) $prm
            incr man_parms
         }

         2 {
            if {![num $prm]} {
               puts "non numeric(maxkey_char)"
               return 0
            }

            if {$prm < 0} {
               puts "illegal value(maxkey_char) must be >=0"
               return 0
            }

            set opt(maxkey_char) $prm
            incr man_parms
         }

         3 {
            if {![num $prm]} {
               puts "non numeric(char_range)"
               return 0
            }

            if {$opt(maxkey_char) > 0} {
               if {$prm <= 0} {
                  puts "illegal value(char_range) must be >0 when \
                        'maxkey_char' >0"
                  return 0
               }
            } elseif {$prm < 0} {
               puts "illegal value(char_range) must be >= 0"
               return 0
            }

            set opt(char_range) $prm
            incr man_parms
         }
               

         4 {
            set opt(outfile) $prm
            incr man_parms
         }
      }

      incr curparm
   }

   if {$man_parms < [expr $req_man_parms + 1]} {
      puts "incorrect number of parameters"
      usage
      return 0
   }

   return 1
}

proc do_switch {parm} {
   # Parse and set one option based on a switch. Returns 1 upon
   # success, 0 otherwise.
   global opt

   set sw [string toupper [string index $parm 1]]

   switch $sw {
      S {
         if {[string length $parm] < 3} {
            puts "missing squence number on 'S' parameter"
            return 0
         }

         set val [string range $parm 2 end]

         if {![num $val]} {
            puts "non numeric value on 'S' parameter"
            return 0
         }

         if {$val <= 1} {
            puts "illegal range on 'S' parameter must be >=1"
            return 0
         }

         set opt(seqn) $val
      }

      N {
         set opt(do_seq) 0
      }

      F {
         if {[string length $parm] < 3} {
            puts "missing ASCII code number on 'F' parameter"
            return 0
         }

         set val [string range $parm 2 end]

         if {![num $val]} {
            puts "non numeric value on 'F' parameter"
            return 0
         }

         if {$val < 1 || $val > 255} {
            puts "illegal range on 'F' parameter must be between 1 and 255"
            return 0
         }

         set opt(field_sep) $val
      }

      R {
         if {[string length $parm] < 3} {
            puts "missing ASCII code on 'R' parameter"
            return 0
         }

         set val [string range $parm 2 end]

         if {![num $val]} {
            puts "non numeric value on 'R' parameter"
            return 0
         }

         if {$val < 1 || $val > 255} {
            puts "illegal range on 'R' parameter must be between 1 and 255"
            return 0
         }

         set opt(rec_sep) $val
      }
         
      L {
         set opt(do_len) 1
      }

      V {
         set opt(is_var) 1
      }

      default {
         puts "unknown parameter '$parm'"
         return 0
      }
   }

   return 1
}

proc usage {} {
   # Display program usage.
   global APNAME SWITCH_CHAR

   puts "$APNAME: A program to generate numeric ASCII data"
   set mes "usage: %s %sN %sSnnn %sFnnn %sRnnn %sL %sV" 
   puts [format $mes $APNAME $SWITCH_CHAR $SWITCH_CHAR $SWITCH_CHAR \
                     $SWITCH_CHAR $SWITCH_CHAR $SWITCH_CHAR]
   puts "       maxrec maxkey_char char_range outfile"
   puts "Where:"
   puts "'nnn' is a decimal number"
   puts "'maxrec' is the number of records to generate"
   puts "'maxkey_char' is the number of fields in each record"
   puts "'char_range' is the largest number to be generated in a field"
   puts "'outfile' is the output file name"
   set mes "'%sFnnn' is the field mark separator char (default is "
   append mes "space %sF32)"
   puts [format $mes $SWITCH_CHAR $SWITCH_CHAR]
   set mes "'%sL' is generate record length as first field"
   puts [format $mes $SWITCH_CHAR]
   set mes "'%sN' is do not generate sequence numbers"
   puts [format $mes $SWITCH_CHAR]
   set mes "'%sRnnn' is the record separator char (default is "
   append mes "CRLF)"
   puts [format $mes $SWITCH_CHAR]
   set mes "'%sSnnn' is the starting sequence numer (default is "
   append mes "1 %sS1)"
   puts [format $mes $SWITCH_CHAR $SWITCH_CHAR]
   set mes "'%sV' is generate variable length fields (instead of "
   append mes "all being 5 char)"
   puts [format $mes $SWITCH_CHAR]
   puts "switch character signatures are shown in uppercase for clairity"
}

# Program main line.
# display logo
puts "$APNAME for $tcl_platform(platform) Version $VERSION"
puts "by Rick Smereka, Copyright (c) 2005"
puts "$APNAME comes with ABSOLUTELY NO WARRANTY"
puts "This is free software, and you are welcome to redistribute it"
puts "under certain conditions; see 'gpl.txt' for information."

# check number of parameters passed
if {$argc == 0} {
   usage
   exit
}

# parse switches
if {![parse_comm_line $argc $argv]} {
   puts "program abort"
   parray opt
   exit
}

# seed random generator
expr srand([clock clicks])

set ret [catch {
   set out [open $opt(outfile) "w"]
}]

if {$ret} {
   puts "error opening '$opt(outfile)' for writing"
   puts "program abort"
   exit
}

# set output file channel to binary translation if a record delimiter
# has been given
if {$opt(rec_sep)} {
   fconfigure $out -translation binary
}

# unbuffered stdout
fconfigure stdout -buffering none

# record creation loop
for {set i 0} {$i < $opt(maxrec)} {incr i} {
   set record ""

   # set sequence number if enabled
   if {$opt(do_seq)} {
      set record [format "%05d%c" $opt(seqn) $opt(field_sep)]
      incr opt(seqn)
   }

   # field generation loop
   for {set j 0} {$j < $opt(maxkey_char)} {incr j} {
      set val [rnd 0 $opt(char_range)]

      if {$opt(is_var)} {
         set key $val
      } else {
         set key [format "%05d" $val]
      }

      append record $key

      # add field delimiter as long as we are not on the last field
      if {$j < [expr $opt(maxkey_char) - 1]} {
         append record [format "%c" $opt(field_sep)]
      }
   }
      
   # delimit record if delimiter given
   if {$opt(rec_sep)} {
      append record [format "%c" $opt(rec_sep)]
   }

   set len [string length $record]

   # generate length field if requested
   if {$opt(do_len)} {
      set key [format "A%010d%c" $len $opt(field_sep)]
      set record $key$record 
   }

   if {$opt(rec_sep)} {
      set ret [catch {
         puts -nonewline $out $record
      }]
   } else {
      set ret [catch {
         puts $out $record
      }]
   }

   if {[expr (($i + 1) % 500)] == 0} {
      puts -nonewline "processed record [expr $i + 1]\r"
   }
}

close $out
puts "done, created $opt(maxrec) records"
exit
