/* dbgen - Program to generate random table data for the Bbuuzzb
   database engine/server.
   Rick Smereka, Copyright (C) 2002-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

   Original Linux version. Program syntax:

      dbgen[m] -v -u -rnn -fnn rec_num table_name

   Where:

   v - generate variable length numbers (default=no, fixed at 3 digits)
   u - generate random number of sub-fields and sub-sub-fields
   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 
   'dbeng_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 sub-fields or 
   sub-sub-fields 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. Jan/2002, Rick Smereka

   Ported to QNX 4.x and 32bit Windows. Feb/2002, Rick Smereka

   Excluded include of 'flsocket.h' in the case of DOS.
   Apr/2002, Rick Smereka

   Changed call to 'sys_log_init' to include the application
   name. Jun/2002, Rick Smereka

   Fixed bug in 'do_switch' which caused an error if the maximum
   number generated was greater than 99. Nov/2002, Rick Smereka

   Ported to Debian Linux. Jan/2003, Rick Smereka

   Modified for the updated 'socloc' API ('sloc.c').
   May/2003, Rick Smereka

   Modified for the updated 'sys_log' API ('sys_log.c').
   Modified for the updated 'bbuuzzb' API.
   Jun/2003, Rick Smereka

   Re-compiled for the new 'bbuuzzb' API which implements the
   catalog table in single user applications as well as
   client/server. When this application starts and the catalog
   is enabled in single user mode (see 'dbengcfg.c') both the
   logical table name and the physical file name are required.
   Feb/2004, Rick Smereka

   Added include of 'appinit.h' and call to 'appinit_register_name'
   from function 'main'. Changed debug code to use the log manager
   ('logman'). Recoded function 'term_app'. Apr/2004,
   Rick Smereka

   Re-compile after changing the 'socloc' API.
   Jan/2005, Rick Smereka

   Re-compile after modifications to low level TCP socket communication
   module (ipcomm.c). Feb/2006, Rick Smereka */

#include "stdhead.h"
#ifndef OS_DOS
#include "flsocket.h"
#include "appinit.h"
#endif
#include "dbmess.h"
#include "dbiocode.h"
#include "dbcomm.h"

#ifdef IPC_TCP
#include "socloc.h"
#include "sloc.h"
#include "slconfig.h"
#include "sliocode.h"
#ifdef OS_WIN32
WSADATA wsaData;
#endif
#endif

/* high level database API changes depending
   on whether we are compiling for stand-alone
   or multiuser */

#ifdef MULTIUSER
#include "dbcs.h"
#include "dbcscfg.h"
#else
#include "dbeng.h"
#include "dblocal.h"
#include "dbengcfg.h"
#include "dblocfg.h"
#endif

#define VERSION "1.08.01-2006.02.23"

#ifdef MULTIUSER
#define APNAME "dbgenm"
#else
#define APNAME "dbgen"
#endif

#define MAXFIELD 25
#define MAXSUBFIELD 10
#define MAXSUBSUBFIELD 10
/* #define DEBUG 1 */

int main(int, char **);
int parse_comm_line(int, char **);
int do_switch(char *);
int c_initialize(void);
void gen_rec(void);
int do_sf(long, int);
void get_value(char *);
void term_app(void);
void usage(void);

#ifdef IPC_TCP
int c_ipc_socloc_init(void);
#endif

/* option or switch structure */

struct
   {
   int is_var;          // are fields variable length
   int is_sub_field;    // generate random number of sub-fields/sub-sub-fields
   int char_range;      // upper range of char number to generate
   int num_fields;      // number of fields to generate
   long maxrec;         // number of records to generate
   char outfile[255];   // name of output table
   } opt;

/* global data */

int tid;

int main(int argc, char **argv)
{
   char mess[75];
   long now;
   int ret;

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

   printf("by Rick Smereka, Copyright (c) 2002-2006\n");
   printf("%s comes with ABSOLUTELY NO WARRANTY\n", APNAME);
   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.is_var = FALSE;
   opt.is_sub_field = FALSE;
   opt.char_range = 999;
   opt.num_fields = -1;
   opt.maxrec = 0L;

   /* parse switches */

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

#ifndef OS_DOS
   // register application name with 'appinit'

   if (!appinit_register_name(APNAME))
      {
      printf("%s:fatal error registering app name with appinit\n", APNAME);
      return(0);
      }
#endif

   /* 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())
      return(0);

#ifdef DEBUG
   /* debugging via system logger except for DOS which will use
      the personal logger to create a log file in the current directory */

#ifdef OS_DOS
   sprintf(mess, "%s.log", APNAME);

   if (logman_start(mess, APNAME))
      {
      printf("%s:unable to open log file '%s',program abort\n", APNAME, mess);
      term_app();
      return(0);
      }
#else
   if (logman_start(SYS_LOG_SERVICE_NAME, APNAME))
      {
      printf("%s:unable to start system logging,program abort\n", APNAME);
      term_app();
      return(0);
      }
#endif
#endif

   srand(time(&now) % 37);    // seed random generator
   tid = 1;                   // indicate open after create

   /* attempt to create a new table and open it */

   if ((ret = db_new_table(opt.outfile, &tid)) != DBENG_OK)
      {
      db_io_code_string(ret, mess);
      printf("%s:bad rc[%s] from db_new_table,program abort\n", APNAME, mess);
      term_app();
      return(0);
      }

   gen_rec();

   if ((ret = db_close(tid)) != DBENG_OK)
      {
      db_io_code_string(ret, mess);
      printf("%s:bad rc[%s] from db_close\n", APNAME, mess);
      }

   printf("\n%s:done,created %ld records\n", APNAME, opt.maxrec);
   term_app();
   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 = 2;

   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("%s:out of range[maxrec][%ld]\n", APNAME, opt.maxrec);
               return(FALSE);
               }

            man_parms++;
            break;

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

            if (!strlen(opt.outfile))
               {
               printf("%s:output table name is empty\n", APNAME);
               return(FALSE);
               }

            man_parms++;
            break;
         };

      curparm++;
      }

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

   return(TRUE);
}

int do_switch(char *parm)
{
   /* Parse and set one option based on a switch. Function returns
      'TRUE' upon success, 'FALSE' otherwise. */

   switch(toupper(parm[1]))
      {
      case 'V':
         opt.is_var = TRUE;
         break;

      case 'S':
         opt.is_sub_field = TRUE;
         break;

      case 'R':
         if (strlen(parm) > 5)
            {
            printf("%s:out of range[max number used],s/b 10-999\n", APNAME);
            return(FALSE);
            }

         if ((opt.char_range = atoi(&parm[2])) < 10)
            {
            printf("%s:out of range[max number used],s/b 10-999\n", APNAME);
            return(FALSE);
            }

         if (opt.char_range > 999)
            {
            printf("%s:out of range[max number used],s/b 10-999\n", APNAME);
            return(FALSE);
            }

         break;

      case 'F':
         if ((opt.num_fields = atoi(&parm[2])) < 1)
            {
            printf("%s:out of range[number of fields]\n", APNAME);
            return(FALSE);
            }

         break;

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

   return(TRUE);
}

#ifdef MULTIUSER
int c_initialize(void)
{
   /* Initialize socket communication, form connection to
      'socloc' server (TCP only), update the config list and connect
      to a 'Bbuuzzb' server. Function returns 'TRUE' upon
      success, 'FALSE' otherwise. */

   char mname[] = "c_initialize";
   char mes[128];
   int ret;

#ifdef IPC_TCP
   if (!c_ipc_socloc_init())
      return(FALSE);
#endif

   /* attempt to locate a 'Bbuuzzb' server and connect to it */

   if ((ret = db_initialize()) != DBENG_OK)
      {
      db_io_code_string(ret, mes);
      printf("%s:error connecting to the Bbuuzzb server,rc[%s]. "
             "Program abort\n", mname, mes);
      return(FALSE);
      }

   /* display current connected server host name and
      TCP port number */

#ifdef IPC_TCP
   (void)db_get_active(mes, &ret);
   printf("%s:connected to Bbuuzzb server %s on TCP port %d (decimal)\n",
          mname, mes, ret);
#endif

   return(TRUE);
}
#endif

#ifndef MULTIUSER
int c_initialize(void)
{
   /* Start the 'DBENG' local engine, read the config file
      and display the result settings. Function returns
      'TRUE' upon success, 'FALSE' otherwise. */

   char mname[] = "c_initialize";
   char *mes;
   int ret, flag;

#ifdef DEBUG
#ifdef IPC_TCP
   if (!c_ipc_socloc_init())
      return(FALSE);
#endif
#endif

   if ((mes = (char *)malloc(DBENG_PATH_LIMIT)) == (char *)NULL)
      {
      printf("%s:alloc fail[mes]. Program abort\n", mname);
      return(FALSE);
      }

   ret = db_initialize();

   /* interpret code from init which may be
      a fatal config error */

   switch(ret)
      {
      case DBENG_OK:
         printf("%s:read config file successfully\n", mname);
         break;

      case DBENG_CONFIG_UNABLE_TO_OPEN:
         printf("%s:config file not found, loaded defaults\n", mname);
         break;

      default:
         db_io_code_string(ret, mes);
         printf("%s:fatal config error[%s]\n", mname, mes);
         printf("%s:terminating\n", mname);
         free(mes);
         return(FALSE);
         break;
      };

   (void)db_config_get_tmp_path(mes);
   printf("%s:tmp path is '%s'\n", mname, mes);
   (void)db_config_get_log(mes);
   printf("%s:log is '%s'\n", mname, mes);

   if ((ret = db_config_get_catalog(mes)) == DBENG_OK)
      printf("%s:catalog is '%s'\n", mname, mes);
   else
      printf("%s:catalog name not loaded\n", mname);

   (void)db_config_get_catalog_flag(&flag);

   if (flag_2_logic_string(flag, mes))
      printf("%s:catalog is %s\n", mname, mes);

   free(mes);
   return(TRUE);
}
#endif

#ifdef IPC_TCP
int c_ipc_socloc_init(void)
{
   /* Initialize socket communication, form connection to
      'socloc' server (TCP only) and update the config list.
      Function returns 'TRUE' upon success, 'FALSE' otherwise. */

   char mname[] = "c_ipc_socloc_init";
   char *thelist, mes[128];
   int ret;

   /* startup WinSock (if Windoze) */

#ifdef OS_WIN32
   if (WSAStartup(WINSOCK_VERSION, &wsaData))
      {
      printf("%s:unable to start WinSock. "
            "Program abort\n", mname);
      return(FALSE);
      }
#endif

   /* attempt to connect to a 'socloc' server */

   if ((ret = sloc_initialize()) != SL_OK)
      {
      sl_code_string(ret, mes);
      printf("%s:error initializing socloc interface,rc[%s]. "
            "Program abort\n", mname, mes);
      return(FALSE);
      }

   /* get config list from 'socloc' server and update
      the client list with it */

   if ((thelist = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      printf("%s:alloc fail[thelist]. Program abort\n", mname);
      return(FALSE);
      }

   if ((ret = sloc_config_get_list(thelist)) != SL_OK)
      {
      free(thelist);
      sl_code_string(ret, mes);
      printf("%s:error getting socloc config list,rc[%s]. "
             "Program abort\n", mname, mes);
      return(FALSE);
      }

   if ((ret = sl_config_put_list(thelist)) != SL_OK)
      {
      sl_code_string(ret, mes);
      printf("%s:error putting socloc config list,rc[%s]. ",
             "Program abort\n", mname, mes);
      free(thelist);
      return(FALSE);
      }

   free(thelist);
   return(TRUE);
}
#endif

void gen_rec(void)
{
   /* Generate the records. Table is assumed to be open
      with the 'tid' loaded. */

   char thenum[4], mess[75], mname[] = "gen_rec";
   long i;
   int ret, j, nfields;

   /* record generation loop */

   for(i = 1L; i <= opt.maxrec; i++)
      {
      /* initialize new record */

      if ((ret = db_new(tid)) != DBENG_OK)
         {
         db_io_code_string(ret, mess);
         printf("%s:%s:bad rc[%s] from db_new[%ld]\n", APNAME, mname,
                mess, i);
         return;
         }

      /* number of fields in record */

      nfields = (opt.num_fields == -1) ? ((rand() % MAXFIELD) + 1) :
                opt.num_fields;

      /* field generation loop */

      for(j = 1; j <= nfields; j++)
         {
         /* get field value */

         get_value(thenum);

         /* put the field into record */

         if ((ret = db_put_field(tid, j, thenum)) != DBENG_OK)
            {
            db_io_code_string(ret, mess);
            printf("%s:%s:bad rc[%s] from db_put_field[%ld,%d]\n", APNAME, 
                   mname, mess, i, j);
            return;
            }

         /* put sub-fields and sub-sub-fields if requested */

         if (opt.is_sub_field)
            if (!do_sf(i, j))
               return;
         }

      /* write the new record */

      if ((ret = db_write(tid)) != DBENG_OK)
         {
         db_io_code_string(ret, mess);
         printf("%s:%s:bad rc[%s] from db_write[%ld]\n", APNAME, 
                mname, mess, i);
         return;
         }
         
      if (((i + 1L) % 5) == 0L)
         printf("%s:%s:processed record %ld\r", APNAME, mname, i);
      }
}

int do_sf(long rec_num, int field_num)
{
   /* Decide whether to create sub-fields and sub-sub-fields
      within the field data. Function returns 'TRUE' upon
      success, 'FALSE' otherwise. */

   char thenum[4], mess[75], mname[] = "do_sf";
   int ret, has_sf, has_ssf, num_sf, num_ssf, i, j;

   has_sf = rand() % 2;

   if (!has_sf)
      return(TRUE);

   num_sf = (rand() % MAXSUBFIELD) + 1;

   for(i = 1; i <= num_sf; i++)
      {
      get_value(thenum);

      if ((ret = db_put_subfield(tid, field_num, i, thenum)) != DBENG_OK)
         {
         db_io_code_string(ret, mess);
         printf("%s:%s:bad rc[%s] from db_put_subfield[%ld,%d,%d]\n",
                APNAME, mname, mess, rec_num, field_num, i);
         return(FALSE);
         }

      has_ssf = rand() % 2;

      if (has_ssf)
         {
         num_ssf = (rand() % MAXSUBSUBFIELD) + 1;

         for(j = 1; j <= num_ssf; j++)
            {
            get_value(thenum);

            if ((ret = db_put_subsubfield(tid, field_num, i, j, thenum))
                != DBENG_OK)
               {
               db_io_code_string(ret, mess);
               printf("%s:%s:bad rc[%s] from db_put_subsubfield[%ld,%d,%d"
                      ",%d]\n", APNAME, mname, mess, rec_num, field_num,
                      i, j);
               return(FALSE);
               }
            }
         }
      }

   return(TRUE);
}

void get_value(char *vout)
{
   /* Create a random number in proper range and return it in
      'vout' which must already be allocated to sufficient size. */

   int ival;

   ival = (rand() % opt.char_range) + 1;
   
   if (opt.is_var)
      sprintf(vout, "%d", ival);
   else
      sprintf(vout, "%03d", ival);
}

void term_app(void)
{
   // Prepare to terminate the application. Shutdown all IPC API's.

   db_end();

   if (logman_is_active())
      logman_end();

#ifdef IPC_TCP
   if (sloc_is_init())
      sloc_term_api();

#ifdef OS_WIN32
   WSACleanup();
#endif
#endif

#ifndef OS_DOS
   (void)appinit_remove_name();
#endif
}

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

   printf("%s: A program to generate random data for Bbuuzzb.\n", APNAME);
   printf("usage: %s %cv %cs %crnn %cfnn maxrec table_name\n\n", APNAME,
          SWITCH_CHAR, SWITCH_CHAR, SWITCH_CHAR, SWITCH_CHAR);
   printf("Where:\n");
   printf("'maxrec' is the number of records to generate\n");
   printf("'table_name' is the output table name\n");
   printf("'%cv' is generate variable length fields (instead of all being "
          "3 char)\n", SWITCH_CHAR);
   printf("'%cs' is generate random number of sub-fields/sub-sub-fields"
          " (default=off)\n", SWITCH_CHAR);
   printf("'%crnn' is the maximum number to generate (default=999,"
          "range 10-999)\n", SWITCH_CHAR); 
   printf("'%cfnn' is the number of fields to generate (default=" 
          "random,max=%d)\n", SWITCH_CHAR, MAXFIELD);
}
