# dbgen - Program to generate random table data for the Bbuuzzb
# database engine/server.
# Rick Smereka, Copyright (C) 2006.

# 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:

#    dbgen[m] -v -s -rnn -fnn rec_num table_name

# Where:

# v - generate variable length numbers (default=no, fixed at 3 digits)
# s - generate random number of subfields and subsubfields
# r - maximum number generated (default=999, cannot exceed three digits)
# f - number of fields to generate in each record (default=random,max=25)

# rec_num - number of records to generate
# table_name - name of table

# There are two versions of this program. The single user version
# called 'dbgen' and the client/server version called 'dbgenm'.
# The 'table_name' must be given in accordance with the 
# 'db_new_table' guidelines. This program will attempt to create
# a new table. It is an error if the table already exists.
# Some execution examples:

#    dbgen 100000 /bbuuzzb/tables/test.buz

# The above example will execute the single user version of this
# program and generate a new table with 100,000 records. A random
# number of fields per record will be generated. The number in
# each field will be fixed at three digits with zero padding on
# the left and will be a maximum of 999. No subfields or 
# subsubfields will be generated.
  
#    dbgenm 250000 "test.table,/bbuuzzb/tables/test.buz"

# The above example will execute the multiuser version of this
# program and generate a new table with 250,000 records. Note
# that a logical and physical file name are supplied. Also
# notice that quotes are placed around the 'table_name'
# parameter. All default options as described in the previous
# example will apply.

# Initial Tcl version, Mar/2006, Rick Smereka

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

source $TCLDIR/lib/stdhead.tcl
source $TCLDIR/lib/comcode.tcl
source $TCLDIR/lib/appinit.tcl
const MULTIUSER 0
source $TCLDIR/dbeng/dbmess.tcl
source $TCLDIR/dbeng/dbiocode.tcl
source $TCLDIR/dbeng/dbeng.tcl
source $TCLDIR/dbeng/dbengcfg.tcl
source $TCLDIR/dbeng/dbengcat.tcl
source $TCLDIR/dbeng/dbfxp.tcl
source $TCLDIR/dbeng/dbengsrt.tcl

const APNAME "dbgen"
const VERSION "1.08.01-2006.03.22"
const MAXFIELD 25
const MAXSUBFIELD 10
const MAXSUBSUBFIELD 10

# global option or switch array
set opt(is_var) 0
set opt(is_sub_field) 0
set opt(char_range) 999
set opt(num_fields) -1
set opt(maxrec) 0
set opt(outfile) ""

# database tid
set dbtid 0

proc process {} {
   # Open a new table, generate the records and close the table.
   # attempt to create a new table and open it
   global APNAME DBENG_OK opt dbtid
   set mname "process"
   set tid 1

   set ret [db_new_table $opt(outfile) tid]

   if {$ret != $DBENG_OK} {
      puts "$mname:bad rc([db_io_code_string $ret]) from db_new_table"
      puts "program abort"
      return
   }

   set dbtid $tid
   puts "$mname:tid is $tid"
   gen_rec
   set ret [db_close $tid]

   if {$ret != $DBENG_OK} {
      puts "$mname:bad rc([db_io_code_string $ret]) from db_close"
   }

   puts "\n$mname:done,created $opt(maxrec) record(s)"
}

proc parse_comm_line {gc gv} {
   # Parse entire command line. Returns 1 upon success, 0 otherwise.
   global APNAME SWITCH_CHAR opt
   const req_man_parms 2
   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 {
            set opt(outfile) $prm
            incr man_parms
         }
      }

      incr curparm
   }

   if {$man_parms < [expr $req_man_parms + 1]} {
      puts "$APNAME: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 {
      V {
         set opt(is_var) 1
      }

      S {
         set opt(is_sub_field) 1
      }

      R {
         if {[string length $parm] < 3} {
            puts "missing range 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 < 10 || $val > 999} {
            puts "out of range(max number used),s/b 10-999"
            return 0
         }

         set opt(char_range) $val
      }

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

         set val [string range $parm 2 end]

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

         if {$val < 1 || $val > 25} {
            puts "out of range(number of fields)"
            return 0
         }

         set opt(num_fields) $val
      }

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

   return 1
}

proc c_initialize {} {
   # Start the 'dbeng' local engine, read the config file and
   # display the result settings. Returns 1 upon success, 0
   # otherwise.
   global DBENG_OK DBENG_CONFIG_UNABLE_TO_OPEN
   set mname "c_initialize"

   set ret [db_initialize]

   # interpret code from init which may be a
   # fatal config error
   switch $ret \
      $DBENG_OK {puts "$mname:read config file successfully"} \
      $DBENG_CONFIG_UNABLE_TO_OPEN \
                {puts "$mname:config file not found,loaded defaults"} \
      default {puts "$mname:fatal config error([db_io_code_string $ret])"; \
               puts "$mname:terminating";return 0}

   db_config_get_tmp_path mes
   puts "$mname:tmp path is '$mes'"
   db_config_get_log mes
   puts "$mname:log is '$mes'"
   set ret [db_config_get_catalog mes]

   if {$ret == $DBENG_OK} {
      puts "$mname:catalog is '$mes'"
   } else {
      puts "$mname:catalog name not loaded"
   }

   db_config_get_catalog_flag flag

   if {[flag_2_logic_string $flag mes]} {
      puts "$mname:catalog is $mes"
   }

   return 1
}

proc gen_rec {} {
   # Generate the records. Table is assumed to be open
   # with the 'tid' loaded into 'dbtid'.
   global dbtid opt DBENG_OK MAXFIELD
   set mname "gen_rec"

   # record generation loop
   for {set i 1} {$i <= $opt(maxrec)} {incr i} {
      # initialize new record
      set ret [db_new $dbtid]

      if {$ret != $DBENG_OK} {
         puts "$mname:bad rc([db_io_code_string $ret]) from db_new($i)"
         return
      }

      # set the number of fields in this record
      if {$opt(num_fields) == -1} {
         set nfields [rnd 1 $MAXFIELD]
      } else {
         set nfields $opt(num_fields)
      }

      # field generation loop
      for {set j 1} {$j <= $nfields} {incr j} {
         set thenum [get_value]
         set ret [db_put_field $dbtid $j $thenum]

         if {$ret != $DBENG_OK} {
            puts "$mname:bad rc([db_io_code_string $ret]) from \
                  db_put_field($i,$j)"
            return
         }

         # put subfields and subsubfields if requested
         if {$opt(is_sub_field)} {
            if {![do_sf $i $j]} {
               return
            }
         }
      }

      # write the new record
      set ret [db_write $dbtid]

      if {$ret != $DBENG_OK} {
         puts "$mname:bad rc([db_io_code_string $ret]) from db_write($i,$j)"
         return
      }

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

proc do_sf {rec_num field_num} {
   # Decide whether to create subfields and subsubfields
   # within the field data. Returns 1 upon success,
   # 0 otherwise.
   global dbtid DBENG_OK MAXSUBFIELD MAXSUBSUBFIELD
   set mname "do_sf"

   set has_sf [rnd 0 1]

   if {!$has_sf} {
      return 1
   }

   set num_sf [rnd 1 $MAXSUBFIELD]

   for {set i 1} {$i <= $num_sf} {incr i} {
      set thenum [get_value]
      set ret [db_put_subfield $dbtid $field_num $i $thenum]

      if {$ret != $DBENG_OK} {
         puts "$mname:bad rc([db_io_code_string $ret]) from \
               db_put_subfield($rec_num,$field_num,$i)"
         return 0
      }

      set has_ssf [rnd 0 1]

      if {$has_ssf} {
         set num_ssf [rnd 1 $MAXSUBSUBFIELD]

         for {set j 1} {$j <= $num_ssf} {incr j} {
            set thenum [get_value]
            set ret [db_put_subsubfield $dbtid $field_num $i $j $thenum]

            if {$ret != $DBENG_OK} {
               puts "$mname:bad rc([db_io_code_string $ret]) from \
                     db_put_subsubfield($rec_num,$field_num,$i,$j)"
               return 0
            }
         }
      }
   }

   return 1
}

proc get_value {} {
   # Create a random number in the proper range and return it.
   global opt

   set ival [rnd 1 $opt(char_range)]

   if {$opt(is_var)} {
      set ret $ival
   } else {
      # if the number should be fixed three digit, reformat it
      set ret [lpad $ival 0 3]
   }

   return $ret
}

proc term_app {} {
   # Prepare to terminate the application. Shutdown all IPC API's.

   db_end

   if {[logman_is_active]} {
      logman_end
   }

   appinit_remove_name
}

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

   puts "$APNAME: A program to generate random data for Bbuuzzb."
   puts [format "usage: %s %sv %ss %srnn %sfnn maxrec table_name\n" $APNAME \
         $SWITCH_CHAR $SWITCH_CHAR $SWITCH_CHAR $SWITCH_CHAR]
   puts "Where:"
   puts "'maxrec' is the number of records to generate"
   puts "'table_name' is the output table name"
   set mes "'%sv' is generate variable length fields " 
   append mes "(instead of all being 3 char)"
   puts [format $mes $SWITCH_CHAR]
   set mes "'%ss' is generate random number of subfields/subsubfields"
   append mes " (default=off)"
   puts [format $mes $SWITCH_CHAR]
   set mes "'%srnn' is the maximum number to generate (default=999,"
   append mes "range 10-999)"
   puts [format $mes $SWITCH_CHAR]
   set mes "'%sfnn' is the number of fields to generate (default=" 
   append mes "random,max=%d)"
   puts [format $mes $SWITCH_CHAR $MAXFIELD]
}

# Program main line.
# unbuffered stdout
fconfigure stdout -buffering none

# display logo
puts "$APNAME for $tcl_platform(platform) Version $VERSION"
puts "by Rick Smereka, Copyright (c) 2006"
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
}

# initialize
set opt(is_var) 0
set opt(is_sub_field) 0
set opt(char_range) 999
set opt(num_fields) -1
set opt(maxrec) 0

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

# register application name with 'appinit'
if {![appinit_register_name $APNAME]} {
   puts "$APNAME:fatal error registering app name with appinit"
   exit
}

# initialize local engine if we are running locally or
# startup the 'socloc' interface (TCP only) and connect to a
# Bbuuzzb database server if we are running in
# client/server mode
if {![c_initialize]} {
   exit
}

# seed random generator
expr srand([clock clicks])
parray opt
process
term_app
exit
