/* @author Andrew DeBiase */
    private Tuple <Chromosome2, Chromosome2> Crossover(Chromosome2 parentOne, Chromosome2 parentTwo)
    {
        int jointsLength = parentOne.jointMovements.Length;
        int crossPoint   = Random.Range(1, jointsLength);

        for (int i = crossPoint; i < jointsLength; i++)
        {
            Tuple <float, Vector3>[] temp = parentOne.jointMovements[i];
            parentOne.jointMovements[i] = parentTwo.jointMovements[i];
            parentTwo.jointMovements[i] = temp;
        }

        return(new Tuple <Chromosome2, Chromosome2>(parentOne, parentTwo));
    }
    /*
     * Handles mutating a chromosome passed in by adding a randomly generated value
     * to the torques of the chromosome
     * @author Ernest Essuah Mensah
     */
    private Chromosome2 Mutate(Chromosome2 parent)
    {
        // Copy torques from parent to mutate
        Tuple <float, Vector3>[][] jointMovements = parent.jointMovements;

        int mutationIndex = Random.Range(0, parent.jointMovements.Length);

        for (int i = 0; i < COMPLEX_CHROMOSOME_LENGTH; i++)
        {
            Tuple <float, Vector3> current = parent.jointMovements[mutationIndex][i];

            // Pick a random axis to mutate
            // 0: x-axis; 1: y-axis, 2: z-axis
            int     mutationAxis = Random.Range(0, 2);
            float   mutation     = Random.Range(-INIT_TORQUE_MAG, INIT_TORQUE_MAG);
            Vector3 newMovement;

            if (mutationAxis == 0)
            {
                newMovement = new Vector3(current.Item2.x + mutation, current.Item2.y, current.Item2.z);
            }
            else if (mutationAxis == 1)
            {
                newMovement = new Vector3(current.Item2.x, current.Item2.y + mutation, current.Item2.z);
            }
            else
            {
                newMovement = new Vector3(current.Item2.x, current.Item2.y, current.Item2.z + mutation);
            }

            // Mutate the time between joint movements
            float mutationTime = Random.Range(-MAX_TIME_BETWEEN_TORQUES, MAX_TIME_BETWEEN_TORQUES);

            float newTime = Mathf.Max(current.Item1 + mutationTime, 0);

            jointMovements[mutationIndex][i] = new Tuple <float, Vector3>(newTime, newMovement);
        }

        return(new Chromosome2(jointMovements));
    }
    // This should be called right after the golfer is instantiated
    public void InitializeAgent(Chromosome2 newChrom, GolferSettings newSettings, float holeDistOffset = 0)
    {
        chrom       = newChrom;
        jointsInUse = new Rigidbody[chrom.jointMovements.Length];
        settings    = newSettings;
        // hide the golf hole if we don't need it
        if (settings.fitnessFunc == GolferSettings.Fitness.drivingDist)
        {
            hole.gameObject.SetActive(false);
        }
        else
        {   // position the hole based on the distance it's supposed to be from the golfer, maintaining the y-coordinate
            hole.position  = transform.position - Vector3.right * (settings.holeDist + holeDistOffset) + new Vector3(0, hole.position.y, 0);
            actualHoleDist = Vector3.Distance(transform.position, hole.position);
        }

        // Destroy the joint connecting the club to the second hand if we're going one-handed
        if (settings.clubGrip == GolferSettings.ClubGrip.oneHand)
        {
            Destroy(secondHandJoint);
        }
    }
    // --------------- ACTUAL GENETIC ALGORITHM STUFF BELOW ----------------


    /* @author Matthew Graber, Azhdaha Fayyaz, Andrew DeBiase, Vladislav Dozorov */
    // Here is where the actual simulations are managed
    private IEnumerator Simulate()
    {
        // ----------- CREATE INITIAL POPULATION, SET UP SIMULATION ------------

        float holeDistOffset;

        // Initialize arrays
        chroms       = new Chromosome2[numAgents];
        agents       = new GameObject[numAgents];
        fitnesses    = new float[numAgents];
        fitnessTrack = new float[numGens, numAgents]; // 2D array with fitness for each gen

        // determine the number of joints based on whether we're using the golfer's full body or not
        int numJoints = (moveableJoints == GolferSettings.MoveableJointsExtent.fullBody ? 12 : 8);

        // initialize chromosomes with random values
        for (int i = 0; i < chroms.Length; i++)
        {
            // create Vector3 array of random torques to give to the chromosome
            Tuple <float, Vector3>[][] initJointMovements = new Tuple <float, Vector3> [numJoints][];
            for (int j = 0; j < initJointMovements.Length; j++)
            {
                initJointMovements[j] = new Tuple <float, Vector3> [COMPLEX_CHROMOSOME_LENGTH];
                for (int k = 0; k < COMPLEX_CHROMOSOME_LENGTH; k++)
                {
                    initJointMovements[j][k] = new Tuple <float, Vector3>(Random.Range(0f, MAX_TIME_BETWEEN_TORQUES),
                                                                          new Vector3(Random.Range(-INIT_TORQUE_MAG, INIT_TORQUE_MAG),
                                                                                      Random.Range(-INIT_TORQUE_MAG, INIT_TORQUE_MAG),
                                                                                      Random.Range(-INIT_TORQUE_MAG, INIT_TORQUE_MAG)));
                }
            }
            chroms[i] = new Chromosome2(initJointMovements);
        }

        // ----------- RUN THE SIMULATION ------------

        for (int i = 0; i < numGens; i++)
        {
            // display the current generation number
            currentGenText.text = "" + (i + 1) + " / " + numGens;

            // Determine the random hole distance offset for this generation
            // (if holeDistRand == 0 then it will just be 0)
            holeDistOffset = Random.Range(-holeDistRand, holeDistRand);


            // have each agent do several swings and average the fitnesses of those swings
            // clear out fitnesses array
            fitnesses = new float[numAgents];
            for (int swingNum = 0; swingNum < NUM_SWINGS_PER_GEN; swingNum++)
            {
                // Instantiate new agents, give them their respective chromosomes, tell them to start swinging their clubs
                for (int j = 0; j < agents.Length; j++)
                {
                    agents[j] = Instantiate(agentPrefab, new Vector3(PARALLEL_POSITION_OFFSET, 0, -j * DIST_BETWEEN_AGENTS), Quaternion.identity);
                    agents[j].GetComponent <GolferBrain2>().InitializeAgent(chroms[j], settings, holeDistOffset);
                    agents[j].GetComponent <GolferBrain2>().BeginSwinging();
                }

                // Allow this generation to run for the specified time.
                // Execution of this code pauses here until time is up, then automatically resumes.
                yield return(new WaitForSeconds(timePerGen));

                // Get the fitnesses of the agents and then clear them out
                for (int j = 0; j < agents.Length; j++)
                {
                    fitnesses[j] += agents[j].GetComponent <GolferBrain2>().GetFitness();
                    Destroy(agents[j]);
                }
            }
            // complete the calculation of the average fitnesses over those three swings for the gen
            for (int j = 0; j < fitnesses.Length; j++)
            {
                fitnesses[j] = fitnesses[j] / NUM_SWINGS_PER_GEN;
            }

            // Track fitness
            // Initialize best chrom & fit as the first one
            if (i == 0)
            {
                bestChrom   = chroms[0];
                bestFitness = fitnesses[0];
            }

            // Add fitnesses to 2d fitnessTrack array
            // Assign new best chrom if there is one
            for (int j = 0; j < agents.Length; j++)
            {
                fitnessTrack[i, j] = fitnesses[j];

                if (fitnessTrack[i, j] > bestFitness)
                {
                    bestFitness = fitnessTrack[i, j];
                    bestChrom   = chroms[j];
                }
            }

            // create new chromosome array
            Chromosome2[] newChroms = new Chromosome2[chroms.Length];

            /* find most fit individuals
             *  by sorting the fitnesses array in descending order,
             *  then taking the first however many biggest fitnesses
             *  and putting their respective chromosomes in the new chromosome array
             */
            float[] sortedFitnesses = fitnesses;
            Array.Sort(sortedFitnesses);
            Array.Reverse(sortedFitnesses);
            for (int j = 0; j < numElites; j++)
            {
                try {
                    newChroms[j] = chroms[Array.FindIndex(fitnesses, x => x == sortedFitnesses[j])];
                }
                catch {
                    Debug.LogWarning("something weird happened");
                }
            }

            // Andrew DeBiase
            // Crossover selection
            for (int pair = numElites; pair < chroms.Length; pair += 2)
            {
                //Tournament selection with 2 candidates for each parent
                // first parent:
                int cand1idx = Random.Range(0, numAgents);
                int cand2idx = Random.Range(0, numAgents);
                while (cand1idx == cand2idx)
                {
                    cand2idx = Random.Range(0, numAgents);
                }
                int par1idx = (fitnesses[cand1idx] > fitnesses[cand2idx] ? cand1idx : cand2idx);
                // second parent
                cand1idx = Random.Range(0, numAgents);
                cand2idx = Random.Range(0, numAgents);
                while (cand1idx == cand2idx)
                {
                    cand2idx = Random.Range(0, numAgents);
                }
                int par2idx = (fitnesses[cand1idx] > fitnesses[cand2idx] ? cand1idx : cand2idx);

                // determine if we crossover
                float crossValue = Random.Range(0.0f, 1.0f);
                if (crossValue < crossoverProb)
                {
                    // crossover
                    Tuple <Chromosome2, Chromosome2> xresult = Crossover(chroms[par1idx], chroms[par2idx]);
                    newChroms[pair]     = xresult.Item1;
                    newChroms[pair + 1] = xresult.Item2;
                }
                else
                {   // don't crossover, just send the parents on to the new array
                    newChroms[pair]     = chroms[par1idx];
                    newChroms[pair + 1] = chroms[par2idx];
                }
            }

            //Vladislav Dozorov
            //Handle when mutation occurs
            for (int candmidx = numElites; candmidx < newChroms.Length; candmidx++)
            {
                float mutationValue = Random.Range(0.0f, 1.0f);
                if (mutationValue <= mutationProb)
                {
                    newChroms[candmidx] = Mutate(newChroms[candmidx]);
                }
            }
        }
        completionText.SetActive(true);
        // ExportCSV()
        GetComponent <Exporter>().FinishedSimulation();
        yield break;
    }