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

import data.INoteAmount;
import data.MultiBeat;
import data.NoteData;
import java.util.ArrayList;

/**
 *
 * @author Tom
 */
public class PolyphonFitnessFunction {
    private ArrayList<INoteAmount> alina;
    
    /**
     * Konstruktor
     * @param alina die Noten und die Haeufigkeit ihres Auftretens in der Kategorie
     */
    public PolyphonFitnessFunction(ArrayList<INoteAmount> alina) {
        this.alina = alina;
    }
    
    /**
     * berechnet die Guete eines Takts nach mehreren Attributen
     * @param mb ein Takt
     * @return Guetwert (Fitnesswert)
     */
    public double getFitnessValue(MultiBeat mb)
    {
        double[] fitnessObjectives = new double[7];
        double fitness = 0;
        
        fitnessObjectives[0] = getNoteRelationshipFitness(mb);
        fitnessObjectives[1] = getNoteDirectionFitness(mb);
        fitnessObjectives[2] = getBeginningEndingNoteFitness(mb);
        fitnessObjectives[3] = getDrasticDurationChangeFitness(mb);
        fitnessObjectives[4] = getLastNoteLongerFitness(mb);
        fitnessObjectives[5] = getCMajorOrAMinorFitness(mb);
        fitnessObjectives[6] = getInterestingNoteFitness(mb);
        
        
        for(double f:fitnessObjectives){
            fitness += f;
        }
        
        return (fitness / fitnessObjectives.length);
    }
    
    /**
     * sucht im Takt nach Noten, welche in der Kategorie haeufig vertreten sind
     * besitzt der Takt Noten, welche einen Anteil von >= 15% aufweisen ist dies sehr gut
     * bei einem Anteil von >= 10% gut und darunter keinen Einfluss mehr
     * @param mb ein Takt
     * @return Guetewert (Fitnesswert)
     */
    private double getInterestingNoteFitness(MultiBeat mb){
        ArrayList<String> sBest = new ArrayList<>();
        ArrayList<String> sGood = new ArrayList<>();
        NoteData[][] nd = mb.getBeat();
        int best = 0, good = 0, ok = 0, RestPass = 0;
        
        for (INoteAmount ina : alina){
            if (ina.getAmount() >= 15){
                sBest.add(ina.getName());
            } else if (ina.getAmount() >= 10){
                sGood.add(ina.getName());
            }
        }
        
        for (int i=0; i<nd.length; i++){
            for (int j=0; j<nd[i].length; j++){
                if (nd[i][j].getPitch() != Integer.MIN_VALUE){
                    if (sBest.contains(nd[i][j].getNote())){
                        best++;
                    } else if (sGood.contains(nd[i][j].getNote())){
                        good++;
                    } else {
                        ok++;
                    }
                } else {
                    RestPass++;
                }
            }
        }
        
        double f = (((double)best * 1.4) 
                + ((double)good * 1.0) 
                + ((double)ok * 0.6)) 
                / ((nd.length * nd[0].length) - RestPass);
        return f;
    }
    
    /**
     * finden im Takt drastische Tempowechsel statt,
     * (zB.: von 16tel auf 4tel, 8tel auf ganze, usw.)
     * erhaelt der Takt einen schlechteren Guetewert
     * @param mb ein Takt
     * @return Guetewert (Fitnesswert)
     */
    private double getDrasticDurationChangeFitness(MultiBeat mb)
    {
        int drasticDurationChange = 0;
        int RestPass = 0;
        NoteData[][] ndArr = mb.getBeat();
        
        if (ndArr[0].length > 0){
            for (int j=1; j<ndArr[0].length; j++){
                if (ndArr[0][j-1].getPitch() != Integer.MIN_VALUE && ndArr[0][j].getPitch() != Integer.MIN_VALUE){
                    double dfast = ndArr[0][j-1].getDuration();
                    double dslow = ndArr[0][j].getDuration();
                    if (dfast < dslow){
                        double temp = dfast;
                        dfast = dslow;
                        dslow = temp;
                    }
                    
                    if (Double.compare(dslow, dfast) == 0){ //beide gleich lange Dauer
                        
                    } else if ((Double.compare(dfast, 0.125) == 0 //Zweiundreissigstel
                            || Double.compare(dfast, 0.25) == 0 //Sechtzehntel
                            || Double.compare(dfast, 0.33) == 0 //Triole
                            || Double.compare(dfast, 0.5) == 0) //Achtel
                            && (Double.compare(dslow, 2.0) == 0 //Halbe
                            || Double.compare(dslow, 4.0) == 0)){ //Ganze
                        //von 32, 16, Triole oder Achtel auf Halbe oder Ganze
                        drasticDurationChange++;
                    } else if ((Double.compare(dfast, 0.125) == 0 //Zweiundreissigstel
                            || Double.compare(dfast, 0.25) == 0) //Sechtzehntel
                            && (Double.compare(dslow, 1.0) == 0)){ //Viertel
                        //von 32 oder 16 auf Viertel
                        drasticDurationChange++;
                    } else if ((Double.compare(dfast, 0.125) == 0) //Zweiundreissigstel
                            && (Double.compare(dslow, 0.5) == 0)){ //Achtel
                        //von 32 auf Achtel
                        drasticDurationChange++;
                    } else if ((Double.compare(dfast, 0.5) == 0) //Achtel
                            && (Double.compare(dslow, 4.0) == 0)){ //Ganze
                        //von Achtel auf Ganze
                        drasticDurationChange++;
                    }
                } else {
                    RestPass++;
                }
            }
        }
        
        double f = ((ndArr[0].length - 1 - RestPass)-(double)drasticDurationChange) / (ndArr[0].length - 1 - RestPass);
        
        return f;
    }
    
    /**
     * je aehnlicher die Tonhoehen der ersten Note des Takts und der letzten Note sind,
     * desto besser wirkt sich das auf den Guetewert aus
     * @param mb ein Takt
     * @return Guetewert (Fitnesswert)
     */
    private double getBeginningEndingNoteFitness(MultiBeat mb)
    {
        NoteData[][] ndArr = mb.getBeat();
        
        if (ndArr[0].length > 0){
            int diff = ndArr[0][0].getPitch() - ndArr[0][ndArr[0].length-1].getPitch();
            if (diff == 0){ //gleiche Tonhoehe
                return 1.0;
            } else if (Math.abs(diff) == 1){ //um einen Halbton verschieden
                return 0.5;
            } else if (Math.abs(diff) == 2){ //um einen ganzen Ton verschieden
                return 0.25;
            } else {
                return 0.0;
            }
        }
        return 0.0;
    }
    
    /**
     * bewertet die Kontur eines Takts
     * sind die Noten aufsteigend oder absteigend,
     * wirkt sich das positiver auf den Takt aus,
     * als wenn er gleichbleibend oder schwingend verlaeuft
     * @param mb ein Takt
     * @return Guetewert (Fitnesswert)
     */
    private double getNoteDirectionFitness(MultiBeat mb)
    {
        int falling = 0, rising = 0, stable = 0, RestPass = 0;
        NoteData[][] ndArr = mb.getBeat();
        
        if (ndArr[0].length > 0){
            for (int j=1; j<ndArr[0].length; j++){
                if (ndArr[0][j-1].getPitch() != Integer.MIN_VALUE && ndArr[0][j].getPitch() != Integer.MIN_VALUE){
                    if((ndArr[0][j-1].getPitch() < ndArr[0][j].getPitch())){ //aufsteigend
                        rising++;
                    } else if((ndArr[0][j-1].getPitch() > ndArr[0][j].getPitch())){ //absteigend
                        falling++;
                    } else { //gleich bleibend
                        stable++;
                    }
                } else {
                    RestPass++;
                }
            }
        }
        
        double f;
        if (((Double.compare(rising, 0.0) == 0 && Double.compare(falling, 0.0) != 0) //nur aufsteigend
                || (Double.compare(falling, 0.0) == 0 && Double.compare(rising, 0.0) != 0)) //oder nur absteigend
                && Double.compare(stable, 0.0) == 0){ //und nicht gleichbleibend
            f = (((double)falling * 1.6) 
                + ((double)rising * 1.6) 
                + ((double)stable * 0.4)) 
                / (ndArr[0].length - 1 - RestPass);
        } else { //gleichbleibend oder schwingend
            f = (((double)falling * 0.5) 
                + ((double)rising * 0.5) 
                + ((double)stable * 0.4)) 
                / (ndArr[0].length - 1 - RestPass);
        }
        return f;
    }
    
    /**
     * es werden die Tonhoehenaenderungen aufeinanderfolgender Noten berechnet
     * liegen diese zwischen 1 und 3 Halbtonschritten, wirkt sich dies positiv
     * auf den Guetewert aus
     * @param mb ein Takt
     * @return Guetewert (Fitnesswert)
     */
    private double getNoteRelationshipFitness(MultiBeat mb)
    {
        int same = 0, oneStep = 0, twoSteps = 0, threeSteps = 0, moreSteps = 0, RestPass = 0;
        NoteData[][] ndArr = mb.getBeat();
        
        for (int i=0; i<ndArr.length; i++){
            if (ndArr[i].length > 0){
                for (int j=1; j<ndArr[i].length; j++){
                    if (ndArr[i][j-1].getPitch() != Integer.MIN_VALUE && ndArr[i][j].getPitch() != Integer.MIN_VALUE){
                        if((ndArr[i][j-1].getPitch() - ndArr[i][j].getPitch()) == 0){
                            same++;
                        } else if(Math.abs((ndArr[i][j-1].getPitch() - ndArr[i][j].getPitch())) == 1){
                            oneStep++;
                        } else if(Math.abs((ndArr[i][j-1].getPitch() - ndArr[i][j].getPitch())) == 2){
                            twoSteps++;
                        } else if(Math.abs((ndArr[i][j-1].getPitch() - ndArr[i][j].getPitch())) == 3){
                            threeSteps++;
                        } else if(Math.abs((ndArr[i][j-1].getPitch() - ndArr[i][j].getPitch())) >= 4){
                            moreSteps++;
                        }
                    } else {
                        RestPass++;
                    }
                }
            }
        }
        
        double f = (((double)same * 0.4) 
                + ((double)oneStep * 1.4) 
                + ((double)twoSteps * 1.4) 
                + ((double)threeSteps * 1.0) 
                + ((double)moreSteps * 0.6)) 
                / ((ndArr.length * (ndArr[0].length - 1)) - RestPass);
        return f;
    }
    
    /**
     * je mehr Noten des Takts in C-Dur oder A-Moll sind,
     * desto besser ist sein Guetewert
     * @param mb ein Takt
     * @return Guetewert (Fitnesswert)
     */
    private double getCMajorOrAMinorFitness(MultiBeat mb)
    {
        int cMajor = 0, RestPass = 0;
        NoteData[][] ndArr = mb.getBeat();
        
        for (int i=0; i<ndArr.length; i++){
            for (int j=0; j<ndArr[i].length; j++){
                if (ndArr[i][j].getPitch() != Integer.MIN_VALUE){ //Pausen ignorieren
                    switch (ndArr[i][j].getNote()){
                        case "C":
                            cMajor++;
                            break;
                        case "D":
                            cMajor++;
                            break;
                        case "E":
                            cMajor++;
                            break;
                        case "F":
                            cMajor++;
                            break;
                        case "G":
                            cMajor++;
                            break;
                        case "A":
                            cMajor++;
                            break;
                        case "B":
                            cMajor++;
                            break;
                    }
                } else {
                    RestPass++;
                }
            }
        }
        
        double f = (double)cMajor * 1.1 / ((ndArr.length * ndArr[0].length) - RestPass);
        return f;
    }
    
    /**
     * ist die letzte Note im Takt eine Viertel-, halbe oder ganze Note
     * erfuellt der Takt die Kriterien, sonst nicht
     * @param mb ein Takt
     * @return 1.0 wenn gut, 0.0 wenn schlecht
     */
    private double getLastNoteLongerFitness(MultiBeat mb)
    {
        NoteData[][] ndArr = mb.getBeat();
        
        for (int i=0; i<ndArr.length; i++){
            if (ndArr[0][ndArr[i].length-1].getDuration() >= 1.0){
                return 1.0;
            }
        }
        return 0.0;
    }
}
