/*
 * Name: Geoff Kuenning
 * Course: CS 70, Fall 2000
 * Assignment #7
 *
 * This file contains the main program for the assignment.  This
 * program serves as a test driver for a set of DNA recombination
 * functions.
 *
 * Since it is a test driver, the program has a very simple
 * command-line interface.  There are two methods of invocation:
 *
 *	assign_07 mutate <position> <string1> <string2>
 *
 * or
 *
 *	assign_07 combine <position> <string1> <string2>
 *
 * In both cases, "position" is a number, and the "strings" are
 * strings of characters (presumably gene sequences).  Positions are
 * numbered from zero.
 *
 * The mutate operation replaces a single character (gene) at the
 * specified position of string1 with the entire contents of string2.
 * Note that this can make the gene sequence longer if string2 is more
 * than one character long, or can shorten it if string2 is the empty
 * string.
 *
 * The combine operation glues the first "position" characters of
 * string1 to the remaining characters of string2 beginning at
 * "position" and continuing to the end of string2.  The two strings
 * do not need to be the same length.
 *
 * Examples:
 *
 *	Shell command				Output
 *
 *	assign_07 mutate 3 abcdefg X		abcXefg
 *	assign_07 mutate 3 abcdefg XY		abcXYefg
 *	assign_07 mutate 3 abcdefg ''		abcefg
 *	assign_07 mutate 99 abcdefg X		abcdefg
 *	assign_07 combine 0 abcdefg uvwxyz	uvwxyz
 *	assign_07 combine 3 abcdefg uvwxyz	abcxyz
 *	assign_07 combine 99 abcdefg uvwxyz	abcdefg
 *	assign_07 combine 4 abc uvwxyz		abcyz
 */

#include "charlist.hh"
#include <iostream>
#include <string>

/*
 * Table of contents: the following functions are defined in the file
 */
int			main(int argc, char* argv[]);
					// Test driver
static void		usage(char* programName);
					// Issue a usage message
CharList		mutate(const CharList& target, int position,
			  const CharList& newGenes);
					// Do the mutation operation
CharList		combine(const CharList& parent1,
			  const CharList& parent2, int position);
					// Do the combination operation

/*
 * Global constants.  These are used to define the layout of arguments.
 */
const int		ARGS_NEEDED = 4;
					// Number of arguments expected
					// ..(excluding program name)
const int		OPERATION_ARG = 0 + 1;
					// Where to find operation
const int		POSITION_ARG = 1 + 1;
					// Where to find position
const int		STRING1_ARG = 2 + 1;
					// Where to find first string
const int		STRING2_ARG = 3 + 1;
					// Where to find second string

/*
 * Main driver for testing DNA recombination.  See above for
 * documentation.
 */
int main(
    int			argc,		// Argument count
    char*		argv[])		// Argument vector
    {

    /*
     * Check the arguments to be sure we have the right number.  Then
     * convert the position to an integer, and convert the two strings
     * into two lists of characters.
     */
    if (argc != ARGS_NEEDED + 1)
	usage(argv[0]);
    int position = atoi(argv[POSITION_ARG]);
    CharList string1(argv[STRING1_ARG]);
    CharList string2(argv[STRING2_ARG]);

    /*
     * Figure out the operation requested, do it, and print the result.
     */
    CharList modifiedString;
    if (string(argv[OPERATION_ARG]) == "mutate")
	modifiedString = mutate(string1, position, string2);
    else if (string(argv[OPERATION_ARG]) == "combine")
	modifiedString = combine(string1, string2, position);
    else
	usage(argv[0]);

    cout << modifiedString << endl;

    /*
     * Because the usage routine aborts the program, we can only get
     * here if we succeeded.
     */
    return 0;
    }

/*
 * Print a usage message and exit.  As is conventional, the exit
 * status is 2 if there is a usage error.
 */
static void usage(
    char*		programName)	// Name we were run under
    {
    cerr << "Usage:\t" << programName
      << " mutate position string1 new-value\n";
    cerr << '\t' << programName << " combine position string1 string2\n";
    exit(2);
    }

/*
 * Perform DNA mutation.  We walk through the target string, copying
 * it to the result.  When we get to the mutated position, we replace
 * it with a copy of the new genes to be inserted.
 */
CharList mutate(
    const CharList&	target,		// Gene list to be mutated
    int			position,	// Position to mutate (0-indexed)
    const CharList&	newGenes)	// Genes to insert at mutation point
    {
    CharList result;

    /*
     * Because a CharListIterator doesn't provide a way to find out
     * how far we are in the list, we need a separate variable to
     * track our position.
     */
    int whereWeAre = 0;

    /*
     * Copy the target to the result verbatim, except that when we
     * reach the mutated position, we copy all of newGenes into the
     * result, and skip the gene at the target in that position.
     */
    for (CharListIterator i(target);  i;  ++i, ++whereWeAre)
	{
	if (whereWeAre != position)
	    result.pushTail(*i);
	else
	    {
	    for (CharListIterator j(newGenes);  j;  ++j)
		result.pushTail(*j);
	    }
	}

    return result;
    }

/*
 * Perform DNA recombination.  We walk through the two parent strings
 * simultaneously, copying one or the other to the result.  The choice
 * of what to copy depends on the current position relevant to the
 * recombination position.
 */
CharList combine(
    const CharList&	parent1,	// First parent's gene list
    const CharList&	parent2,	// Second parent's gene list
    int			position)	// Where to switch from first to second
    {
    CharList result;

    /*
     * We need one iterator for each parent, plus an integer to track
     * the current position (as in mutation).
     */
    CharListIterator parent1Gene(parent1);
    CharListIterator parent2Gene(parent2);

    /*
     * Go through both gene lists.  Note that the loop continues as
     * long as *either* parent has valid genes.  A side implication of
     * this loop structure is that the iterators must be robust enough
     * to allow operator++ to be called even if they are expired.
     */
    for (int currentPosition = 0;
      parent1Gene  ||  parent2Gene;
      ++parent1Gene, ++parent2Gene, ++currentPosition)
	{

	/*
	 * Depending on the current position, add one gene or the
	 * other to the result list.  Note that we only attempt the
	 * push if the associated iterator is valid (i.e., if it can
	 * legally be dereferenced).  To do otherwise would either
	 * produce garbage output or segfault the program, depending
	 * on the implementation of the iterators.
	 */
	if (currentPosition < position)
	    {
	    if (parent1Gene)
		result.pushTail(*parent1Gene);
	    }
	else
	    {
	    if (parent2Gene)
		result.pushTail(*parent2Gene);
	    }
	}

    return result;
    }
