// rdk

import java.util.Random;


// 		  look for subsections of phrase starting from largest looking to smallest (award small point bonus and then break if one is found)

public class Organism {
	
	public StringBuffer DNA;
	public StringBuffer aminoAcidChain;
	public double survivalValue;
	public double myLengthBonus;
	public double myMatchingBonus;
	public double myContainsBonus;
	public static double maxSurvivalValue = 0;
	public int yearsToLive;
	public static String idealPhrase;
	public static double probabilityForMutation;
	public String parent1DNA;
	public String parent2DNA;
	public int numOfMuts;	// the number of genetic mutations taken place on the DNA of the organism
	private static double maxLengthBonus = 2.4;
	private static double lengthGrowthFunc = 1.45;					// higher the number the more extreme curve
	private static double maxValueBonus =  3;
	private static double valueGrowthFunc = 1.35;
	private static double containsBonusDivider = 7;
	
	public Organism(){
		// create single-letter organism 
		
		numOfMuts = 0;
		DNA = new StringBuffer();
		aminoAcidChain = new StringBuffer();
		
		// start out with 3 DNA bases
		DNA.append(getRandomChar());
		DNA.append(getRandomChar());
		DNA.append(getRandomChar());
		
		giveAminoAcidChain();
		
		parent1DNA = "origin of life";
		parent2DNA = "origin of life";
		
		survivalValue = survivalRating(aminoAcidChain);
		
		yearsToLive = giveYearsToLive(survivalValue);
		// run through the survival rating function
		// run through the yearsToLive function (converts survival rating into a year value)
		
	}
	
	public Organism(Organism parent){
		
		parent1DNA = parent.DNA.toString();
		parent2DNA = parent.DNA.toString();
		
		DNA = new StringBuffer(parent.DNA.toString());
		aminoAcidChain = new StringBuffer();
		// randomly pick between the two parents' DNA 
		
		// genetic mutations (number determined by some probability)
		
		int numOfMutations = 0;
		for(int i = 0; i < DNA.length(); i++){
			if(isYes(probabilityForMutation))
				numOfMutations++;
		}
		
		numOfMuts = numOfMutations;
		
		for(int i = 0; i< numOfMutations; i++){
			randomlyAlterString(DNA);
		}
		
		giveAminoAcidChain();
		
		survivalValue = survivalRating(aminoAcidChain);
		
		yearsToLive = giveYearsToLive(survivalValue);
	}
	
	public Organism(Organism parent1, Organism parent2){
		
		parent1DNA = parent1.DNA.toString();
		parent2DNA = parent2.DNA.toString();
		
		DNA = new StringBuffer();
		aminoAcidChain = new StringBuffer();
		// randomly pick between the two parents' DNA 
		
		int pickParent = getRandomNumber(1);		// pick which parent's DNA length to match
		
		int lengthOfDNA = 0;
		if(pickParent == 0)
			lengthOfDNA = parent1.DNA.length();
		else
			lengthOfDNA = parent2.DNA.length();
		
		
		int choice = 0;
		boolean done = false;
		for(int i = 0; (i*3) < lengthOfDNA ; i++){
			if(parent1.DNA.length() >= (i*3)+3 && parent2.DNA.length() >= (i*3)+3){
				choice = getRandomNumber(1);
				if(choice == 0)
					DNA.append(parent1.DNA.substring((i*3), (i*3) + 3));
				else
					DNA.append(parent2.DNA.substring((i*3), (i*3) + 3));
			}
			else{
				if(pickParent == 0)
					DNA.append(parent1.DNA.substring((i*3), lengthOfDNA));
				else
					DNA.append(parent2.DNA.substring((i*3), lengthOfDNA));
				
				done = true;
			}
			if(done == true)
				break;
		}
		
		
		// genetic mutations (number determined by some probability)
		
		int numOfMutations = 0;
		for(int i = 0; i < DNA.length(); i++){
			if(isYes(probabilityForMutation))
				numOfMutations++;
		}
		
		numOfMuts = numOfMutations;
		
		for(int i = 0; i< numOfMutations; i++){
			randomlyAlterString(DNA);
		}
		
		giveAminoAcidChain();
		
		survivalValue = survivalRating(aminoAcidChain);
		
		yearsToLive = giveYearsToLive(survivalValue);
	}
	

	
	private char getRandomChar(){
		Random r = new Random();
		
		int randLetterNum = Math.abs(r.nextInt()) % 4;
		char randChar = "augc".charAt(randLetterNum);	// get random lower-case letter
		
		return randChar;
	}
	
	private void giveAminoAcidChain(){
		// basic first, add more complex rules later
		int possibleNumberOfAminoAcidsInChain = (int)Math.floor(DNA.length() / 3);
		String threeBases = null;
		char theAminoAcid = ' ';
		for(int i = 0; i < possibleNumberOfAminoAcidsInChain; i++){
			threeBases = DNA.substring(i*3, (i*3) + 3);
			theAminoAcid = getAminoAcid(threeBases);
			if(theAminoAcid == 'Z')						// if stop code
				break;
			aminoAcidChain.append(theAminoAcid);
		
		}
		
	}
	
	
	private char getAminoAcid(String threeBases){
		if(threeBases.equals("uuu") || threeBases.equals("uuc"))	// phenylalanine
			return 'F';
		else if(threeBases.equals("uua") || threeBases.equals("uug") || threeBases.equals("cuu")
				|| threeBases.equals("cuc") || threeBases.equals("cua") || threeBases.equals("cug"))	// leucine
			return 'L';
		else if(threeBases.equals("auu") || threeBases.equals("auc") || threeBases.equals("aua"))	// isoleucine
			return 'I';
		else if(threeBases.equals("aug"))	// methionine
			return 'M';
		else if(threeBases.equals("guu") || threeBases.equals("guc") || threeBases.equals("gua")
				|| threeBases.equals("gug"))														// valine
			return 'V';
		else if(threeBases.equals("ucu") || threeBases.equals("ucc") || threeBases.equals("uca")
				|| threeBases.equals("ucg") || threeBases.equals("agu") || threeBases.equals("agc"))	// serine
			return 'S';
		else if(threeBases.equals("ccu") || threeBases.equals("ccc") || threeBases.equals("cca")
				|| threeBases.equals("ccg"))														// proline
			return 'P';
			else if(threeBases.equals("acu") || threeBases.equals("acc") || threeBases.equals("aca")
					|| threeBases.equals("acg"))														// threonine
			return 'T';
		else if(threeBases.equals("gcu") || threeBases.equals("gcc") || threeBases.equals("gca")
				|| threeBases.equals("gcg"))														// alanine
			return 'A';
		else if(threeBases.equals("uau") || threeBases.equals("uac"))								// tyrosine
			return 'Y';
		else if(threeBases.equals("uaa") || threeBases.equals("uag") || threeBases.equals("uga"))
			return 'Z';  																		// stop signal
		else if(threeBases.equals("cau") || threeBases.equals("cac"))							// histidine
			return 'H';
		else if(threeBases.equals("caa") || threeBases.equals("cag"))							// glutamine
			return 'Q';
		else if(threeBases.equals("aau") || threeBases.equals("aac"))							// asparagine
			return 'N';
		else if(threeBases.equals("aaa") || threeBases.equals("aag"))							// lysine
			return 'K';
		else if(threeBases.equals("gau") || threeBases.equals("gac"))							// aspartic acid
			return 'D';
		else if(threeBases.equals("gaa") || threeBases.equals("gag"))							// glutamic acid
			return 'E';
		else if(threeBases.equals("ugu") || threeBases.equals("ugc"))							// cysteine
			return 'C';
		else if(threeBases.equals("ugg"))														// trptophan
				return 'W';
		else if(threeBases.equals("cgu") || threeBases.equals("cgc") || threeBases.equals("cga")
				|| threeBases.equals("cgg") || threeBases.equals("aga") || threeBases.equals("agg"))	// arginine
			return 'R';
		else if(threeBases.equals("ggu") || threeBases.equals("ggc") || threeBases.equals("gga")
				|| threeBases.equals("ggg"))													// glycine
			return 'G';
		else
			return '!';	// error!
		
		// list of all base triplets for amino acids
	}
	
	public void updateOrganism(){
		survivalValue = survivalRating(aminoAcidChain);
		yearsToLive = giveYearsToLive(survivalValue);
	}
	
	
	private void randomlyAlterString(StringBuffer workingString){
		int numberOfOptions = (3 * workingString.length()) + 1;
		Random r = new Random();
		int randInt = Math.abs(r.nextInt()) % numberOfOptions;		// get operation to perform (insertion or value change)
		
		int randLetterNum = Math.abs(r.nextInt()) % 4;
		char randLetter = "augc".charAt(randLetterNum);	// get random lower-case letter
		
		if(randInt <= workingString.length()){						// insert/ append letter
			// insert (remember case of insert after the end
				if(randInt < workingString.length())
					workingString.insert(randInt, randLetter);
				else
					workingString.append(randLetter);	
		}
		else if (randInt > workingString.length() && randInt < (2 * workingString.length()) +1){														// modify existing letter
			// alter existing letter ( letter at position randInt - 2 )
			workingString.setCharAt(randInt - workingString.length() -1, randLetter);
		}
		else{
			if(workingString.length() >= 1)
				workingString.deleteCharAt(randInt - (2 * workingString.length()) - 1);
		}
		
	}
	
	
	//	 compare the workingString to idealPhrase and return a value based on how well they match up
	private double survivalRating(StringBuffer workingString){
		double theScore = 0;
		
		if(workingString.length() == 0 && idealPhrase.length() != 0){
			return 0;
		}
		
		// environment favors the workingString to be the same length as idealPhrase

		int numberOfLetters = 0;
		if(workingString.length() <= idealPhrase.length())
			numberOfLetters = workingString.length();
		else
			numberOfLetters = (2 * idealPhrase.length()) - workingString.length();
		
		if(numberOfLetters < 0)
			numberOfLetters = 0;
		double lengthBonus = (Math.pow(lengthGrowthFunc, (double)numberOfLetters + (10 - idealPhrase.length())));
		lengthBonus =  (lengthBonus/ Math.pow(lengthGrowthFunc, 10)) * maxLengthBonus;	// max of maxLengthBonus points added to the score if the length of workingString is the same as idealPhrase
		if(numberOfLetters == 0)
			lengthBonus = 0;
		myLengthBonus = lengthBonus;
		theScore += lengthBonus;
		
		
		
		// environment favors the workingString to have the same values as idealPhrase
		double numberMatch = 0;
		for(int i = 0; i < workingString.length(); i++){
			if((idealPhrase.length() - 1) >= i){
				if(workingString.charAt(i) == idealPhrase.charAt(i))
					numberMatch++;
			}
		}
		double matchingBonus = (Math.pow(valueGrowthFunc, (double)numberMatch + (10 - idealPhrase.length())));
		matchingBonus = (matchingBonus / Math.pow(valueGrowthFunc, 10)) * maxValueBonus;
		if(numberMatch == 0)
			matchingBonus = 0;
		myMatchingBonus = matchingBonus;
		theScore += matchingBonus;

		
		
		//environment has some favortism towards the workingString that contains values of the idealPhrase
		double containsBonus = 0;
		if(idealPhrase.contains(workingString)){
			containsBonus = (Math.pow(valueGrowthFunc, 1 + (10 - idealPhrase.length())));
			containsBonus = (containsBonus / Math.pow(valueGrowthFunc, 10)) * (maxValueBonus / containsBonusDivider);
			if(workingString.length() == 0)
				containsBonus = 0;
		}
		myContainsBonus = containsBonus;
		theScore += containsBonus;
			
		
		return theScore;
	}
	
	private int giveYearsToLive(double survivalValue){
		// max survival rating 
		double maxContainsBonus = 0;
		
		maxContainsBonus = (Math.pow(1.5, 1 + (10 - idealPhrase.length())));
		maxContainsBonus = (maxContainsBonus / 57.665039) * (maxValueBonus / 3);


		maxSurvivalValue = maxLengthBonus + maxValueBonus + maxContainsBonus;
		double probabilityOfLiving = (survivalValue / maxSurvivalValue) * .95;						// .95: not all "perfect" organisms live (5% die)
		boolean live = isYes((probabilityOfLiving));
		if(live){
			if(survivalValue >= 1)
				return (int)Math.round(survivalValue);
			else
				return 1;
		}
		else
			return -1;
	}
	
	private int getRandomNumber(int max){
		Random r = new Random();
		
		int randNum = Math.abs(r.nextInt()) % (max + 1);
		
		return randNum;
	}

	private boolean isYes(double probability){
		Random r = new Random();
		
		int randNum = (Math.abs(r.nextInt()) % 1000) + 1;
		if(randNum >= 0 &&  randNum<= (probability * 1000)){
			return true;
		}
		return false;
		
	}
	
}
