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

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;
import jm.JMC;
import jm.music.data.Note;
import jm.music.data.Phrase;
import jm.music.tools.Mod;

/**
 *
 * @author smuRf
 */
public class Mutation {
    private Phrase chromosome;
    private Phrase fragment;
    
    public Mutation(Phrase chromosome)
    {
        this.chromosome = chromosome.copy();
    }
    
    public Phrase doMutate()
    {
        Random rnd = new Random();
        
        int nrMutations = rnd.nextInt(4);
        
        for(int cntMutations = 0; cntMutations < nrMutations; cntMutations++)
        {
            boolean invalid = true;

            int boundaryOne = 0;
            int boundaryTwo = 0;
            while(invalid)
            {
                boundaryOne = rnd.nextInt(chromosome.size());
                boundaryTwo = rnd.nextInt(chromosome.size());

                if(Math.abs(boundaryOne - boundaryTwo) > 6)
                    invalid = false;
            }

            int startIndex = boundaryOne < boundaryTwo ? boundaryOne : boundaryTwo;
            int endIndex = boundaryOne > boundaryTwo ? boundaryOne : boundaryTwo;

            fragment = new Phrase("fragment");
            for(int i = startIndex; i < endIndex; i++)
                fragment.add(chromosome.getNote(i).copy());

            double prop = rnd.nextDouble();

            if(prop <= 0.1)
                transpose(fragment);
            if(prop > 0.1 && prop <= 0.2)
                shufflePitches(fragment);
            if(prop > 0.2 && prop <= 0.3)
                changeFewPitches(fragment);
            if(prop > 0.3 && prop <= 0.4)
                changeOnePitch(fragment);
            if(prop > 0.4 && prop <= 0.5)
                mutateOneNote(fragment);
            if(prop > 0.5 && prop <= 0.6)
                sortAscending(fragment);
            if(prop > 0.6 && prop <= 0.7)
                sortDescending(fragment);
            if(prop > 0.7 && prop <= 0.8)
                reverse(fragment);
            if(prop > 0.8 && prop <= 1.0)
                shuffleRhythmValues(fragment);

            for(int i = 0; i < fragment.size(); i++)
                chromosome.setNote(fragment.getNote(i), i + startIndex);

            prop = rnd.nextDouble();

            if(prop <= 0.33)
                copyFragment(fragment);
            if(prop > 0.66 && prop <= 1.0)
                swapFragment(fragment, startIndex);
        }
        
        return chromosome;
    }
    
    public Phrase getChromosome()
    {
        return chromosome.copy();
    }
    
    private Phrase transpose(Phrase fragment)
    {
        Random rnd = new Random();
        
        int offset = rnd.nextInt(3 - 1) + 1;
        Mod.transpose(fragment, offset);
        
        return fragment;
    }
    
    private Phrase shufflePitches(Phrase fragment)
    {
        Mod.shuffle(fragment);
        
        return fragment;
    }
    
    private Phrase changeFewPitches(Phrase fragment)
    {
        Random rnd = new Random();
        
        int numberToChange = rnd.nextInt(fragment.size());
        
        for(int i = 0; i < numberToChange; i++)
        {
            int index = rnd.nextInt(fragment.size());
            Note note = fragment.getNote(index);
            
            boolean up = rnd.nextDouble() < 0.5 ? true : false;
            
            if(up)
            {
                do
                {
                    note.setPitch(note.getPitch() + 1);
                }while(!note.isNatural());
            }
            else
            {
                do
                {
                    note.setPitch(note.getPitch() - 1);
                }while(!note.isNatural());
            }
            
            fragment.setNote(note, index);
        }
        
        return fragment;
    }
    
    private Phrase changeOnePitch(Phrase fragment)
    {
        Random rnd = new Random();
        
        int index = rnd.nextInt(fragment.size());
        Note note = fragment.getNote(index);

        boolean up = rnd.nextDouble() < 0.5 ? true : false;

        if(up)
        {
            do
            {
                note.setPitch(note.getPitch() + 1);
            }while(!note.isNatural());
        }
        else
        {
            do
            {
                note.setPitch(note.getPitch() - 1);
            }while(!note.isNatural());
        }
        
        fragment.setNote(note, index);
        
        return fragment;
    }
    
    private Phrase mutateOneNote(Phrase fragment)
    {
        Mod.mutate(fragment, 1, JMC.CHROMATIC_SCALE);
        
        return fragment;
    }
    
    private Phrase sortAscending(Phrase fragment)
    {
        int len = fragment.size();
        int j = 0;
        Note tmp;
        
        for(int i=0; i < len; i++)
        {
            j = i;
            for(int k = i; k < len; k++)
            {
                if(fragment.getNote(j).getPitch() > fragment.getNote(k).getPitch())
                {
                    j = k;
                }
            }
            tmp = fragment.getNote(i);
            fragment.setNote(fragment.getNote(j), i);
            fragment.setNote(tmp, j);
        }
        return fragment;
    }
    
    private Phrase sortDescending(Phrase fragment)
    {
        int len = fragment.size();
        int j = 0;
        Note tmp;
        
        for(int i=0; i < len; i++)
        {
            j = i;
            for(int k = i; k < len; k++)
            {
                if(fragment.getNote(j).getPitch() < fragment.getNote(k).getPitch())
                {
                    j = k;
                }
            }
            tmp = fragment.getNote(i);
            fragment.setNote(fragment.getNote(j), i);
            fragment.setNote(tmp, j);
        }
        return fragment;
    }
    
    private Phrase reverse(Phrase fragment)
    {
        Mod.inversion(fragment);
        return fragment;
    }
    
    private Phrase shuffleRhythmValues(Phrase fragment)
    {
        Random rnd = new Random();
        ArrayList<Double> newOrderedRhythmValues = new ArrayList();
        HashSet<Integer> usedIndizees = new HashSet();
        
        while(usedIndizees.size() < fragment.size())
        {
            int index = rnd.nextInt(fragment.size());
            
            if(!usedIndizees.contains(index))
            {
                usedIndizees.add(index);
                newOrderedRhythmValues.add(fragment.getNote(index).getRhythmValue());
            }
        }
        
        for(int i = 0; i < fragment.size(); i++)
        {
            Note note = fragment.getNote(i);
            note.setRhythmValue(newOrderedRhythmValues.get(i));
            fragment.setNote(note, i);
        }
        
        return fragment;
    }
    
    private void copyFragment(Phrase fragment)
    {
        Random rnd = new Random();
        
        int index = rnd.nextInt(chromosome.size() - fragment.size());
        
        for(int i = index; i < (index + fragment.size()); i++)
            chromosome.setNote(fragment.getNote(i - index), i);
    }
    
    public static Phrase mixWithMotif(Phrase base, Phrase motif)
    {
        Random rnd = new Random();
        
        int index = rnd.nextInt(base.size() - motif.size());
        
        for(int i = index; i < (index + motif.size()); i++)
            base.setNote(motif.getNote(i - index), i);
        
        return base.copy();
    }
    
    private void swapFragment(Phrase fragment, int fragmentStartIndex)
    {
        Phrase temp = new Phrase("temp");
        Random rnd = new Random();
        
        int index = rnd.nextInt(chromosome.size() - fragment.size());
        
        for(int i = index; i < (index + fragment.size()); i++)
            temp.add(chromosome.getNote(i).copy());
            
        for(int i = index; i < (index + fragment.size()); i++)
            chromosome.setNote(fragment.getNote(i - index), i);
            
        for(int i = fragmentStartIndex; i < (fragmentStartIndex + fragment.size()); i++)
            chromosome.setNote(temp.getNote(i - fragmentStartIndex), i);
    }
}
