/* Copyright (C) Kimmo Kulovesi 1998                        arkhan@softhome.net
 *                       - Random Name Generator v1.5
 *
 *                                          Fully rewritten version.
  *****************************************************************************/
/*-----------------------------------------------------------------------------
  | Function prototypes                                                       |
  ----------------------------------------------------------------------------*/

void usage();               // Prints program usage and quits
void header(int);           // Prints a header to stdout (HTML...etc)
unsigned char get_names();  // Reads the names from a file
void generate_names();      // Generates an amount of random names
void list_names();          // Lists all names in order
void print_analysis();      // Print general information on the file used

/*-----------------------------------------------------------------------------
  | Define macros and constants                                               |
  ----------------------------------------------------------------------------*/

//#define seven_bits_default  // Uncomment this to default to 7-bit chars

//#define debug               // ...just prints useless info (stderr)
//#define debug_b             // prints even more useless info (stdout)

#define html        1       // The flag value for HTML output
#define no_cols     2       //                    single-column output
#define quiet       4       //                    quiet mode (no headers)
#define seven_bit   8       // Use only 7-bit chars in ASCII

#define max_dup     100     // The number of names to store for duplicate check
#define max_dup_rep 350     // Maximum number of times to retry getting a name
#define max_syl     1000    // The maximum number of syllables to read
#define syl_length  15      // The maximum length of a syllable
#define mid_length  10      // The middle syllable is shorter to save memory
#define num_cols    4       // The number of columns to divide the screen in
                            // (don't change unless you REALLY mean it)
#define table_split 25      // Number of lines to print before splitting table
                            // (split tables load faster on most WWW browsers)

/*-----------------------------------------------------------------------------
  | Include the headers                                                       |
  ----------------------------------------------------------------------------*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>     // On some compilers, this is called "strings.h"
#include <time.h>
#include "a_dice.h"     // Header for the advanced random number generation

/*-----------------------------------------------------------------------------
  | Initialize global variables                                               |
  ----------------------------------------------------------------------------*/

char filename[256];         // The Name of the Rose

unsigned int flags = 0;     // General environment flags
long num_to_print = -999;   // Number of names to print

unsigned int num_start = 0,     // A counter for each of the syllables
             num_mid = 0,
             num_final = 0;

char start[max_syl + 1][syl_length + 1],
     mid[max_syl + 1][mid_length + 1],
     final[max_syl + 1][syl_length + 1];

unsigned char column = 0;   // Counter for creating columned output
long table = 0;             //             splitting HTML tables

char name[45];

/*-----------------------------------------------------------------------------
  | Start the main -function                                                  |
  ----------------------------------------------------------------------------*/

void main(int argc, char *argv[])
{
    int i, j;
    filename[0] = '\0';

#ifdef seven_bits_default
    flags = flags | seven_bit;
#endif

/*-----------------------------------------------------------------------------
  | Display program usage and quit if not enough parameters                   |
  ----------------------------------------------------------------------------*/

    if (argc < 2)
        usage();

/*-----------------------------------------------------------------------------
  | Reads in command line parameters                                          |
  ----------------------------------------------------------------------------*/

    for (i = argc - 1; i; i--)
    {
            // Check if it is an option switch

        if (argv[i][0] == '-' || argv[i][0] == '/')
        {
            if (argv[i][1] == 'h' || argv[i][0] == 'H')
            {
                flags = flags | html;
#ifdef debug
                fprintf(stderr, "\t\t_I__ HTML mode on, flags now %u\n", flags);
#endif
            }
            else if (argv[i][1] == 'c' || argv[i][0] == 'C')
            {
                flags = flags | no_cols;
#ifdef debug
                fprintf(stderr, "\t\t_I__ Single column mode on, flags now %u\n", flags);
#endif
            }
            else if (argv[i][1] == 'q' || argv[i][0] == 'Q')
            {
                flags = flags | quiet;
#ifdef debug
                fprintf(stderr, "\t\t_I__ Quiet mode on, flags now %u\n", flags);
#endif
            }
#ifndef seven_bits_default
            else if (argv[i][1] == '7')
            {
                flags = flags | seven_bit;
#ifdef debug
                fprintf(stderr, "\t\t_I__ 7-bit chars on, flags now %u\n", flags);
#endif
            }
#else
            else if (argv[i][1] == '8')
            {
                flags -= seven_bit;
#ifdef debug
                fprintf(stderr, "\t\t_I__ 8-bit chars on, flags now %u\n", flags);
#endif
            }
#endif
            else if (argv[i][1] == '1' && argv[i][0] == '-')
                num_to_print = -1;
            else
                printf("Warning: Incorrect parameter \"%s\" ignored.\n", argv[i]);
        }

            // Check if it is the number of names requested (starts with 0-9)

        else if (((argv[i][0] >= 48 && argv[i][0] <= 57) || argv[i][0] == '+') && num_to_print == -999)
        {
                // Check for any non-numeric characters (might be a filename)

            j = 0;

            for (j++; j < strlen(argv[i]); j++)
            {
                if (argv[i][j] < 48 || argv[i][j] > 57)
                {
                    j = -1;
                    strcpy(filename, argv[i]);
#ifdef debug
                    fprintf(stderr, "\t\t_I__ Read file name : \"%s\"\n", filename);
#endif
                    break;
                }
            }

            if (j >= 0)
                num_to_print = (long)atol(argv[i]);
        }

            // Just a "plain" parameter, must be our filename

        else
        {
            if (filename[0] != '\0')
                fprintf(stderr, "Warning: Multiple file names, excess ignored: \"%s\".\n", filename);

            strcpy(filename, argv[i]);
#ifdef debug
                    fprintf(stderr, "\t\t_I__ Read file name : \"%s\"\n", filename);
#endif
        }
    }

/*-----------------------------------------------------------------------------
  | Checks for correct parameters                                             |
  ----------------------------------------------------------------------------*/

    if (filename[0] == '\0')    // Quit with usage if no file name found
        usage();

    if (num_to_print == -999)
    {
        num_to_print = num_cols * 3 - 1;
#ifdef debug
        fprintf(stderr, "\t\t_C__ No number of names specified, using default.\n");
#endif
    }
    else if (num_to_print == 0)         // Don't use columns in analysis mode
    {
        if (!(flags & no_cols))
        {
            flags = flags | no_cols;
#ifdef debug
            fprintf(stderr, "\t\t_C__ Switch to No-Columns (analysis mode)\n");
#endif
        }
    }
    else if (!(flags & no_cols) && num_to_print < num_cols && num_to_print > 0)
    {
        flags = flags | no_cols;
#ifdef debug
        fprintf(stderr, "\t\t_C__ Switch to No-Columns (too few names)\n");
#endif
    }
            // HTML and Quiet Mode don't go well together

    if (flags & html && flags & quiet)
    {
        fprintf(stderr, "Warning: Quiet mode disabled due to HTML.\n");
        flags -= quiet;

#ifdef debug
        fprintf(stderr, "\t\t_C__ Quiet mode off, flags now %u\n", flags);
#endif
    }

/*-----------------------------------------------------------------------------
  | Print a header and read the names from a file                             |
  ----------------------------------------------------------------------------*/

    header(0);          // Print HTML header

    if (!get_names())
        exit(2);        // Quit if a file error has occurred

/*-----------------------------------------------------------------------------
  | Make final checks and start generating the names                          |
  ----------------------------------------------------------------------------*/

            // Check that there is at least one of each syllable
            // (or the loops will fail)

    if (num_start < 1)
    {
        num_start = 1;
        start[0][0] = '\0';
    }
    if (num_mid < 1)
    {
        num_mid = 1;
        mid[0][0] = '\0';
    }
    if (num_final < 1)
    {
        num_final = 1;
        final[0][0] = '\0';
    }

    header(1);          // Start HTML tables or print a line

        // Choose output method based on the number of names requested

    if (num_to_print == 0)
        print_analysis();
    else if (num_to_print < 0 || num_to_print >= (unsigned long)(num_start * num_mid * num_final))
        list_names();
    else
        generate_names();

/*-----------------------------------------------------------------------------
  | Finally, exit with errorlevel 0                                           |
  ----------------------------------------------------------------------------*/

    header(2);          // End HTML or print a line

    exit(0);
};

/******************************************************************************
  | Generate_Names: Generates random names                                    |
  *****************************************************************************/

void name_out();            // Prints a name on the screen
void tables_out();          // Prints and splits tables/columns
void get_rand_name();       // Randomizes a name

void generate_names()
{
    long dupindex = -1, i, j = 0;
    int k = 0;
    char dupchk[max_dup][45];

    Init_Dice(time(NULL));      // Initialize the random number generator

    for (i = num_to_print; i; i--)
    {
            // Handle tables and columned output

        tables_out();

/*-----------------------------------------------------------------------------
  | Generate a random name, and repeat if it duplicates a previous name       |
  ----------------------------------------------------------------------------*/

        do
        {
            get_rand_name();

            for (j = dupindex; j >= 0; j--)
            {
                if (!strcmp(dupchk[j], name))
                    break;
            }

            k++;
        }
        while (j >= 0 && k < max_dup_rep);
        k = 0;

/*-----------------------------------------------------------------------------
  | Store the name for duplicate checking, and print it on the screen         |
  ----------------------------------------------------------------------------*/

        if (dupindex < max_dup - 1)
        {
            dupindex++;
            strcpy(dupchk[dupindex], name);
        }

        name_out();
    }
};

/******************************************************************************
  | List_Names: Lists all names                                               |
  *****************************************************************************/

void list_names()
{
    int i, j, k, l;

/*-----------------------------------------------------------------------------
  | Loop through all names in order                                           |
  ----------------------------------------------------------------------------*/

    for (i = 0; i < num_start; i++)
    for (j = 0; j < num_mid;   j++)
    for (k = 0; k < num_final; k++)
    {
            // Handle tables and columned output

        tables_out();

/*-----------------------------------------------------------------------------
  | Get and print the name                                                    |
  ----------------------------------------------------------------------------*/

        strcpy(name, start[i]);
        strcat(name, mid[j]);
        strcat(name, final[k]);

        name_out();
    }
};

/******************************************************************************
  | Get_Rand_Name: Randomizes a name                                          |
  *****************************************************************************/

void get_rand_name()
{
    strcpy(name, start[RollDie_B(num_start) - 1]);       // 1st syllable
    strcat(name, mid[RollDie_B(num_mid) - 1]);           // 2nd -- || --
    strcat(name, final[RollDie_B(num_final) - 1]);       // 3rd -- || --
};

/******************************************************************************
  | Tables_Out: Prints and splits HTML/ascii tables/columns                   |
  *****************************************************************************/

void tables_out()
{
    if (!(flags & no_cols))
    {
        if (column >= num_cols)
        {
            column = 0;                   // Reset column counter
            table++;                  // Add to table lines count

            if (flags & html)
                printf("</TR>\n<TR>");
            else
                printf("\n");
        }
        else if (flags & html && !column && !table)
            printf("<TR>");
    }
    else if (column || table)           // No linefeed before 1st name
    {
        if (flags & html)
            printf("<BR>\n");
        else
            printf("\n");
    }

    column++;                           // Count names on each line

        // Split tables for incremental loading

    if (flags & html && !(flags & no_cols) && table > table_split)
    {
        table = 0;
        printf("</TABLE><BR>\n<TABLE border=1 cellspacing=1 cellpadding=2 valign=top><TR>\n");
    }
};

/******************************************************************************
  | Name_Out: Prints a name on the screen                                     |
  *****************************************************************************/

void name_out()
{
    if (flags & html && !(flags & no_cols))
        printf("<TD>");                         // HTML table cell

    if (!(flags & no_cols))
        printf("%-19s", name);
    else
        printf("%-45s", name);

    if (flags & html && !(flags & no_cols))
        printf("</TD>");                        // End cell
};

/******************************************************************************
  | Print_Analysis: Prints information about the file being used              |
  *****************************************************************************/

void print_analysis()
{
    int i, j, k, l;
    char *name;

    if(flags & html)
    {
        printf("Number of starting syllables:&nbsp; %i<BR>\n", num_start);
        printf("Number of middle syllables  :&nbsp; %i<BR>\n", num_mid);
        printf("Number of ending syllables  :&nbsp; %i<BR>\n", num_final);
        printf("Number of possible names    :&nbsp; %lu", (unsigned long)(num_start * num_mid * num_final));
    }
    else
    {
        printf("Number of starting syllables:  %i\n", num_start);
        printf("Number of middle syllables  :  %i\n", num_mid);
        printf("Number of ending syllables  :  %i\n", num_final);
        printf("Number of possible names    :  %lu", (unsigned long)(num_start * num_mid * num_final));
    }
};

/******************************************************************************
  | Get_Names: Reads in the namefile specified in global "filename"           |
  *****************************************************************************/

    // Define the status flags

#define rd_inf   1
#define rd_first 2
#define rd_mid   3
#define rd_fin   4

    // Initialize variables

unsigned char get_names()
{
    int i = 0, k = 0;
    unsigned char quit = 0, status = 0, j = 1;
    FILE *namefile;
    char tempstr[256];

/*-----------------------------------------------------------------------------
  | First, open the file                                                      |
  ----------------------------------------------------------------------------*/

    namefile = fopen(filename, "r");        // Attempt to open as-is

    if (namefile == NULL)
    {
#ifdef debug_b
        fprintf(stderr, "\t\t_F__ Failed to open \"%s\".\n", filename);
#endif
        strcpy(tempstr, filename);          // Check if extension missing
        strcat(tempstr, ".nam");
        namefile = fopen(tempstr, "r");

        if (namefile == NULL)
        {
#ifdef debug_b
            fprintf(stderr, "\t\t_F__ Failed to open \"%s\".\n", tempstr);
#endif
            strcpy(tempstr, "./names/");    // Check if it is in a directory
            strcat(tempstr, filename);
            namefile = fopen(tempstr, "r");

            if (namefile == NULL)
            {
#ifdef debug_b
                fprintf(stderr, "\t\t_F__ Failed to open \"%s\".\n", tempstr);
#endif
                strcpy(tempstr, "./names/");    // Maybe it is missing both
                strcat(tempstr, filename);
                strcat(tempstr, ".nam");
                namefile = fopen(tempstr, "r");

                if (namefile == NULL)
                {
#ifdef debug_b
                    fprintf(stderr, "\t\t_F__ Failed to open \"%s\".\n", tempstr);
#endif
                    perror(filename);       // Give up and quit, file missing
                    return 0;
                }
            }
        }
    }

/*-----------------------------------------------------------------------------
  | Now read through the file                                                 |
  ----------------------------------------------------------------------------*/

    do
    {
        fgets(tempstr, 256, namefile);
        tempstr[strlen(tempstr) - 1] = '\0';            // Remove linefeed

#ifdef debug_b
        printf("INPUT> %s\n", tempstr);
#endif

        i++;            // Increment line counter

        if (i > 5000)   // Check if the file seems excessively long
        {
            if (status == rd_fin)
            {
                fprintf(stderr, "Warning: [end] missing. Possible errors.\n");
                quit = 1;
            }
            else
            {
                fprintf(stderr, "Error: Unrecognized file format.\n");
                return 0;
            }
        }

/*-----------------------------------------------------------------------------
  | Check if we found the header for a new section, and adjust status         |
  ----------------------------------------------------------------------------*/

        else if (!strcmp(tempstr, "[inf]"))
        {
#ifdef debug
            fprintf(stderr, "\t\t_R__ Begin file info at line %i.\n", i);
#endif
            status = rd_inf;
        }
        else if (!strcmp(tempstr, "[first]"))
        {
#ifdef debug
            fprintf(stderr, "\t\t_R__ Begin first syllables from line %i.\n", i);
#endif
            status = rd_first;
            j = 0;
        }
        else if (!strcmp(tempstr, "[mid]"))
        {
#ifdef debug
            fprintf(stderr, "\t\t_R__ Begin middle syllables from line %i.\n", i);
#endif
            status = rd_mid;
            j = 0;
        }
        else if (!strcmp(tempstr, "[final]"))
        {
#ifdef debug
            fprintf(stderr, "\t\t_R__ Begin final syllables from line %i.\n", i);
#endif
            status = rd_fin;
            j = 0;
        }
        else if (!strcmp(tempstr, "[end]"))
        {
#ifdef debug
            fprintf(stderr, "\t\t_R__ End of file marker on line %i.\n", i);
#endif
            quit = 1;
        }

/*-----------------------------------------------------------------------------
  | Process the information found (unless the line has been commented out)    |
  ----------------------------------------------------------------------------*/

        else if (tempstr[0] != '/' && tempstr[0] != '#' && tempstr[0] != ';')
        {
                // Display any file info

            if (status == rd_inf && !quiet)
            {
                if (!(flags & html))
                {
                    if (tempstr[0] != '@')          // Don't show HTML only
                    {                               // comments in ASCII mode

                        if (tempstr[0] == '*')      // Remove comment mark
                        {
                            for (k = 1; k < strlen(tempstr); k++)
                                tempstr[k - 1] = tempstr[k];

                            tempstr[k - 1] = '\0';
                        }
#ifdef debug_b
                        printf("\t____ INFO\n");
#endif
                        printf("%-78s\n", tempstr);
                    }
                }
                else if (tempstr[0] != '*')         // Don't show ASCII only
                {                                   // comments in HTML mode

                    if (tempstr[0] == '@')          // Remove comment mark
                    {
                        for (k = 1; k < strlen(tempstr); k++)
                            tempstr[k - 1] = tempstr[k];

                        tempstr[k - 1] = '\0';
                    }

#ifdef debug_b
                    printf("\t____ INFO\n");
#endif
                    printf("%s<BR>\n", tempstr);
                }
            }

                // Add to the syllables...

            else if (!j)        // ...unless limit exceeded
            {
                if (status == rd_first)                     // First
                {
                    if (strlen(tempstr) > syl_length)
                        fprintf(stderr, "Warning: A syllable is too long on line %i\n", i);
                    else
                    {
                        strcpy(start[num_start++], tempstr);
#ifdef debug_b
                        printf("\t____ ADD START %i> %s\n", num_start, start[num_start - 1]);
#endif

                        if (num_start > max_syl)
                        {
#ifdef debug
                            fprintf(stderr, "\t\t_R__ Beginning syllabes full at line %i.\n", i);
#endif
                            j = 1;
                        }
                    }
                }
                else if (status == rd_mid)                  // Middle
                {
                    if (strlen(tempstr) > mid_length)
                        fprintf(stderr, "Warning: A syllable is too long on line %i\n", i);
                    else
                    {
                        strcpy(mid[num_mid++], tempstr);
#ifdef debug_b
                        printf("\t____ ADD MID %i> %s\n", num_mid, mid[num_mid - 1]);
#endif

                        if (num_mid > max_syl)
                        {
#ifdef debug
                            fprintf(stderr, "\t\t_R__ Middle syllabes full at line %i.\n", i);
#endif
                            j = 1;
                        }
                    }
                }
                else if (status == rd_fin)                  // Final
                {
                    if (strlen(tempstr) > syl_length)
                        fprintf(stderr, "Warning: A syllable is too long on line %i\n", i);
                    else
                    {
                        strcpy(final[num_final++], tempstr);
#ifdef debug_b
                        printf("\t____ ADD END %i> %s\n", num_final, final[num_final - 1]);
#endif

                        if (num_final > max_syl)
                        {
#ifdef debug
                            fprintf(stderr, "\t\t_R__ Ending syllabes full at line %i.\n", i);
#endif
                            j = 1;
                        }
                    }
                }
            }
        }
    }
    while (!quit);

/*-----------------------------------------------------------------------------
  | Return to the main function, after closing file                           |
  ----------------------------------------------------------------------------*/

    fclose(namefile);

    return 1;
};

/******************************************************************************
  | Header: Prints the headers (and footers) used by the generator            |
  *****************************************************************************/

void header(int mode)
{
    if (flags & html)
    {
        if (mode == 0)      // HTML startup
        {
            printf("<HTML>\n");
            printf("<HEAD>\n");
            printf("    <TITLE>Random Name Generation</TITLE>\n");
            printf("    <META name=\"Author\" content=\"Arkhan's Random Name Generator\">\n");
            printf("    <META name=\"Description\" content=\"NGEN v1.5 (C) Kimmo Kulovesi 1998\">\n");
            printf("</HEAD>\n");
            printf("<BODY>\n<CENTER>\n");

            if (!(flags & quiet))
                printf("<H1>Names</H1>\n<BR>\n");

            if (flags & no_cols)
                printf("</CENTER>\n");
        }
        else if (mode == 1)     // HTML tables begin
        {
            if (flags & no_cols) printf("<HR width=\"100%%\">\n");
            else                 printf("<BR>\n<TABLE border=1 cellspacing=1 cellpadding=2 valign=top>\n");
        }
        else if (mode == 2)     // HTML end
        {
            if (flags & no_cols) printf("<BR>\n<HR width=\"100%%\">\n");
            else                 printf("</TABLE><BR>\n");

            printf("<FONT size=-1>Random Name Generator v1.5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Copyright (C) Kimmo Kulovesi 1998\n<BR>\n");

            if (!(flags & no_cols))
                printf("</CENTER>");

            printf("</BODY>\n</HTML>\n");
        }
    }
    else if (!(flags & quiet))
    {
        if (mode == 0)
            return;

        if (mode == 2)
            printf("\n");

        if (flags & seven_bit)
            printf("-------------------------------------------------------------------------------\n");
        else
            printf("ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\n");

        if (mode == 2)
            printf("Random Name Generator v1.5\tCopyright (C) Kimmo Kulovesi 1998\n");
    }
};

/******************************************************************************
  | Usage: Prints information about the command line parameters and quits     |
  *****************************************************************************/

void usage()
{
    printf("Random Name Generator v1.5  -  Copyright (C) Kimmo Kulovesi 1998\n");
    printf("                                             arkhan@softhome.net\n");
#ifdef seven_bits_default
    printf("\nngen <namefile> [number of names] [-h] [-c] [-q] [-8] [>> output.ext]\n");
#else
    printf("\nngen <namefile> [number of names] [-h] [-c] [-q] [-7] [>> output.ext]\n");
#endif
    printf("\nExamples: ngen harn_m2.nam 60");
    printf("\n          ngen harn_f.nam 120 -h >> names.htm");
    printf("\n          ngen general.nam -1 -q -c >> allnames.txt\n");
    printf("\n-h - Encode output in HTML (CGI/WWW use)");
    printf("\n-c - Single column mode");
    printf("\n-q - Quiet mode (no header/footer display)");
#ifdef seven_bits_default
    printf("\n-8 - Use 8-bit chars in ASCII mode\n");
#else
    printf("\n-7 - Use 7-bit chars in ASCII mode\n");
#endif
    printf("\n-1 for number of names lists all names.");
    printf("\n 0 for number of names prints info on the namefile.\n");

    exit(1);
};
