package org.monome.pages;

/*
 * Class for creating and manipulating grids of frequencies
 * by John Fisher (of bagger288, www.bagger288.com)
 * made for Pages, by Tom Dinchak
 * 
 */
public class JustPitchSet {
	
	//width and height of pitch grid
	private int gridWidth = 8;
	private int gridHeight = 8;

	/* use this array if the pitch set is in ratios.  
	 * this array is in 3 dimensions [x][y][z].  X and Y are the location in the pitch grid.
	 * [x][y][0] is the numerator and [x][y][1] is the denominator.
	 */
	public int[][][] rLattice = new int[gridWidth][gridHeight][2];
	
	//use this array if the pitch set is in equal temperament
	public int[][] etGrid = new int[gridWidth][gridHeight];    

	//hold the ratio grid numerators and denominators if pitch is set in ratio grid mode
	public int[][][] rGrid = new int[gridWidth][gridHeight][2];
	
	//holds the ratios for calculating the pitch grid, if the grid is in justLattice mode
	public int[][] jLatticeRatios = new int[4][2];
	
	
	//Important parameter:  the base frequency for calculating all the pitches!
	private float baseFrequency = 880.0f;
	
	//intervals for ETLattice
	private int xInterval = 4;  //how much to increase towards the east or decrease towards the west, in semitones
	private int yInterval = 7;  //same, but for north and south, relatively
	
	//number of equal temperament divisions.  used by the getETempFreq function
	private int etTet = 12;
	
	//'origin' for calculating all notes or ratios, as location in the just intonation array
	private int centerX = 7;
	private int centerY = 7;
	
	//minimum values for the numerator and denominator in the just Intonation Grid
	private int minNumerator = 1;
	private int minDenominator = 1;


	/*
	 * Constructor ************************** Constructor *****************
	 */
	public JustPitchSet(){
			//todo: should anything belong here?
	}
	
	/*
	 * diagnostic function that prints the frequencies of all the notes in the equal temperament grid, based
	 * on the baseFrequency
	 */
	public void printETFrequencies(){
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				float freq = getETempFreq(x, y, baseFrequency, etTet);
				System.out.print("   " + freq);
			}
			System.out.println("");
		}
	}
	
	/*
	 * This method builds a grid of equal temperament MIDI notes
	 */
	public void calcETLattice(){
		System.out.println("Calculated Equal Temperament Lattice");
		etGrid[centerX][centerY] = 0;
		
		//next two loops calculate center row
		for(int x=centerX + 1; x<gridWidth; x++){
			etGrid[x][centerY] = etGrid [x-1][centerY] + xInterval;
		}
		for (int i=centerX - 1; i>=0; i--){
			etGrid[i][centerY] = etGrid[i+1][centerY] - xInterval;
		}
		
		//calcuate all rows above center row
		for(int y=centerY - 1; y >= 0; y--){
			for(int x=0; x<gridWidth; x++){
				etGrid[x][y] = etGrid[x][y+1] + yInterval;
			}
		}
		
		//calculate rows below center row
		for(int y=centerY + 1; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				etGrid[x][y] = etGrid[x][y-1] - yInterval;
			}
		}
		//uncomment to print equal temp. pitches
		//print2dArray(etGrid);
	}
	

		/*
		 * This function creates a grid of ratios in a 3 dimensional array, where [x][y] determines the 
		 * position, and [x][y][0] is the numerator of that position, and [x][y][1] is the denominator.   
		 * numeratorX and denominatorX are the numerator and denominator to multiply the ratios by on the X axis
		 * sane for numY and denomY on the Y axis.
		 */
	public void calcJustLattice(){
		System.out.println("Calculated Just Intonation Lattice");
		
		//set center values to 1/1 unity
		rLattice[centerX][centerY][0] = 1;
		rLattice[centerX][centerY][1] = 1;
		
		//next two loops calculate center row
		for(int x = centerX +1; x<gridWidth; x++){
			rLattice[x][centerY][0] = rLattice[x-1][centerY][0] * jLatticeRatios[0][0];
			rLattice[x][centerY][1] = rLattice[x-1][centerY][1] * jLatticeRatios[0][1];
		}
		for(int x = centerX -1; x>=0; x--){
			rLattice[x][centerY][0] = rLattice[x+1][centerY][0] * jLatticeRatios[1][0];
			rLattice[x][centerY][1] = rLattice[x+1][centerY][1] * jLatticeRatios[1][1];
		}
		
		//calculate all rows below center row
		for(int y = centerY+1; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				rLattice[x][y][0] = rLattice[x][y-1][0] * jLatticeRatios[3][0];
				rLattice[x][y][1] = rLattice[x][y-1][1] * jLatticeRatios[3][1];
			}
		}
		//and above center row
		for(int y = centerY-1; y>=0; y--){
			for(int x=0; x<gridWidth; x++){
				rLattice[x][y][0] = rLattice[x][y+1][0] * jLatticeRatios[2][0];
				rLattice[x][y][1] = rLattice[x][y+1][1] * jLatticeRatios[2][1];
			}
		}
		//uncomment to print ratios
		//printRatioArray(rLattice);
	}


	
	/*
	 * This calculates a simple grid of ratios. 
	 * minNumerator is the minimum numerator to start the grid with
	 * minDenominator is the minimum denominator to start the grid with
	 */
	public void calcJustGrid(){
		System.out.println("Calculated just intonation ratio Grid");
		for(int y=0; y<gridWidth; y++){
			for(int x=0; x<gridWidth; x++){
				rGrid[x][y][0] = x + minNumerator;
				rGrid[x][y][1] = y + minDenominator;
			}
		}
		//uncomment to print ratio grid
		//printRatioArray(rGrid);
	}
	
	public void print2dArray(int[][] theArray){
		int[][] twoDGrid = theArray;
		// print array in rectangular form
		for (int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				System.out.print(" " + twoDGrid[x][y]);
			}
			System.out.println("");
		}
	}
	
	
	/*
	 * re-sets the root frequency to a new value based on the root frequency * a frequency ratio, located in an array
	 */
	public void resetBaseWithRatio(int indexX, int indexY, int[][][] rLattice){
		baseFrequency = (rLattice[indexX][indexY][0]/rLattice[indexX][indexY][1]) * baseFrequency;
	}
	
	
	/*
	 *prints an array of ratios which are formatted as [x][y][0] = numerator, [x][y][1] = denominator
	 */
	public void printRatioArray(int[][][] theArray){
		int[][][] threeDGrid = theArray;
		// print array in rectangular form
		for (int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				System.out.print("  " + threeDGrid[x][y][0] + "/" + threeDGrid[x][y][1]);
			}
			System.out.println("");
		}
	}

	
	/*
	 * this function rotates the ET grid clockwise
	 */
	public void rotateETCW(int[][] etGrid){
		int[][] placeHolder = new int[gridWidth][gridHeight];
		for (int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				placeHolder[gridWidth -1 - y][x] = etGrid[x][y];
			}
		}
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				etGrid[x][y] = placeHolder[x][y];
			}
		}
		//print2dArray(etGrid);
	}
	
	/*
	 * this function rotates the ET grid counter clockwise
	 */
	public void rotateETCCW(int[][] etGrid){
		int[][] placeHolder = new int[gridWidth][gridHeight];
		for (int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				placeHolder[y][gridWidth -1 -x] = etGrid[x][y];
			}
		}
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				etGrid[x][y] = placeHolder[x][y];
			}
		}
		//print2dArray(etGrid);
	}
	
	/*
	 * this function flips the ET grid horizontally
	 */
	public void flipETHorizontal(int[][] etGrid){
		int[][] placeHolder = new int[gridWidth][gridHeight];
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				placeHolder[gridWidth -1 -x][y] = etGrid[x][y];
			}
		}
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				etGrid[x][y] = placeHolder[x][y];
			}
		}
		//print2dArray(etGrid);
	}
	
	/*
	 * this function flips the ET grid vertically
	 */
	public void flipETVertical(int[][] etGrid){
		int[][]placeHolder = new int[gridWidth][gridHeight];
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				placeHolder[x][gridHeight -1 -y] = etGrid[x][y];
			}
		}
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				etGrid[x][y] = placeHolder[x][y];
			}
		}
		//print2dArray(etGrid);
	}
	
	/*
	 * rotates a ratio array Clockwise
	 */
	public void rotateJICW(int[][][] rLattice){
		int[][][] placeHolder = new int[gridWidth][gridHeight][2];
		for (int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				placeHolder[gridWidth -1 - y][x][0] = rLattice[x][y][0];
				placeHolder[gridWidth -1 - y][x][1] = rLattice[x][y][1];
			}
		}
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				rLattice[x][y][0] = placeHolder[x][y][0];
				rLattice[x][y][1] = placeHolder[x][y][1];
			}
		}
	}
	
	/*
	 * rotates a ratio array Counter clockwise
	 */
	public void rotateJICCW(int[][][] rLattice){
		int[][][] placeHolder = new int[gridWidth][gridHeight][2];
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridHeight; x++){
				placeHolder[y][gridWidth -1 -x][0] = rLattice[x][y][0];
				placeHolder[y][gridWidth -1 -x][0] = rLattice[x][y][1];
			}
		}
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				rLattice[x][y][0] = placeHolder[x][y][0];
				rLattice[x][y][1] = placeHolder[x][y][1];
			}
		}
	}
	
	/*
	 * flips a ratio array horizontally
	 */
	public void flipJIHorizontal(int[][][] rLattice){
		int[][][] placeHolder = new int[gridWidth][gridHeight][2];
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridHeight; x++){
				placeHolder[gridWidth -1 -x][y][0] = rLattice[x][y][0];
				placeHolder[gridWidth -1 -x][y][1] = rLattice[x][y][1];
			}
		}
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				rLattice[x][y][0] = placeHolder[x][y][0];
				rLattice[x][y][1] = placeHolder[x][y][1];
			}
		}
	}
	
	/*
	 * flips a ratio array vertically
	 */
	public void flipJIVertical(int[][][] rLattice){
		int[][][] placeHolder = new int[gridWidth][gridHeight][2];
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridHeight; x++){
				placeHolder[x][gridHeight -1 -y][0] = rLattice[x][y][0];
				placeHolder[x][gridHeight -1 -y][1] = rLattice[x][y][1];
			}
		}
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				rLattice[x][y][0] = placeHolder[x][y][0];
				rLattice[x][y][1] = placeHolder[x][y][1];
			}
		}
	}
	
	
	/*
	 * adds to the numerator and denominator of all ratios in the grid
	 */
	public void addToRatio(int addNumerator, int addDenominator, int[][][] rLattice){
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				rLattice[x][y][0] += addNumerator;
				rLattice[x][y][1] += addDenominator;
			}
		}
	}
	
	/*
	 * multiplies with the numerator and denominator of all ratios in the grid
	 */
	public void multiplyRatio(int multNumerator, int multDenominator, int [][][]rLattice){
		for(int y=0; y<gridHeight; y++){
			for(int x=0; x<gridWidth; x++){
				rLattice[x][y][0] *= multNumerator;
				rLattice[x][y][0] *= multDenominator;
			}
		}
	}
	
	/*
	 * Prints out all important variables of the JustPitchSet class
	 */
	public void printAllVars(){
		System.out.println("");
		System.out.println("JustPitchSet Variables: -----------------------------");
		System.out.println("Base Frequency: " + baseFrequency);
		System.out.println("Equal Temp X: " + xInterval + " and Y: " + yInterval);
		System.out.println("Grid Size: " + gridWidth + " , " + gridHeight);
		System.out.println("ET Divisions: " + etTet);
		System.out.println("Minimum X: " + minNumerator + " Minimum Y: " + minDenominator);
		System.out.println("Center Position: " + centerX + ", " + centerY);
		System.out.println("Ratio X: " + jLatticeRatios[0][0] + "/" + jLatticeRatios[0][1]);
		System.out.println("Ratio Y: " + jLatticeRatios[1][0] + "/" + jLatticeRatios[1][1]);
	}

	
	/* ****************************** Getters and Setters **********************************/
	/* *************************************************************************************/
	
	public void setXET(int x){
		xInterval = x;
		calcETLattice();
	}
	
	public void setYET(int y){
		yInterval = y;
		calcETLattice();	
	}
	
	public void setBothET(int x, int y){
		xInterval = x;
		yInterval = y;
		calcETLattice();
	}
	
	/*
	 * This function creates a small array holding the values of the ratios used  
	 * to build the array.  each x value if [x][y] holds a ratio in y=1 and y=2 where 
	 * y=1 is the numerator and  y=2 is the denominator
	 */
	public void setRatioArray(int numeratorX, int denominatorX, int numeratorY, int denominatorY){
		jLatticeRatios[0][0] = numeratorX;       // ratio for X axis  (for moving east)
		jLatticeRatios[0][1] = denominatorX;     // ratio for X axis
		jLatticeRatios[1][0] = denominatorX;     // inversion of X axis ratio (for moving west)
		jLatticeRatios[1][1] = numeratorX;       // inversion of X axis ratio
		jLatticeRatios[2][0] = numeratorY;       // ratio for Y axis (for moving north)
		jLatticeRatios[2][1] = denominatorY;     // ratio for Y axis
		jLatticeRatios[3][0] = denominatorY;     // inversion of Y axis ratio (for moving south)
		jLatticeRatios[3][1] = numeratorY;     // inversion of Y axis ratio
			
		//print out the ratios for calculating the lattice
		/*
		System.out.println("X ratio: " + jLatticeRatios[0][0] + "/" + jLatticeRatios[0][1] + " inversion: " + jLatticeRatios[1][0] + "/" + jLatticeRatios[1][1]);
		System.out.println("Y ratio: " + jLatticeRatios[2][0] + "/" + jLatticeRatios[2][1] + " inversion: " + jLatticeRatios[3][0] + "/" + jLatticeRatios[3][1]);
		System.out.println("");
		*/
		
		calcJustLattice();
	}
	
	/*
	 * Same as setRatioArray but for only X
	 */
	public void setXJustLattice(int numerator, int denominator){
		jLatticeRatios[0][0] = numerator;
		jLatticeRatios[0][1] = denominator;
		jLatticeRatios[1][0] = denominator;
		jLatticeRatios[1][1] = numerator;
		calcJustLattice();
	}
	
	/*
	 * Same as setRatioArray but for only Y
	 */
	public void setYJustLattice(int numerator, int denominator){
		jLatticeRatios[2][0] = numerator;
		jLatticeRatios[2][1] = denominator;
		jLatticeRatios[3][0] = numerator;
		jLatticeRatios[3][1] = denominator;
		calcJustLattice();
	}
	
	public void setMinNumerator(int numerator){
		minNumerator = numerator;
		calcJustGrid();
	}
	
	public void setMinDenominator(int denominator){
		minDenominator = denominator;
		calcJustGrid();
	}
	
	public void setBothMinimums(int numerator, int denominator){
		minNumerator = numerator;
		minDenominator = denominator;
		calcJustGrid();
	}
	
	public void setBaseFrequency(float frequency){
		baseFrequency = frequency;
	}
	
	public void setGridSize(int width, int height){
		gridWidth = width;
		gridHeight = height;
	}
	
	public void setETtet(int divisions){
		etTet = divisions;
	}
	
	public void setJICenter(int theX, int theY){
		centerX = theX;
		centerY = theY;
	}
	

	
	/* *****Getters*********** *****/
	
	/* 
	 *get frequency from just intonation lattice 
	 *indexX and indexY are the index values to call from the array
	 */
	public float getLattFreq(int indexX, int indexY, float baseFrequency){
		//get the ratio in decimal form
		float preFreq = (rLattice[indexX][indexY][0]) / (rLattice[indexX][indexY][1]);
		
		float freq = preFreq * baseFrequency; //mulitply times root frequency
		
		return freq;
	}
	
	/*
	 * get frequency from just intonation grid
	 * indexX and indexY are the index values to call from the array
	 */
	public float getJGridFreq(int indexX, int indexY, float baseFrequency){
		float ratio = (rGrid[indexX][indexY][0]) / (rGrid[indexX][indexY][1]);
		
		float freq = ratio * baseFrequency;
		
		return freq;
	}
	/*
	 * get frequency from equal temperament grid
	 * indexX and indexY are the index values to call from the array
	 * etTet is the number of equal divisions per octave
	 * 
	 */
	public float getETempFreq(int indexX, int indexY, float baseFrequency, int etTet){
		int thePitch = etGrid[indexX][indexY];
		float dPitch = thePitch;
	/*
		if(thePitch ==0){
			return baseFrequency; */

			double freqD = baseFrequency * Math.pow(2, (dPitch/etTet));
			float freq = (float)freqD;
			return freq;
	/*	}
		else if(thePitch<0){
			float absThePitch = Math.abs(dPitch);
			double differenceFreq = (baseFrequency * Math.pow(2,(absThePitch/etTet))) - baseFrequency;
			float freq = baseFrequency - differenceFreq;
			return freq;
		}
		else {
			System.out.println("What is this mysterious value you gave me?");
			return baseFrequency;
		}	*/
	}
	
	public int getMinNumerator(){
		return minNumerator;
	}
	
	public int getMinDenominator(){
		return minDenominator;
	}
	
	public float getBaseFrequency(){
		return baseFrequency;
	}
	
	public int getGridWidth(){
		return gridWidth;
	}
	
	public int getGridHeight(){
		return gridHeight;
	}
	
	public int getETtet(){
		return etTet;
	}
	
	public int[][] getJLatticeRatios(){
		return jLatticeRatios;
	}
	
	public int getXInterval(){
		return xInterval;
	}
	
	public int getYInterval(){
		return yInterval;
	}
	
	public int getCenterX(){
		return centerX;
	}
	
	public int getCenterY(){
		return centerY;
	}

	
//end class
}


