package net.beadsproject.touch.pianopush;

import java.awt.Color;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import net.beadsproject.beads.core.AudioContext;
import net.beadsproject.beads.data.Pitch;
import net.beadsproject.beads.data.Sample;
import net.beadsproject.beads.data.SampleManager;
import net.beadsproject.touch.perform.PerformNode;
import net.beadsproject.touch.perform.Performer;
import net.beadsproject.touch.world.Region;
import net.beadsproject.touch.world.RegionMap;
import net.beadsproject.touch.world.World;
import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PImage;

public class PianoPush extends World {
	
	//in this case we'll be making one of these for each contact point
	//what if we wanted to make a common one for all contact points?
	
	//need global tree and local tree, where several local trees share a global tree as root
	//put them in the same package, then give local tree code access to global tree code
	//generate the global tree at init, then generate local trees for each contact that occurs.
	
	//this needs mods: global and local trees need to interact. 
	//local tree responds to message, but forwards message to global tree
	
	PerformNode[] notes;
	PerformNode[] effects;
	AudioContext ac;
	Map<Region, PerformNode> regionToNodeMap;
	Map<Integer, Performer> idToPerformerMap;
	
	private PApplet app;
	int width, height;
	
	public PianoPush(final PApplet app, final AudioContext ac, int w, int h) {
		this.width = w;
		this.height = h;
		this.ac = ac;
		this.app = app;
		notes = new PerformNode[12];
		effects = new PerformNode[4];
		
		regionToNodeMap = new Hashtable<Region, PerformNode>();
		idToPerformerMap = new Hashtable<Integer, Performer>();
		
		//TODO - make map of regions to perform nodes
		
		PerformNode root = new PerformNode() {
			@Override
			public void doConnect(Performer performer) {
//				gsp.setSample(null);
				MyPerformer myp = (MyPerformer)performer;
				myp.masterRateEnv.setValue(1f);
				myp.masterRateEnv.addSegment(1f, 500f);
				myp.masterRateEnv.addSegment(0f, 50f);
				myp.gsp.setRateEnvelope(myp.masterRateEnv);
				myp.gainEnv.setValue(1f);
				myp.masterPitch.setValue(1f);
				myp.pitchModAmp.setValue(0f);
				myp.pitchLFOFreq.setValue(0.1f);
				myp.gsp.reTrigger();
			}
			@Override
			public void doDisconnect(Performer performer) {
				//make sure the sample plays out
				MyPerformer myp = (MyPerformer)performer;
				myp.masterRateEnv.addSegment(1f, 50f);
				//that's all
			}
		};
		String noteDir = "../audio/piano";
		Sample[] sampleList = new Sample[12];
		//add notes, once octave at mid register
		for(int i = 0; i < 12; i++) {
			sampleList[i] = SampleManager.sample(noteDir + "/" + "Piano.mf." + Pitch.pitchNames[i] + "5" + ".wav");
		}
		SampleManager.group("piano", sampleList);
		//set up the essential Sample keys
		int index = 0;
		for(final Sample s : SampleManager.getGroup("piano")) {
			PerformNode pn = new PerformNode() {
				public void doConnect(Performer performer) {
					MyPerformer myp = (MyPerformer)performer;
					myp.gsp.setSample(s);
				}
				public void doDisconnect(Performer performer) {
				}
			};
			root.addChild(pn);
			notes[index++] = pn;
		}
		//then set up some effects
		//when you go from sample to effect, the effect applies to the existing sample		
		
		//the effects
		PerformNode fxNode = null;
		index = 0;
		
		//FX 1
		fxNode = new PerformNode() {
			public void doConnect(Performer performer) {
				MyPerformer myp = (MyPerformer)performer;
				myp.pitchModAmp.addSegment(0.1f, 50f);
				myp.masterPitch.addSegment(1f, 500f);
			}
			public void doDisconnect(Performer performer) {
			}
		};
		root.addChild(fxNode);
		effects[index++] = fxNode;
		
		//FX 2
		fxNode = new PerformNode() {
			public void doConnect(Performer performer) {
				MyPerformer myp = (MyPerformer)performer;
				myp.masterPitch.addSegment(0.5f, 500f);
			}
			public void doDisconnect(Performer performer) {
			}
		};
		root.addChild(fxNode);	
		effects[index++] = fxNode;	
		
		//FX 3
		fxNode = new PerformNode() {
			public void doConnect(Performer performer) {
				MyPerformer myp = (MyPerformer)performer;
				myp.randomness.addSegment(0.1f, 100f);
			}
			public void doDisconnect(Performer performer) {
				MyPerformer myp = (MyPerformer)performer;
				myp.randomness.addSegment(0f, 100f);
			}
		};
		root.addChild(fxNode);	
		effects[index++] = fxNode;
		
		//FX 4
		fxNode = new PerformNode() {
			public void doConnect(Performer performer) {
				MyPerformer myp = (MyPerformer)performer;
				myp.pitchLFOFreq.addSegment(1f, 500f);
			}
			public void doDisconnect(Performer performer) {
				MyPerformer myp = (MyPerformer)performer;
				myp.pitchLFOFreq.addSegment(0.1f, 500f);
			}
		};
		root.addChild(fxNode);	
		effects[index++] = fxNode;
		
		root.computeUniqueValue(0, 1);
		
		layoutPiano();
	}

	public PerformNode[] getNotes() {
		return notes;
	}

	public PerformNode[] getEffects() {
		return effects;
	}

	@Override
	public void newContact(int ID, Region region) {
		MyPerformer newPerformer = new MyPerformer(ac);
		PerformNode pn = regionToNodeMap.get(region);
		Performer.enterNode(pn, newPerformer);
		idToPerformerMap.put(ID, newPerformer);
	}
	
	@Override
	public void contactEnded(int ID) {
		Performer.exitNode(idToPerformerMap.get(ID));
	}

	@Override
	public void contactMoved(int ID, Region dest) {
		Performer.switchNodes(
								regionToNodeMap.get(dest), 
								idToPerformerMap.get(ID));
	}

	@Override
	public List<Region> getRegions() {
		// TODO Auto-generated method stub
		return null;
	}
	
	public RegionMap regionMap;
	public PGraphics pianoImage;
	public PImage displayImage;
	
	public void layoutPiano()
	{
		// Generate regions and visual representation		
		regionMap = new RegionMap(width,height);
		HashMap<Region,Color> regionColours = new HashMap<Region,Color>();
		PGraphics tempImg = app.createGraphics(width,height,app.P2D);		
		tempImg.beginDraw();
		tempImg.background(0);	
		tempImg.noStroke();
		
		pianoImage = app.createGraphics(width,height,app.P2D);
		pianoImage.beginDraw();
		pianoImage.background(0);
		
		// the general layout is:
		/*
		 * +------------+
		 * | fx1 |  fx2 |
		 * +-----+------+
		 * | fx3 |  fx4 |
		 * +-----+------+
		 * |[ ]|[ ]|[ ]|| <-- flats
		 * |c|d|e|f|g|a| ... etc|
		 * +------------+
		 */
		
		PerformNode[] keys = getNotes();
		PerformNode[] fx = getEffects();
		
		// run through and count the number of white keys
		int whiteKeys = Pitch.major.length;		
		
		// divide the width by that amount
		float dWhiteKey = (float)width/whiteKeys;
		
		// we have numFxRows rows of effects
		int numFxRows = (fx.length+1)/2;
		
		// hence we have
		float dRow = (float)height / (numFxRows + 1);
		
		// layout the regions and map from region to perform Node
		Region idMap[] = new Region[keys.length+fx.length]; 
		
		int index = 0;
		
		// layout fx first
		int row = 0, col = 0;
		for(PerformNode effect: fx)
		{
			RegionMap.PSetFill(tempImg,index);			
			tempImg.rect(col*(width/2), row*dRow, width/2, dRow);
			idMap[index] = new Region();
			regionToNodeMap.put(idMap[index], effect);
			regionColours.put(idMap[index], new Color(Color.HSBtoRGB((float) Math.random(), .75f, .75f)));
			
			pianoImage.fill(150,100,100);
			pianoImage.rect(col*(width/2), row*dRow, width/2, dRow);			
			
			col++;
			if (col>=2)
			{
				row++;
				col = 0;
			}
			
			index ++;
		}
		
		// lay out the white keys
		col = 0;
		for(int i: Pitch.major)
		{			
			PerformNode key = keys[i];			
			RegionMap.PSetFill(tempImg,index);			
			tempImg.rect(col*dWhiteKey, height-dRow, dWhiteKey, dRow);
			
			idMap[index] = new Region();
			regionToNodeMap.put(idMap[index], key);
			regionColours.put(idMap[index], new Color(Color.HSBtoRGB((float) Math.random(), .75f, .75f)));
			
			pianoImage.fill(255);
			pianoImage.rect(col*dWhiteKey, height-dRow, dWhiteKey, dRow);
			
			index ++;			
			col++;			
		}
		
		// lay out the black keys
		float fBlack = 0.5f;
		float wBlack = (float) (dWhiteKey*.7);
		float hBlack = fBlack*dRow;
		int lastWhiteKey = 0;
		for(int i=0;i<keys.length;i++)
		{
			int isWhite = Arrays.binarySearch(Pitch.major,i);
			if (isWhite>=0) 
				lastWhiteKey = isWhite;
			else // isBlack 
			{	
				float x = (lastWhiteKey+1)*dWhiteKey - wBlack/2;
				float y = height-dRow;
				
				RegionMap.PSetFill(tempImg,index);
				tempImg.rect(x,y,wBlack,hBlack);
				
				idMap[index] = new Region();
				regionToNodeMap.put(idMap[index], keys[i]);
				regionColours.put(idMap[index], new Color(Color.HSBtoRGB((float) Math.random(), .75f, .75f)));
				
				pianoImage.fill(0);
				pianoImage.rect(x,y,wBlack,hBlack);
				
				index++;	
			}
		}
		
		tempImg.endDraw();
		pianoImage.endDraw();
		
		regionMap.generateFromPGraphics(tempImg,idMap);	
		
		displayImage = regionMap.generatePImage(app, regionColours);
		// displayImage = pianoImage.get();
	}
	
	
}
