/*
   <p>Test harness for <i>bsearch(), chopsearch()</i>
   and <i>chopinsert()</i>.</p>

   <kbd>
   <b>environ - C programming support functions</b><br>
   Copyright (C) 2005  Martin C Gregorie
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the Lesser GNU General Public License.
   
   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 Lesser 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.
   </kbd>
  
   <p>Contact Martin Gregorie at <i>martin@gregorie.org</i></p>
*/

#include <stdio.h>
#include <string.h>
#include "environ.h"

#ifdef LINUX
#include <stdlib.h>
#endif

#define TABMAX      20000    /* max table size */
#define PROGNAME    argv[0]  /* program name */
#define OPTARG      argv[1]  /* command argument subscripts */
#define TABFILE     argv[2]  /* file of values for initial load */
#define DATAFILE    argv[3]  /* file of search values */
#define MANDARGS    3        /* mandatory argument count */

int searchdepth;

typedef struct
    {
        char *key;
        int  val;
    } item;

int itemcmp(const void *target, const void *elem)
{
    char *t;
    item *e;

    searchdepth++;
    t = (char*)target;
    e = (item*)elem;
    return (strcmp(t, e->key));
}

void dumptable(item **tab, int lim)
{
    int  i;
    item *t;

    printf("Table contents are now:\n");
    for(i=0; i<lim; i++)
    {
        t = tab[i];
        printf("%d: %s = %d\n", i, t->key, t->val);
    }
}

item *newitem(char *key, int val)
{
    item *p;

    p = (item*)malloc(sizeof(item));
    if (!p)
        return(NULL);

    p->key = (char*)malloc(strlen(key)+1);
    if (!p->key)
        return(NULL);

    strcpy(p->key, key);
    p->val = val;
    return (p);
}

int main(int argc, char **argv)
{
    int     type;
    int     debug;
    int     i;
    int     n;

    FILE    *f;
    item    **tab;
    int     tablim;
    int     loaded;

    item    *found;
    item    *new;
    int     insertpt;
    int     reply;
    int     filesource;
    int     endfound;
    int     testcount;
    int     hits;
    int     misses;
    int     inserts;
    int     totdepth;
    int     maxdepth;
    int     mindepth;

    char    buf[MAXLINE];

    /*
        Show help if required
    */    
    if (argc < MANDARGS || strcmp(OPTARG, "-?") == 0)
    {
        printf("\nsearchlist -c <tabfile> [<datafile>]\n");
        printf("-c = test chopsearch() and chopinsert()\n");
        printf("     Use upper case to turn table dumping on.\n");
        printf("\n<tabfile> is loaded into the table.\n");
        printf("If <datafile> is present it is read; otherwise\n");
        printf("search keys are read from stdin until ^d is input.\n\n");
        exit(1);
    }

    /*
        parameter validation
    */
    for (i=1, n=0; i<argc; i++)
        if (argv[i][0] == '-')
            n++;

    if (n != 1)
        error(PROGNAME,"exactly one option must be present","");

    type = OPTARG[1];
    switch(type)
    {
        case 'c': debug = FALSE;
                  break;

        case 'C': debug = TRUE;
                  break;

        default:
                  error(PROGNAME,
                        "option must be -c or -C not",
                        OPTARG);
    }

    /*
        Show run conditions
    */
    printf("Type is %c\n", type);

    if (debug)
        printf("Table dump is on\n");

    /*
        Allocate and load the table
    */
    f = fopen(TABFILE, "r");
    if (!f)
        error(PROGNAME,"can't open",TABFILE);

    tab = (item**)malloc(sizeof(item*)*TABMAX);
    if (!tab)
        error(PROGNAME, "can't allocate array storage","");

    if(debug)
        printf("Loading the table\n");
    tablim = 0;
    while (fgets(buf, MAXLINE, f))
    {
        if (tablim>=TABMAX)
            error(PROGNAME, "Table limit exceeded at", buf);

        buf[strlen(buf)-1] = 0; /* get rid of the newline */
        if (debug)
            printf("tab[%d] = %s\n", tablim, buf);
            
        tab[tablim] = newitem(buf, tablim);
        if (!tab[tablim])
            error(PROGNAME,"Failed to allocate storage for", buf);
        tablim++;
    }
    fclose(f);
    loaded = tablim;

    /*
        The test proper
    */    
    if (argc <= MANDARGS)
    {
        filesource = FALSE;
        f = fdopen(0, "r");
        if (!f)
            error(PROGNAME, "can't select STDIN for input", "");
            
        printf("\nHit ^d to end the test\n\n");
    }
    else
    {
        filesource = TRUE;
        f = fopen(DATAFILE, "r");
        if (!f)
            error(PROGNAME, "can't open", DATAFILE);
    }

    testcount = 0;
    totdepth = 0;
    maxdepth = 0;
    mindepth = TABMAX+1;
    hits = 0;
    misses = 0;
    inserts = 0;
    endfound = FALSE;
    while(!endfound)
    {
        if (!filesource)
        {
            printf("\nKey: ");
            fflush(stdout);
        }    

        if (fgets(buf, MAXLINE, f))
        {
            buf[strlen(buf)-1] = 0; /* get rid of the newline */
            if (filesource && debug)
                printf("\nKey: %s\n", buf);

            testcount++;
            searchdepth = 0;
            found = (item*)chopsearch((void*)buf,
                                      (void**)tab,
                                      tablim,
                                      &insertpt,
                                      itemcmp);
            if (found)
            {
                hits++;
                new = NULL;
            }
            else
            {
                inserts++;
                misses++;
                new = newitem(buf, tablim);
                if (!new)
                    error(PROGNAME,
                          "Failed to allocate storage for",
                          buf);

                reply = chopinsert((void**)tab,
                                   &tablim,
                                   TABMAX,
                                   new,
                                   insertpt);
                if (reply)
                    error(PROGNAME,
                          "failed to insert",
                          buf);
            }

            totdepth += searchdepth;
            if (searchdepth > maxdepth)
                maxdepth = searchdepth;
            if (searchdepth < mindepth)
                mindepth = searchdepth;
            
            if (debug || (!filesource))
            {
                if (found)
                    printf("Found %s (%d). Search depth=%d\n",
                           found->key,
                           found->val,
                           searchdepth);
                else
                if (new)
                    printf("Inserted %s (%d). Search depth=%d\n",
                           new->key,
                           new->val,
                           searchdepth);
                else
                    printf("Not found. Search depth=%d\n", searchdepth);
                
            }

            if (debug)
                dumptable(tab,tablim);
        }
        else
            endfound = TRUE;
    }

    if (filesource)
        fclose(f);

    printf("Test ended. \n");
    printf("Number of tests=%d\n", testcount);
    printf("  Hits  Misses Inserts\n");
    printf("%6d  %6d  %6d\n",
           hits,
           misses,
           inserts);

    if (testcount != 0)
        printf("%6.2f%% %6.2f%% %6.2f%%\n",
               hits*100.0/testcount,
               misses*100.0/testcount,
               inserts*100.0/testcount);

    if (loaded == tablim || loaded == 0)
        printf("Table size is %d items\n", tablim);
    else
        printf("Table grew from %d to %d (%5.2f%%)\n",
               loaded,
               tablim,
               (tablim-loaded)*100.0/loaded);

    if (testcount != 0)
        printf("Search depth: min=%d, avg=%3.1f, max=%d\n",
               mindepth,
               1.0*totdepth/testcount,
               maxdepth);
                
    exit(0);
}
