package main;

import java.awt.Color;

import com.softsynth.jsyn.AddUnit;
import com.softsynth.jsyn.DivideUnit;
import com.softsynth.jsyn.EnvelopePlayer;
import com.softsynth.jsyn.EqualTemperedTuning;
import com.softsynth.jsyn.ExponentialLag;
import com.softsynth.jsyn.LineOut;
import com.softsynth.jsyn.MultiplyUnit;
import com.softsynth.jsyn.SynthEnvelope;
import com.softsynth.jsyn.Synth;
import com.softsynth.jsyn.SynthMixer;

public class Glissandi extends Thread{
	
	WaveForm[] gliss;
	AddUnit[] num;
	DivideUnit[] ratio;
	MultiplyUnit[] freq;
	SynthEnvelope env;
	MultiplyUnit denMult;
	AddUnit den;
	EnvelopePlayer envPlayer;
	boolean isOn = false;
	double fund;
	GrainStream grainStream[];
	LineOut  lineOut;
	SynthMixer glissMixer;
	SynthMixer grainMixer;
	ExponentialLag grainFader;
	MultiplyUnit grainMult;
	ExponentialLag glissFader;
	MultiplyUnit glissMult;
	//MultiplyUnit glissMultForCurve;
	int currentTick;
	
	public Glissandi(){
	}
	
	public void init(GrainStream[] gS, int gMin, int cT, int nG){
		
		currentTick = cT;
		Rise.MASTER_MIXER = new SynthMixer(3,1);
		glissMixer = new SynthMixer(Rise.VOICES,1);
		grainMixer = new SynthMixer(nG,1);
		Rise.CLICK_MIXER = new SynthMixer(10,1);
		lineOut = new LineOut();
		Rise.MASTER_MULT = new MultiplyUnit();
		Rise.MASTER_FADER = new ExponentialLag();
		grainMult = new MultiplyUnit();
		grainFader = new ExponentialLag();
		glissMult = new MultiplyUnit();
		glissFader = new ExponentialLag();
		Rise.CLICK_MULT = new MultiplyUnit();
		Rise.CLICK_FADER = new ExponentialLag();
		
		//System.out.println(Rise.FIRST_NUM + " " + Rise.VOICES);
		
		gliss = new WaveForm[Rise.VOICES];
		num = new AddUnit[Rise.VOICES];
		ratio = new DivideUnit[Rise.VOICES];
		freq = new MultiplyUnit[Rise.VOICES];
		
		fund = EqualTemperedTuning.getMIDIFrequency(24+Rise.TRANSPOSE_SCORE+Rise.TRANSPOSE_SOUND);
		//System.out.println(Rise.TOTAL_DUR);
		
		//Make sure this is ok
		envPlayer = new EnvelopePlayer();
		double[] envData = new double[302];
		double playDuration = Rise.TOTAL_DUR - Rise.START_TIME;
		double startEnvPosition = Rise.START_TIME/Rise.TOTAL_DUR;
		double envLength = 1 - startEnvPosition;
		
		envData[0] = 0;
		envData[1] = startEnvPosition;
		for (int i = 1; i < envData.length/2; i++){
			envData[i * 2] = playDuration/((envData.length-2)/2);
			envData[i * 2 + 1] = startEnvPosition + (envLength/ ((envData.length-2)/2)) * (i);
		}
		//System.out.println(envData[1] + " " + envData[envData.length-1]);
		env = new SynthEnvelope(envData);
		
		denMult = new MultiplyUnit();
		denMult.inputA.connect(envPlayer.output);
		denMult.inputB.set(-1 * (Rise.VOICES - 1));

		den = new AddUnit();
		den.inputA.set(Rise.FIRST_NUM);
		den.inputB.connect(denMult.output);
		
		int advanceTime = (int) (currentTick + 4 * Synth.getTickRate());
		
		//int[] cuePoints = new int[Rise.VOICES * 14];
		
		for (int i = 0; i < Rise.VOICES; i++){
			
			//System.out.println(i);
					
			gliss[i] = new WaveForm();
			num[i] = new AddUnit();
			ratio[i] = new DivideUnit();
			freq[i] = new MultiplyUnit();

			double spectrum = (Rise.MAX_SPECTRUM - Rise.MIN_SPECTRUM)/2. + Rise.MIN_SPECTRUM;
			gliss[i].spectrum.set(spectrum);

			num[i].inputA.set(i);
			num[i].inputB.connect(den.output);
			
			ratio[i].inputA.connect(num[i].output);
			ratio[i].inputB.connect(den.output);

			freq[i].inputA.connect(ratio[i].output);
			freq[i].inputB.set(fund);

			freq[i].output.connect(gliss[i].frequency);
			
			//gliss[i].envRate.set(1); // figure out this number
			
			
			int startTick = (int) (advanceTime + (((Rise.TOTAL_DUR/(Rise.VOICES-1.) * i) - Rise.START_TIME) * Synth.getTickRate()));
			double startPitch = fund * Rise.FIRST_NUM/(Rise.FIRST_NUM-i);
			//System.out.println("test " + startTick + " " + Synth.getTickCount());
			//System.out.println("show " + ((Rise.TOTAL_DUR/(Rise.VOICES-1.) * i) - Rise.START_TIME));
			
			//double st = (Rise.TOTAL_DUR/(Rise.VOICES-1.) * i);
			
			double startAmp = (((1/(Rise.VOICES-1.)) * i) * (Rise.GLISS_AMP_END - Rise.GLISS_AMP_START)) + Rise.GLISS_AMP_START;
			
			envData = new double[6];
			
			envData[0] = 1;
			envData[1] = startAmp;
			envData[2] = Rise.TOTAL_DUR - ((1/(Rise.VOICES-1.)) * i) - 1;
			envData[3] = Rise.GLISS_AMP_END;
			envData[4] = Rise.FADE_DUR + Math.random()*5;
			envData[5] = 0;
			
			if (Rise.START_TIME > (Rise.TOTAL_DUR/(Rise.VOICES-1.) * i)){
				
				startAmp = (Rise.START_TIME/Rise.TOTAL_DUR) * (Rise.GLISS_AMP_END - Rise.GLISS_AMP_START) + Rise.GLISS_AMP_START;
				
				envData[0] = 1;
				envData[1] = startAmp;
				envData[2] = Rise.TOTAL_DUR - Rise.START_TIME - 1;
				envData[3] = Rise.GLISS_AMP_END;
				envData[4] = Rise.FADE_DUR + Math.random()*5;
				envData[5] = 0;
			}
			
			if (i == Rise.VOICES-1){
				envData = new double[4];
				envData[0] = 1;
				envData[1] = Rise.GLISS_AMP_END;
				envData[2] = Rise.FADE_DUR + Math.random()*5;
				envData[3] = 0;
			}
			
//			for (int j = 0; j < envData.length; j++) {
//				System.out.println(i + " " + envData[j]);
//			}
					
			gliss[i].glissOn(startTick, startPitch, 1 /*(1./Rise.VOICES)*/, envData );
			
//			envData = new double[4];
//			
//			envData[0] = 0;
//			envData[1] = startAmp;
//			envData[2] = Rise.TOTAL_DUR - ((1/(Rise.VOICES-1.)) * i) - 1;
//			envData[3] = 1;
//			
//			gliss[i].glissSustain((int) (startTick + Synth.getTickRate()), envData);
			
//			for (int j = 0; j < 14; j++){
//				cuePoints[14 * i + j] = (int) (startTick - 3 * Synth.getTickRate() + Synth.getTickRate()/2. * j);
//			}
				
			//System.out.println("test " + startTick + " " + endTick + " " + Synth.getTickCount() + " " + ((1./ (Rise.VOICES-1)) * i));
			
			//make this have jitter??
			//gliss[i].glissOff(endTick, (.5 / (Rise.FADE_DUR + Math.random()*5)));
			
			num[i].start(startTick);
			ratio[i].start(startTick);
			freq[i].start(startTick);
			
			glissMixer.connectInput(i, gliss[i].output, 0);
			glissMixer.setGain(i, 0, (1./Rise.VOICES));
		}
		
		//c.init(cuePoints);
		
		denMult.start(advanceTime);
		den.start(advanceTime);
		//System.out.println(envPlayer.getSynthContext().getTickCount());
		
		envPlayer.start(advanceTime);
		
		envPlayer.envelopePort.clear(advanceTime);
		envPlayer.envelopePort.queue(advanceTime,env,0,env.getNumFrames());
		
		grainStream = gS;
		
		for (int i = 0; i < nG; i++){
			grainMixer.connectInput(i, grainStream[i].grain.output, 0);
			grainMixer.setGain(i, 0, 1);
			int grainStartTick = (int) (advanceTime + Rise.START_TIME * Synth.getTickRate());
			
			if (i > gMin){
				grainStartTick = (int) (advanceTime + (((Rise.TOTAL_DUR/(nG - gMin) * (i - gMin)) - Rise.START_TIME) * Synth.getTickRate()));
			}
			
			//System.out.println("tick " + grainStartTick);
			grainStream[i].setStartTick(grainStartTick + (int) (Synth.getTickRate() * Math.random() * 5));
			int endTick = (int) (advanceTime + ((Rise.TOTAL_DUR - Rise.START_TIME) * Synth.getTickRate()));
			grainStream[i].setEndTick(endTick);
		}
		
		grainMult.inputA.connect(grainFader.output);
		grainMult.inputB.connect(grainMixer.getOutput(0));
		
		glissMult.inputA.connect(glissFader.output);
		glissMult.inputB.connect(glissMixer.getOutput(0));
		
		for (int count = 0; count < 10; count++){
			Rise.CLICK_MIXER.setGain(count, 0, 1);
		}
		
		Rise.CLICK_MULT.inputA.connect(Rise.CLICK_FADER.output);
		Rise.CLICK_MULT.inputB.connect(Rise.CLICK_MIXER.getOutput(0));
		
		Rise.MASTER_MIXER.connectInput(0, glissMult.output, 0);
		Rise.MASTER_MIXER.setGain(0, 0, 1);
		
		Rise.MASTER_MIXER.connectInput(1, grainMult.output, 0);
		Rise.MASTER_MIXER.setGain(1, 0, 1);
		
		Rise.MASTER_MIXER.connectInput(2, Rise.CLICK_MULT.output, 0);
		Rise.MASTER_MIXER.setGain(2, 0, 1);
		
		Rise.MASTER_MULT.inputA.connect(Rise.MASTER_FADER.output);
		Rise.MASTER_MULT.inputB.connect(Rise.MASTER_MIXER.getOutput(0));
		
		lineOut.input.connect(0, Rise.MASTER_MULT.output, 0);
		lineOut.input.connect(1, Rise.MASTER_MULT.output, 0);
		
		lineOut.start(advanceTime);
		glissMixer.start(advanceTime);
		grainMixer.start(advanceTime);
		
		Rise.MASTER_MIXER.start(advanceTime);
		Rise.MASTER_MULT.start(advanceTime);
		Rise.MASTER_FADER.start(advanceTime);
		Rise.CLICK_MIXER.start(advanceTime);
		Rise.CLICK_MULT.start(advanceTime);
		Rise.CLICK_FADER.start(advanceTime);
		
		grainMult.start(advanceTime);
		grainFader.start(advanceTime);
		glissMult.start(advanceTime);
		glissFader.start(advanceTime);
		
		for (int i = 0; i < nG; i++){
			grainStream[i].setTimer(envPlayer);
		}
		
		isOn = true;
	}
	
	public void setGlissFader(double a){
		if (Rise.IS_ENGINE_ON == true) {
			glissFader.input.set(a);
		}
	}
	
	public void setGrainFader(double a){
		if (Rise.IS_ENGINE_ON == true) {
			grainFader.input.set(a);
		}
	}

	public synchronized void run() {
		
		try {
			//System.out.println("waiting");
			this.wait(0);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		Synth.sleepUntilTick( (int) (currentTick + 1 * Synth.getTickRate()) );
		
		Rise.CUE_PANEL.color = Color.YELLOW;
		Rise.CUE_PANEL.repaint();
		
		Synth.sleepUntilTick( (int) (currentTick + 2 * Synth.getTickRate()) );
		
		Rise.CUE_PANEL.color = Color.GREEN;
		Rise.CUE_PANEL.repaint();
		
		Synth.sleepUntilTick( (int) (currentTick + 3 * Synth.getTickRate()) );
		
		Rise.CUE_PANEL.color = Color.BLACK;
		Rise.CUE_PANEL.repaint();
		
		int startTime = (int) (currentTick + 3 * Synth.getTickRate()); ///*Synth.getTickCount()*/ currentTick + (int) Synth.getTickRate();
		int duration = (int) (Synth.getTickRate() / 5); // Resolution should be pixles in view
		
		int advanceTime = (int) (Synth.getTickRate() * 0.05); // half second advance
		Synth.sleepUntilTick( startTime  - advanceTime );  // Wake up early!
		int nextTime = startTime;
		
		while(true) 
		{
			if (Rise.IS_ENGINE_ON == true) {
				//System.out.println(envPlayer.output.get());
				
				Rise.CUE_PANEL.envPos = envPlayer.output.get();
				Rise.CUE_PANEL.repaint();
				
				Rise.SCROLLBAR.xPos = envPlayer.output.get() * Rise.SCROLLBAR_WIDTH;
				if (envPlayer.output.get() == 0){
					Rise.SCROLLBAR.xPos = (Rise.START_TIME/Rise.TOTAL_DUR) * Rise.SCROLLBAR_WIDTH;
				}
				
				Rise.SCROLLBAR.repaint();
				nextTime += duration;    // Advance nextTime by fixed amount.
				Synth.sleepUntilTick( nextTime - advanceTime );  // Wake up early!
			}
			else {
				try {
					//System.out.println("waiting");
					this.wait(0);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public void kill() {
		isOn = false;
		for (int i = 0; i < Rise.VOICES; i++){
			gliss[i].glissOff(0, 1);
			num[i].stop();
			ratio[i].stop();
			freq[i].stop();
		}
		
		denMult.stop();
		den.stop();
		envPlayer.stop();
		
		glissMixer.stop(0);
		grainMixer.stop(0);
		
		Rise.MASTER_FADER.stop(0);
		Rise.MASTER_MULT.stop(0);
		Rise.MASTER_MIXER.stop(0);
		
		Rise.CLICK_FADER.stop(0);
		Rise.CLICK_MULT.stop(0);
		Rise.CLICK_MIXER.stop(0);
		
		grainMult.stop(0);
		grainFader.stop(0);
		glissMult.stop(0);
		glissFader.stop(0);
		lineOut.stop(0);
	}
}
