/* TO DO
--*Make more musical
---*More instruments
---*Pressure Levels
---*Tempo Per Machine
*/

import java.lang.*;
import java.util.*;
import java.io.*;
import javax.swing.*;
import java.awt.*;

public class GeneticFiniteStates extends JFrame
{

	// DECIDE WHAT TO DO WITH ALL OF THESE CONSTANTS, SHOULD THEY BE CHANGEABLE?
	public static int NUM_MACHINES = 20; // Change to 50
	public static int NUM_MACHINES_TO_KEEP = 5; // Change to 10, must be less than 50% - 2 of machines to keep
	public static int NOTES_TO_PLAY = 10;

	Vector CurrentMachines = new Vector();
	Vector RankedMachines = new Vector();
	int currentMachineNumber = 0;

	public boolean machinePlaying = false;
	public Long startTime = new Long(System.currentTimeMillis());

	Random theRanGen = new Random();
	
	File outputFile;


/***************************************************************************/
    private void initComponents() {
	setTitle("mGen");
        getContentPane().setLayout(new BorderLayout());
        getContentPane().setBackground(Color.WHITE);

	javax.swing.JMenuBar menuBar = new javax.swing.JMenuBar();
	javax.swing.JMenu fileMenu = new javax.swing.JMenu();
	javax.swing.JMenuItem fileMenuLoad = new javax.swing.JMenuItem();
	javax.swing.JMenuItem fileMenuSave = new javax.swing.JMenuItem();
	menuBar.setVisible(true);
	fileMenu.setVisible(true);
	fileMenu.setText("File");
	fileMenuLoad.setVisible(true);
	fileMenuLoad.setText("Load");
	fileMenuSave.setVisible(true);
	fileMenuSave.setText("Save");
	setJMenuBar(menuBar);
	menuBar.add(fileMenu);
	fileMenu.add(fileMenuLoad);
	fileMenu.add(fileMenuSave);

	// Menu Actions, No Seperate Functions
	fileMenuLoad.addActionListener(new java.awt.event.ActionListener() {
		public void actionPerformed(java.awt.event.ActionEvent e) {
			try
			{
		    		JFileChooser fileChooser = new JFileChooser();
		    		int returnVal = fileChooser.showOpenDialog(GeneticFiniteStates.this);
		    		if (returnVal == JFileChooser.APPROVE_OPTION) 
				{
					File inputFile = fileChooser.getSelectedFile();
				
                                	if ((inputFile.exists()))
                                	{
						// Open input reader for the file
	                                	FileReader frFilename = new FileReader(inputFile);
	
	                                	// Buffer the input
						BufferedReader brFilename = new BufferedReader(frFilename);
					
						CurrentMachines.removeAllElements();
						String readLine;	
						while ((readLine = brFilename.readLine()) != null)
						{
							FiniteStateMusicMachine newMachine = new FiniteStateMusicMachine(readLine);
							CurrentMachines.add(newMachine);
						}
	                                 
	                                	// Close the output stream
	                                	frFilename.close();       
                        		}
		    		}
			}
			catch (Exception ex)
			{
				ex.printStackTrace();
			}
		}
	});

	fileMenuSave.addActionListener(new java.awt.event.ActionListener() {
		public void actionPerformed(java.awt.event.ActionEvent e) {
			try
			{
				JFileChooser fileChooser = new JFileChooser();
			    	int returnVal = fileChooser.showOpenDialog(GeneticFiniteStates.this);
			    	if (returnVal == JFileChooser.APPROVE_OPTION) 
				{
					File outputFile = fileChooser.getSelectedFile();
	
	                                if (outputFile.isDirectory())
	                                {
						System.out.println("That is a directory!");
	                                }
	                                else
	                                {
	                                        if (!(outputFile.exists()))
	                                        {
	                                                outputFile.createNewFile();
	                                        }
	                                        
						// Open output stream to the file in append mode
	                                        FileWriter fwFilename = new FileWriter(outputFile, false);
	 
	                                        // Buffer the output stream
	                                        PrintWriter pwFilename = new PrintWriter(fwFilename);
	 
	                                        // Print to the file
						// Output to the filename
						for (int i = 0; i < CurrentMachines.size(); i++)
						{
							pwFilename.println( ((FiniteStateMusicMachine) CurrentMachines.elementAt(i)).toString() );		
						}
	                                         
	                                        // Close the output stream
	                                        fwFilename.close();
	                                }
			    	}
			}
			catch (Exception ex)
			{
				ex.printStackTrace();
			}
		}
	});

        addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                formMousePressed(evt);
            }
            public void mouseReleased(java.awt.event.MouseEvent evt) {
                formMouseReleased(evt);
            }
        });

        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                exitForm(evt);
            }
        });

	ImageIcon mgenGraphic = new ImageIcon("mgen_interface.gif");
	JLabel myLabel = new JLabel(mgenGraphic);
	getContentPane().add(myLabel);
        pack();
    }

    private void formMouseReleased(java.awt.event.MouseEvent evt)
    {
	if (machinePlaying)
	{
		((FiniteStateMusicMachine) CurrentMachines.elementAt(currentMachineNumber)).stopMachine();
		machinePlaying = false;
        	Long machineRank = new Long(System.currentTimeMillis() - startTime.longValue());
		RankedMachines.add(new RankedMachine(machineRank, (FiniteStateMusicMachine) CurrentMachines.elementAt(currentMachineNumber)));
		currentMachineNumber++;
	}

	if (currentMachineNumber >= CurrentMachines.size())
	{
		outputMachines(RankedMachines);
		sortRankedMachines(RankedMachines);
		evolveMachines();
		generateMachines();
		currentMachineNumber = 0;
	}
    }

    private void formMousePressed(java.awt.event.MouseEvent evt)
    {
        if (currentMachineNumber < CurrentMachines.size())
        {
	        startTime = new Long(System.currentTimeMillis());
		((FiniteStateMusicMachine) CurrentMachines.elementAt(currentMachineNumber)).playMachine(NOTES_TO_PLAY);
		machinePlaying = true;
	}
	else
	{
		outputMachines(RankedMachines);
		sortRankedMachines(RankedMachines);
		evolveMachines();
		generateMachines();

		currentMachineNumber = 0;
	        startTime = new Long(System.currentTimeMillis());
		((FiniteStateMusicMachine) CurrentMachines.elementAt(currentMachineNumber)).playMachine(NOTES_TO_PLAY);
	}
    }

    /** Exit the Application */
    private void exitForm(java.awt.event.WindowEvent evt) {
        System.exit(0);
    }

    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;

/**********************************************************************************/

	public static void main(String[] args)
	{
		GeneticFiniteStates gfs = new GeneticFiniteStates();
		gfs.show();
		gfs.generateMachines();
		/*
		gfs.CurrentMachines.add(new FiniteStateMusicMachine(""));
		gfs.CurrentMachines.add(new FiniteStateMusicMachine(""));
		gfs.CurrentMachines.add(new FiniteStateMusicMachine(""));
		gfs.CurrentMachines.add(new FiniteStateMusicMachine(""));
		gfs.CurrentMachines.add(new FiniteStateMusicMachine(""));
		gfs.CurrentMachines.add(new FiniteStateMusicMachine(""));
		gfs.CurrentMachines.add(new FiniteStateMusicMachine(""));
		gfs.CurrentMachines.add(new FiniteStateMusicMachine(""));
		gfs.CurrentMachines.add(new FiniteStateMusicMachine(""));
		gfs.CurrentMachines.add(new FiniteStateMusicMachine(""));
		*/
	}

	GeneticFiniteStates()
	{
	        initComponents();
	}

	public void outputMachines(Vector RankedMachines)
	{
		try
		{
			// Create the filename object
		        File fileFilename = new File("OutputFile.txt");

			// Check to see if it exists, if not create it.
		        if (!(fileFilename.exists()))
		        {
		                fileFilename.createNewFile();
		        }

		        // Open output stream to the file in append mode
		        FileWriter fwFilename = new FileWriter(fileFilename.getName(), true);

		        // Buffer the output stream
		        PrintWriter pwFilename = new PrintWriter(fwFilename);

			// Output to the filename
			for (int i = 0; i < RankedMachines.size(); i++)
			{
				pwFilename.println( ((FiniteStateMusicMachine) ((RankedMachine) RankedMachines.elementAt(i)).returnMachine()).toString() + " " + ((RankedMachine) RankedMachines.elementAt(i)).returnRank().toString() );
				System.out.println( ((FiniteStateMusicMachine) ((RankedMachine) RankedMachines.elementAt(i)).returnMachine()).toString() + " " + ((RankedMachine) RankedMachines.elementAt(i)).returnRank().toString() );

			}
			pwFilename.println();

		        // Close the output stream
		        fwFilename.close();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	public FiniteStateMusicMachine crossover(FiniteStateMusicMachine machineOne, FiniteStateMusicMachine machineTwo)
	{
		int whichFirst = theRanGen.nextInt(2);
		int crossoverPoint = theRanGen.nextInt(machineOne.toString().length() - 1);
		crossoverPoint = crossoverPoint + 1; // Can not be zero, zero is the machineObject size
		FiniteStateMusicMachine newMachine;
		if (whichFirst == 0)
		{
			newMachine = new FiniteStateMusicMachine(machineOne.toString().substring(0,crossoverPoint) + machineTwo.toString().substring(crossoverPoint,machineTwo.toString().length()));
		}
		else
		{
			newMachine = new FiniteStateMusicMachine(machineTwo.toString().substring(0,crossoverPoint) + machineOne.toString().substring(crossoverPoint,machineTwo.toString().length()));
		}
		return newMachine;
	}

	public FiniteStateMusicMachine mutate(FiniteStateMusicMachine theMachine)
	{
		StringBuffer newMachineString = new StringBuffer(theMachine.toString());
		int numMutations = theRanGen.nextInt(10); // Max of 10 mutations
		//System.out.println("Num Mutations: " + new Integer(numMutations).toString());
		for (int i = 0; i < numMutations; i++)
		{
			int mutationPoint = theRanGen.nextInt(theMachine.toString().length() - 1);
			mutationPoint = mutationPoint + 1; // Can not be zero, zero is the machineObject size
			//System.out.println("Mutation Point: " + new Integer(mutationPoint).toString());
			int theMutation = theRanGen.nextInt(10);
			//System.out.println("The Mutation: " + new Integer(theMutation).toString());
			newMachineString.setCharAt(mutationPoint,Character.forDigit(theMutation,10));
		}

		FiniteStateMusicMachine newMachine = new FiniteStateMusicMachine(newMachineString.toString());
		//System.out.println("New Machine: " + newMachine.toString());

		return newMachine;
	}

	public void evolveMachines()
	{
		// Need to make sure we aren't going over the number to have, otherwise the generations will just keep growing
		// In other words, make sure the num to keep is 50% - 2 of the total machines
		// I should hard code it to 50% - 12

		Vector NewMachines = new Vector();

		for (int i = 0; i < GeneticFiniteStates.NUM_MACHINES_TO_KEEP; i++)
		{
			NewMachines.add((FiniteStateMusicMachine) CurrentMachines.elementAt(i));

			if (i == 0 || i == 1)
			{
				NewMachines.add(mutate((FiniteStateMusicMachine) CurrentMachines.elementAt(i)));
			}
			else
			{
				if (i > 0)
				{
					NewMachines.add(crossover((FiniteStateMusicMachine) CurrentMachines.elementAt(i-1), (FiniteStateMusicMachine) CurrentMachines.elementAt(i)));
				}
			}
		}
		CurrentMachines.removeAllElements();
		CurrentMachines.addAll(NewMachines);
	}

	private void sortRankedMachines(Vector RankedMachines)
	{
		Vector TempHolder = new Vector();
		for (int i = 0; i < RankedMachines.size(); i++)
		{
			boolean isInserted = false;
			for (int q = 0; q < TempHolder.size(); q++)
			{
				if ((((RankedMachine) RankedMachines.elementAt(i)).returnRank().longValue() > ((RankedMachine) TempHolder.elementAt(q)).returnRank().longValue()) && !(isInserted))
				{
					TempHolder.add(q,((RankedMachine) RankedMachines.elementAt(i)));
					isInserted = true;
				}
			}
			if (!(isInserted))
			{
				TempHolder.add(TempHolder.size(),((RankedMachine) RankedMachines.elementAt(i)));
			}
		}
		RankedMachines.removeAllElements();
		CurrentMachines.removeAllElements();
		for (int i = 0; i < TempHolder.size(); i++)
		{
			CurrentMachines.add(((RankedMachine) TempHolder.elementAt(i)).returnMachine());
		}
	}

	public void generateMachines()
	{

		// 10 Machines
		for (int i = CurrentMachines.size(); i < GeneticFiniteStates.NUM_MACHINES; i++)
		{

			StringBuffer theRanString = new StringBuffer();
			theRanString.append(FiniteStateMusicMachine.MACHINE_OBJECT_LENGTH);

			// Num Objects per machine
			for (int q = 0; q < FiniteStateMusicMachine.NUM_OBJECTS_MACHINE; q++)
			{
				// Object Length
				for (int p = 0; p < FiniteStateMusicMachine.MACHINE_OBJECT_LENGTH; p++)
				{
					theRanString.append(theRanGen.nextInt(10));
				}

			}

			FiniteStateMusicMachine theMachine = new FiniteStateMusicMachine(theRanString.toString());

			CurrentMachines.add(theMachine);

		}
		scrambleCurrentMachines();
	}

	public void scrambleCurrentMachines()
	{
		//System.out.println("Scrambling Machines");
		Vector ScrambledMachines = new Vector();
		Vector currentMachinesIndexUsed = new Vector();

		for (int i = 0; i < CurrentMachines.size(); i++)
		{
			boolean indexUsed = true;
			int tempCounter = 0;
			while (indexUsed)
			{
				//System.out.println(tempCounter++);
				Integer currentMachineIndex = new Integer(theRanGen.nextInt(CurrentMachines.size()));
				if (!currentMachinesIndexUsed.contains(currentMachineIndex))
				{
					indexUsed = false;
					ScrambledMachines.add(CurrentMachines.elementAt(currentMachineIndex.intValue()));
					currentMachinesIndexUsed.add(currentMachineIndex);
				}
				// else just keep going
			}
		}

		CurrentMachines.removeAllElements();
		CurrentMachines.addAll(ScrambledMachines);
	}

	private class RankedMachine
	{
		private Long rankNumber;
		private FiniteStateMusicMachine machineObject;

		RankedMachine(Long theRankNumber, FiniteStateMusicMachine theMachine)
		{
			rankNumber = theRankNumber;
			machineObject = theMachine;
		}

		public Long returnRank()
		{
			return rankNumber;
		}

		public FiniteStateMusicMachine returnMachine()
		{
			return machineObject;
		}
	}
}
