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

import data.INote;
import data.IPhrase;
import data.IScore;
import genetic.CrossOver;
import genetic.FitnessFunction;
import genetic.Selection;
import gui.ComposeProgress;
import gui.ComposingDialog;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Random;
import jm.JMC;
import jm.music.data.Note;
import jm.music.data.Part;
import jm.music.data.Phrase;
import jm.music.data.Score;
import jm.music.tools.Mod;
import midi.MIDIAnalyser;

/**
 *
 * @author smuRf
 */
public class GeneticComposer extends Composer implements Runnable{
    private int tempo;
    private Phrase[] motifs;
    private ComposeProgress progress = null;
    
    private static int POPULATION_SIZE = 500;
    private static int NUMBER_BARS = 15;
    private static int PITCH_RANGE = 2;
    private static double SHORTEST_RHYTHM_VALUE = 0.5;
    private static int LOWEST_NOTE = 48;
    private static double HOLD_THRESHOLD = 0.8;
    private static int TOUR_SIZE = 4;
    private static double MUTATION_PROPABILITY = 0.1;
    private static double MOTIF_MIX_PROPABILITY = 0.1;
    private static int NUMBER_ITERATIONS = 300;
    
    public GeneticComposer(ComposingDialog parent, ArrayList<IPhrase> iPhrases, int tempo)
    {
        this.parent = parent;
        this.tempo = tempo;
        
        POPULATION_SIZE = parent.getInitialPopulationSize();
        NUMBER_BARS = parent.getNumberOfBars();
        PITCH_RANGE = parent.getInitialPitchRange();
        SHORTEST_RHYTHM_VALUE = parent.getShortestRhythmValue();
        LOWEST_NOTE = parent.getLowestNote();
        HOLD_THRESHOLD = parent.getInitialHoldPorpability();
        TOUR_SIZE = parent.getTourSize();
        MUTATION_PROPABILITY = parent.getMutationPropability();
        MOTIF_MIX_PROPABILITY = parent.getMotifMixPropability();
        NUMBER_ITERATIONS = parent.getNumberOfIterations();
        
        IPhrase p = iPhrases.get(iPhrases.size() - 1);
        motifs = new Phrase[iPhrases.size()];
        
        int cntMotifs = 0;
        for(IPhrase iPhrase:iPhrases)
        {
            Phrase motif = new Phrase();
            for(INote iNote:iPhrase.getINotes())
                motif.addNote(new Note(iNote.getPitch(), iNote.getRhythmValue()));
            
            int normalizedPitch = (LOWEST_NOTE - motif.getLowestPitch());
            Mod.transpose(motif, normalizedPitch);
            
            boolean notCMajor;
            do
            {
                notCMajor = false;
                for(Note note:motif.getNoteArray())
                {
                    if(!note.isNatural())
                    {
                        notCMajor = true;
                        break;
                    }
                }
                
                if(notCMajor)
                    Mod.transpose(motif, 1);
            }while(notCMajor);
            
            motifs[cntMotifs] = motif;
            cntMotifs ++;
        }
    }
    
    public Phrase[] initializePopulationMotifs()
    {
        Phrase[] chromosomeArray = new Phrase[POPULATION_SIZE];
        
        double finalDuration = NUMBER_BARS * 4;
        Random rnd = new Random();

        for(int cntChromosome = 0; cntChromosome < POPULATION_SIZE; cntChromosome++)
        {
            Phrase chromosome = new Phrase("chromosome_"+(cntChromosome+1));
            
            while(chromosome.getBeatLength() < finalDuration)
            {
                int index = rnd.nextInt(motifs.length);

                double rest = 0;
                if(chromosome.getBeatLength() + motifs[index].getBeatLength() > finalDuration)
                    rest = finalDuration - chromosome.getBeatLength();
                else 
                {
                    if(motifs[index].getBeatLength() % 4 != 0)
                    {
                        double beatLength = motifs[index].getBeatLength();
                        int divide = (int) (motifs[index].getBeatLength() / 4);
                        rest = beatLength - divide * 4;
                        rest = 4.0 - rest;
                    }
                    
                    for(int i = 0; i < motifs[index].getSize(); i++)
                        chromosome.add(motifs[index].getNote(i).copy());
                }

                while(rest > 0)
                {
                    Note note = chromosome.getNote(chromosome.length() - 1).copy();
                    
                    double prop = rnd.nextDouble();
                    
                    if(prop < 0.5)
                    {
                        do
                        {
                            note.setPitch(note.getPitch() + 1);
                        }while(!note.isNatural());
                    }
                    if(prop >= 0.5)
                    {
                        do
                        {
                            note.setPitch(note.getPitch() - 1);
                        }while(!note.isNatural());
                    }
                    
                    if(rest - (int)(rest / 10 * 10) == 0.5)
                    {
                        note.setRhythmValue(0.5);
                        rest -= 0.5;
                    }
                    else if(rest - (int)(rest / 10 * 10) == 0.25 || rest - (int)(rest / 10 * 10) == 0.75)
                    {
                        note.setRhythmValue(0.25);
                        rest -= 0.25;
                    }
                    else if(rest - (int)(rest / 10 * 10) == 0.125 || rest - (int)(rest / 10 * 10) == 0.375 || rest - (int)(rest / 10 * 10) == 0.625 || rest - (int)(rest / 10 * 10) == 0.875)
                    {
                        note.setRhythmValue(0.125);
                        rest -= 0.125;
                    }
                    else if(rest - (int)(rest / 10 * 10) == 0.0)
                    {
                        note.setRhythmValue(1.0);
                        rest -= 1.0;
                    }
                    
                    chromosome.addNote(note);
                }
            }
            chromosomeArray[cntChromosome] = chromosome;
        }
        
        return chromosomeArray;
    }
    
    //OBSOLETE
    public Phrase[] initializePopulationRandom()
    {
        Phrase[] chromosomeArray = new Phrase[POPULATION_SIZE];
        
        double finalDuration = NUMBER_BARS * 4;
        Random rnd = new Random();

        for(int cntChromosome = 0; cntChromosome < POPULATION_SIZE; cntChromosome++)
        {
            double actualDuration = 0;
            double barDuration = 4;

            Phrase chromosome = new Phrase("chromosome_"+(cntChromosome+1));
            while(actualDuration < finalDuration)
            {
                double noteDuration = 0;
                int pitch = LOWEST_NOTE + rnd.nextInt(PITCH_RANGE * 12 - 1) + 1; 
                if(new Note(pitch, JMC.QUARTER_NOTE).isSharp())
                    pitch --;
                if(new Note(pitch, JMC.QUARTER_NOTE).isFlat())
                    pitch ++;
                
                noteDuration = SHORTEST_RHYTHM_VALUE;

                double holdThreshold = HOLD_THRESHOLD;
                while(rnd.nextDouble() < holdThreshold && noteDuration < 4.0 && barDuration > noteDuration)
                {
                    noteDuration *= 2.0;
                    holdThreshold /= 2.0;
                }
                
                if(barDuration < noteDuration)
                    noteDuration /= 2.0;
                
                chromosome.add(new Note(pitch, noteDuration));
                
                barDuration -= noteDuration;
                actualDuration += noteDuration;
                
                if(barDuration == 0)
                    barDuration = 4;
            }
            chromosomeArray[cntChromosome] = chromosome;
        }
        return chromosomeArray;
    }
    
    public void compose()
    {
        new Thread(this).start();
        progress = new ComposeProgress(parent, true, NUMBER_ITERATIONS);
        
        progress.setVisible(true);
    }

    @Override
    public void run() {
        Phrase[] chromosomes;
        if(parent.areMotifsUsedForPopulation())
            chromosomes = initializePopulationMotifs();
        else
            chromosomes = initializePopulationRandom();

        boolean allDead = false;
        double fitness[] = null;
        for(int iteration = 0; iteration < NUMBER_ITERATIONS; iteration++)
        {
            fitness = new double[chromosomes.length];
            double fittest = Double.MIN_VALUE;
            double avgFit = 0;
            for(int i = 0; i < chromosomes.length; i++)
            {
                FitnessFunction f = new FitnessFunction(chromosomes[i], PITCH_RANGE, LOWEST_NOTE);
                fitness[i] = f.getFitnessValue();
                avgFit += fitness[i];
                if(fitness[i] > fittest)
                    fittest = fitness[i];
            }
            
            if(progress != null)
            {
                avgFit /= chromosomes.length;
                progress.setProgress(iteration);
                DecimalFormat roundFitness = new DecimalFormat("#.#####"); 
                progress.setFitness(roundFitness.format(avgFit));
            }
            
            Selection selection = new Selection(chromosomes, fitness, TOUR_SIZE);
            Phrase[] selectedChromosomes = selection.tourSelect();
            
            if(selectedChromosomes.length == 0)
            {
                allDead = true;
                break;
            }
            
            CrossOver crossOver = new CrossOver(selectedChromosomes, motifs, parent.mixInMotifs(), MOTIF_MIX_PROPABILITY, MUTATION_PROPABILITY);
            chromosomes = crossOver.doCrossOver();
        }
        progress.finish();
        progress = null;
        
        if(!allDead)
        {
            double fittest = Double.MIN_VALUE;
            int index = 0;
            for(int i = 0; i < fitness.length; i++)
            {
                if(fitness[i] > fittest)
                {
                    fittest = fitness[i];
                    index = i;
                }
            }
            
            System.out.println(fittest);
            
            Phrase result = chromosomes[index].copy();
            Phrase answer = createAnswerPhrase(result);
            answer.setMute(parent.getAnswerPhraseMute());
            Phrase counter = createCounterPhrase(result);
            counter.setMute(parent.getCounterPhraseMute());
            
            for(Note n:result.getNoteArray())
            {
                n.setRhythmValue(n.getRhythmValue() * 480);
                n.setDynamic(100);
            }
            
            for(Note n:answer.getNoteArray())
            {
                n.setRhythmValue(n.getRhythmValue() * 480);
                n.setDynamic(60);
            }
            
            for(Note n:counter.getNoteArray())
            {
                n.setRhythmValue(n.getRhythmValue() * 480);
                n.setDynamic(60);
            }
            
            Phrase drums = createDrumPhrase(result, parent.getDrumPhraseInstrument());
            drums.setMute(parent.getDrumPhraseMute());
            for(Note n:drums.getNoteArray())
            {
                n.setRhythmValue(n.getRhythmValue() * 480);
                n.setDynamic(80);
            }
            
            score = new Score(tempo);
            score.setPPQN(480);
            Part partOne = new Part(parent.getPhraseInstrument(), 0);
            Part partTwo = new Part(parent.getAnswerPhraseInstrument(), 1);
            Part partThree = new Part(parent.getCounterPhraseInstrument(), 2);
            Part partFour = new Part(0, 9);
            partOne.add(result);
            partTwo.add(answer);
            partThree.add(counter);
            partFour.add(drums);
            score.add(partOne);
            score.add(partTwo);
            score.add(partThree);
            score.add(partFour);
            
            MIDIAnalyser analyser = new MIDIAnalyser();
            IScore iScore = analyser.analyseTask(score);
            
            parent.composerFinished(iScore);
        }
        else
            parent.composerFinished(null);
    }
}
