/*
file:   piglatin.cc
author: muppet
date:   16 october 1999
description:
	ever have a bunch of text you needed to translate into pig latin?
	yeah, happens all the time.  well, this program will take care
	of that for you.  *cough*
	this is rather more heavily commented than it probably needs to
	be, mostly because i wrote it for a bub who doesn't know C or C++
	very well (i won't name any names here, but he has a goatee and
	answers to the epithet ``buttmunch'').
	there's really nothing very C++-specific about this program, 
	except for the use of iostreams.  it would probably make a smaller
	binary if it were written in C...  but i don't think it really
	matters.
change log:
    16 oct 99 - muppet
	created the program...
    18 oct 99 - muppet
	Y is no longer a vowel, so that "you" can become "ouyay".
	now changes all occurrences of "a" to "an," to make the output a
	little easer to speak.
*/

/***********************************************************************
	#includes...
***********************************************************************/

#include <iostream.h>
#include <fstream.h>
#include <stdio.h>      // for perror()
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/***********************************************************************
	global variables and macros
***********************************************************************/

/*
 * in several places we will need a buffer in which to place a contructed
 * string.  let's avoid complications and declare a nice, large buffer
 * globally for general use.
 */
#define BUFLEN  1024    // set this to be sufficiently large
char buf[BUFLEN];

/***********************************************************************
	function prototypes
***********************************************************************/
inline bool isvowel(char c);
char* piglatinify ( char * dest, const char * src );
void go(istream& in, ostream& out);

/***********************************************************************
	the code...
***********************************************************************/

/*
 * we need to be able to test for whether a character is a vowel.
 * this is not built into ctype.h, so we have to roll our own.
 * instead of a macro, this is defined here as an inline function.
 * 
 * argument:
 *	c	any single character.
 * 
 * return value:
 *	a boolean value, true if the given character c is a vowel,
 *	false otherwise.
 */
inline bool isvowel(char c)
{
	// for speed considerations, this is implemented here as
	// a switch statement.  the compiler will typically generate
	// faster machine code for this construct that for a series
	// of ``if'' statements.  to make things really fast, we
	// could have defined a lookup table, but lookup tables are
	// not in general as easy to read.
	switch (c) {
		case 'A':
		case 'a':
		case 'E':
		case 'e':
		case 'I':
		case 'i':
		case 'O':
		case 'o':
		case 'U':
		case 'u':
			return true;
		default:
			return false;
	}
}


/*
 * assume that we've been passed one word, with no surrounding whitespace 
 * or punctuation.  the rules of pig latin say that if a word starts with
 * a vowel, it is left alone, otherwise, the leading consonant group is
 * moved to the end and followed with ``ay.''
 * 
 * arguments:
 *	dest	pointer to a buffer in which we will write the output.
 *		it is up to the caller to ensure that sufficient space
 *		exists.
 * 
 *	src	pointer to the source string which will be translated
 *		into pig latin.  src will NOT be altered in any way.
 * 
 * return value:
 *	returns the pointer given in dest, so that this function can
 *	be placed easily in output stream statements.
 */
char* piglatinify ( char * dest, const char * src )
{
	// first, we find the first vowel in the word.
	const char *s = src;
	while (*s && !isvowel(*s))
		s++;
	
	// copy from the first vowel to the end of the string.
	strcpy(dest,s);
	
	// if the word did not start with a vowel, s will no longer
	// be equal to src.
	if (s-src) {
		// concatenate the leading consonant group to
		// our output string --- only those (s-src) letters!
		strncat(dest,src,s-src);
		
		// add the trailing "ay".
		strcat(dest,"ay");
	}
	
	// fini.
	return dest;
}





/*
 * the piglatinify() function is quite simplistic, and requires a fairly
 * rigid input format.  this makes the algorithm for turning words into
 * pig latin much easier than if had to make it smart enough to handle 
 * punctuation, et cetera.  so, we need a smart front-end to that function.
 * 
 * this function reads raw text from one stream and writes the pig latin
 * version of that text to the output stream.  we read the input one
 * line at a time, and send one word at a time to the translator.
 * 
 * arguments:
 *	in	stream from which we read input.
 *	out	stream to which we write output.
 * return value:
 *	none.
 */
void go(istream& in, ostream&out)
{
	char linebuf[BUFLEN],	// buffer for text read from the file
	     c,			// temporary character storage
	     *word,		// points to the beginning of a word
	     *s;		// our string-iterating pointer...
	
	// read until we reach the end of file...
	while (!in.eof()) {
		// grab one line at a time, up to BUFLEN characters. we
		// don't make any special concessions in here for the case
		// that we hit the length of the buffer in the middle of a
		// word, so we need to make sure to set BUFLEN to be larger
		// than most text file lines. of course, most text file
		// lines are about 80  characters...
		in.getline(linebuf,BUFLEN,'\n');
		
		// now scan the line for words...  we will use the word
		// pointer as a kind of state variable.  when we find the
		// beginning of a word, we will point word to that
		// beginning, and then look for the end of the word.  once
		// we find the end of the word, we process it and then set
		// word back to NULL so that we know that we are now
		// looking for the beginning of a word.  (a /very/
		// simplistic state machine.)
		word=NULL;	// not initially tracking a word
		s=linebuf;	// start at the beginning of the line
		
		// read the entire line, until we reach the NULL
		// terminator ('\0') at the end of the string...
		while (*s) {
			if (!word) {
				// not currently tracking a word...
				if (isalpha(*s)) {
					// we just found the beginning
					// of a word...
					word=s;
				} else {
					// this is not part of a word,
					// go ahead and dump it to output.
					out << *s;
				}
			} else {
				// we are currently tracking a word.
				if (!isalpha(*s)) {
					// we've reached the end of this
					// word!!
					// save this letter...
					c=*s;
					
					// turn the current letter into
					// a terminator so that word
					// will point only that that word,
					// not the entire rest of linebuf.
					*s='\0';
					
					// check for a special case ---
					// since all words will begin with
					// a vowel, "a" should be changed
					// to "an"...
					if (strcmp("a",word)==0) {
						buf[0]='a';
						buf[1]='n';
						buf[2]='\0';
					} else {
						// translate this word.
						piglatinify(buf,word);
					}
					
					// output the translated word and
					// the saved letter.
					out << buf << c;
					
					// reset word to NULL, since we're
					// no longer tracking a word.
					word=NULL;
				}
			}
			// advance to the next letter...
			s++;
		}
		
		// we might have found the end of the string while still
		// tracking a word --- if so, we need to write that as
		// output!
		if (word) {
			piglatinify(buf,word);
			out << buf;
			word=NULL;
		}
		
		// we're reading one line at a time, so add the trailing
		// newline.  note that since each translation adds two
		// characters, the output lines will be  longer than the
		// input lines... there's not a whole lot we can do about
		// that without any sort of input parameters such as output
		// line width and such, so let's just pretend it's not a
		// problem...
		out << endl;
	}
}


/*
 * start point for the program.  we don't do any real work here, only
 * dispatching data to the real worker functions.
 * 
 * arguments:
 *	since main() is a special function, it's input arguments are well-
 *	defined by C/C++ tradition:
 *	argc	number of command line arguments.
 *	argv	array of pointers to the argument strings.
 * return value:
 *	it is customary to return 0 if all goes well, and a non-zero
 *	error code if something goes wrong.  here, however, we just
 *	return zero regardless.
 */
int main(int argc, char*argv[])
{
	if (argc < 2) {
		// no command-line arguments, we'll read from standard 
		// input and and write to standard output.  the user must 
		// interrupt (control+C) or send EOF (control+D in most 
		// unix shells) to quit.
		go(cin,cout);
	} else {
		// arguments given on the command line...
		// assume that the first argument is an input filename,
		// and a second argument (if it exists) is an output
		// filename.
		fstream infile;
		fstream outfile;
		
		if (argc == 2) {
			// only first arg exists.
			// read from that file, write to standard output.
			infile.open(argv[1],ios::in);
			if (!infile) {
				perror(argv[1]);
				exit(1);
			}
			go(infile,cout);
			infile.close();
		} else  if (argc > 2) {
			// second arg exists.
			// read from argv[1], write to argv[2].
			infile.open(argv[1],ios::in);
			if (!infile) {
				perror(argv[1]);
				exit(1);
			}
			outfile.open(argv[2],ios::out);
			if (!outfile) {
				perror(argv[2]);
				exit(1);
			}
			go(infile,outfile);
			infile.close();
			outfile.close();
		}
	}
	return 0;
}

