/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package genetic;

import jm.music.data.Note;
import jm.music.data.Phrase;

/**
 *
 * @author smuRf
 */
public class FitnessFunction {
    private Phrase chromosome;
    private int range;
    private int lowestNote;
    
    public FitnessFunction(Phrase chromosome, int range, int lowestNote)
    {
        this.range = range;
        this.lowestNote = lowestNote;
        this.chromosome = chromosome.copy();
    }
    
    public double getFitnessValue()
    {
        double[] fitnessObjectives = new double[9];
        double fitness = 0;
        
        fitnessObjectives[0] = getNoteRelationshipFitness();
        fitnessObjectives[1] = getNoteDirectionFitness();
        fitnessObjectives[2] = getBeginningEndingNoteFitness();
        fitnessObjectives[3] = getOverFifthFitness();
        fitnessObjectives[4] = getDrasticDurationChangeFitness();
        fitnessObjectives[5] = getNoteInRangeFitness();
        fitnessObjectives[6] = getLastNoteLongerFitness();
        fitnessObjectives[7] = getCMajorFitness();
        fitnessObjectives[8] = getCorrectTimingFitness();
        
        
        for(double f:fitnessObjectives)
            fitness += f;
        
        return (fitness / fitnessObjectives.length);
    }
    
    private double getLastNoteLongerFitness()
    {
        if(chromosome.getNote(chromosome.size() - 1).getRhythmValue() >= 1.0)
            return 1.0;
        else
            return 0.0;
    }
    
    private double getCMajorFitness()
    {
        double f = 0;
        int cMajor = 0;
        
        Note[] noteArray = chromosome.getNoteArray();
        for(int i = 0; i < noteArray.length; i++)
        {
            if(!noteArray[i].isFlat() && !noteArray[i].isSharp())
                cMajor ++;
        }
        
        f = (double)cMajor * 1.1 / noteArray.length;
        return f;
    }
    
    private double getNoteRelationshipFitness()
    {
        double f = 0;
        int same = 0;
        int oneStep = 0;
        int twoSteps = 0;
        int threeSteps = 0;
        int fourSteps = 0;
        
        Note[] noteArray = chromosome.getNoteArray();
        for(int i = 1; i < noteArray.length; i++)
        {
            if(Math.abs(noteArray[i].getPitch() - noteArray[i - 1].getPitch()) == 0)
                same ++;
            if(Math.abs(noteArray[i].getPitch() - noteArray[i - 1].getPitch()) == 1)
                oneStep ++;
            if(Math.abs(noteArray[i].getPitch() - noteArray[i - 1].getPitch()) == 2)
                twoSteps ++;
            if(Math.abs(noteArray[i].getPitch() - noteArray[i - 1].getPitch()) == 3)
                threeSteps ++;
            if(Math.abs(noteArray[i].getPitch() - noteArray[i - 1].getPitch()) == 4)
                fourSteps ++;
        }
        
        f = (((double)same * 0.4) + ((double)oneStep * 1.4) + ((double)twoSteps * 1.4) + ((double)threeSteps * 1.0) + ((double)fourSteps * 0.6)) / (noteArray.length - 1);
        return f;
    }
    
    private double getNoteInRangeFitness()
    {
        double f = 0;
        int inRange = 0;
        
        Note[] noteArray = chromosome.getNoteArray();
        for(int i = 0; i < noteArray.length; i++)
        {
            if(noteArray[i].getPitch() >= lowestNote && noteArray[i].getPitch() < lowestNote + range)
                inRange ++;
        }
        
        f = (double)inRange / (noteArray.length);
        return f;
    }
    
    private double getCorrectTimingFitness()
    {
        double f = 0;
        int correctTiming = 0;
        
        Note[] noteArray = chromosome.getNoteArray();
        double sumBarRhythm = 0;
        for(int i = 0; i < noteArray.length; i++)
        {
            sumBarRhythm += noteArray[i].getRhythmValue();
            
            if(sumBarRhythm == 4)
            {
                correctTiming ++;
                sumBarRhythm = 0;
            }
            if(sumBarRhythm > 4)
                sumBarRhythm = 0;
        }
        
        f = (double)correctTiming * 1.1 / (chromosome.getBeatLength() / 4.0);
        return f;
    }
    
    private double getNoteDirectionFitness()
    {
        double f = 0;
        int falling = 0;
        int rising = 0;
        int stable = 0;
        int upDown = 0;
        int downUp = 0;
        
        Note[] noteArray = chromosome.getNoteArray();
        for(int i = 2; i < noteArray.length; i++)
        {
            if(noteArray[i - 2].getPitch() < noteArray[i - 1].getPitch() && noteArray[i - 1].getPitch() < noteArray[i].getPitch())
                rising ++;
            if(noteArray[i - 2].getPitch() > noteArray[i - 1].getPitch() && noteArray[i - 1].getPitch() > noteArray[i].getPitch())
                falling ++;
            if(noteArray[i - 2].getPitch() == noteArray[i - 1].getPitch() && noteArray[i - 1].getPitch() == noteArray[i].getPitch())
                stable ++;
            if(noteArray[i - 2].getPitch() > noteArray[i - 1].getPitch() && noteArray[i - 1].getPitch() < noteArray[i].getPitch())
                upDown ++;
            if(noteArray[i - 2].getPitch() < noteArray[i - 1].getPitch() && noteArray[i - 1].getPitch() > noteArray[i].getPitch())
                downUp ++;
        }
        
        f = (((double)falling * 1.6) + ((double)rising * 1.6) + ((double)stable * 0.4) + ((double)upDown * 0.5 + ((double)downUp * 0.5))) / (noteArray.length - 2);
        return f;
    }
    
    private double getBeginningEndingNoteFitness()
    {
        double f = 0;
        
        Note[] noteArray = chromosome.getNoteArray();
        if(noteArray[0].getPitch() == noteArray[noteArray.length - 1].getPitch())
            f = 1;
        if(Math.abs(noteArray[0].getPitch() - noteArray[noteArray.length - 1].getPitch()) == 1)
            f = 0.5;
        if(Math.abs(noteArray[0].getPitch() - noteArray[noteArray.length - 1].getPitch()) == 2)
            f = 0.25;
        
        return f;
    }
    
    private double getOverFifthFitness()
    {
        double f = 0;
        int overFourSteps = 0;
        
        Note[] noteArray = chromosome.getNoteArray();
        for(int i = 1; i < noteArray.length; i++)
        {
            if(Math.abs(noteArray[i].getPitch() - noteArray[i - 1].getPitch()) > 4)
                overFourSteps ++;
        }
        
        f = (((noteArray.length - 1) - (double)overFourSteps)) / (noteArray.length - 1);
        
        return f;
    }
    
    private double getDrasticDurationChangeFitness()
    {
        double f = 0;
        int drasticDurationChange = 0;
        
        //double[] pitchReducedChromosome = getPitchReducedChromosome();
        
        Note[] noteArray = chromosome.getNoteArray();
        for(int i = 1; i < noteArray.length; i++)
        {
            double bigger = noteArray[i].getRhythmValue() >= noteArray[i - 1].getRhythmValue()? noteArray[i].getRhythmValue() : noteArray[i - 1].getRhythmValue();
            double smaller = noteArray[i].getRhythmValue() <= noteArray[i - 1].getRhythmValue()? noteArray[i].getRhythmValue() : noteArray[i - 1].getRhythmValue();
            
            double ratio = bigger / smaller;
            
            if(ratio > 4)
                drasticDurationChange ++;
        }
        
        f = ((chromosome.size() - 1)-(double)drasticDurationChange) / (chromosome.size() - 1);
        
        return f;
    }
}
