/*
 *  Written by Hao Wu
 * 
 */
////////////////COPYRIGHT DECLARATION//////////////////////
//////
////// COPYRIGHT GUANYU HE AND HAO WU, 2013
////// ALL THE FOLLOWING CODE IS PROTECTED BY THE COPYRIGHT
//////
////// THE CODE IN THIS FILE CANNOT BE REUSED OUTSIDE CIS565 GPU PROGRAMMING COURSE
////// IN UNIVERSITY OF PENNSYLVANIA UNLESS SPECIAL AUTHORIZATION.
//////
////// CONTACT INFO: heguanyu9037@gmail.com
//////                  wuhao1117@gmail.com
//////
////////////////FILE INFO ///////////////////////////////
//////
////// FAST FOURIER TRANSFORMATION
////// INITIALIZATION THE SPECTRUM TEXTURE
////// AND RELATED FRAME BUFFER
//////
////////////////////////////////////////////////////////////

var fftHorizontalProgram;
var fftVerticalProgram;

var spectrumFramebuffer; 
var spectrumTextureA;
var spectrumTextureB;
var initialSpectrumTex;

var butterflyTextures;
 
var heightFieldTex;

var numFFTStages;

function initFFTHorizontalShader() {
    var vertexShader = getShader(gl, "vs_quad");
    var fragmentShader = getShader(gl, "fs_fftHorizontal");

    fftHorizontalProgram = gl.createProgram();
    gl.attachShader(fftHorizontalProgram, vertexShader);
    gl.attachShader(fftHorizontalProgram, fragmentShader);
    gl.linkProgram(fftHorizontalProgram);
    if (!gl.getProgramParameter(fftHorizontalProgram, gl.LINK_STATUS)) {
        alert("Could not initialise FFT Horizontal shader");
    }
 
    fftHorizontalProgram.vertexPositionAttribute = gl.getAttribLocation(fftHorizontalProgram, "position");

    fftHorizontalProgram.fftDataUniform = gl.getUniformLocation(fftHorizontalProgram, "u_fftData");
    fftHorizontalProgram.butterflyUniform = gl.getUniformLocation(fftHorizontalProgram, "u_butterflyData");

}

function initFFTVerticalShader() {
    var vertexShader = getShader(gl, "vs_quad");
    var fragmentShader = getShader(gl, "fs_fftVertical");

    fftVerticalProgram = gl.createProgram();
    gl.attachShader(fftVerticalProgram, vertexShader);
    gl.attachShader(fftVerticalProgram, fragmentShader);
    gl.linkProgram(fftVerticalProgram);
    if (!gl.getProgramParameter(fftVerticalProgram, gl.LINK_STATUS)) {
        alert("Could not initialise FFT Vertical shader");
    }
 
    fftVerticalProgram.vertexPositionAttribute = gl.getAttribLocation(fftVerticalProgram, "position");

    fftVerticalProgram.fftDataUniform = gl.getUniformLocation(fftVerticalProgram, "u_fftData");
    fftVerticalProgram.butterflyUniform = gl.getUniformLocation(fftVerticalProgram, "u_butterflyData");

}


function initFFTFramebuffer()
{
	spectrumFramebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, spectrumFramebuffer);
    spectrumFramebuffer.width = meshSize;
    spectrumFramebuffer.height = meshSize;
    
    spectrumTextureA = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, spectrumTextureA);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, spectrumFramebuffer.width, spectrumFramebuffer.height, 0, gl.RGBA, gl.FLOAT, null);
    
    spectrumTextureB = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, spectrumTextureB);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, spectrumFramebuffer.width, spectrumFramebuffer.height, 0, gl.RGBA, gl.FLOAT, null);
    
   
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, spectrumTextureA, 0);
    
    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
        throw new Error("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
    }

    gl.bindTexture(gl.TEXTURE_2D, null);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}

function bitReverse(x, numFFTStages)
{
	x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
    x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
    x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
    x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
    x = ((x >> 16) | (x << 16));
    x >>>= 32 - numFFTStages;
    return x;
}

function initButterflyTextures()
{
	// initialize butterfly indices and weights for every stage
	numFFTStages = Math.log(meshSize)/Math.LN2;
	var delta = 1.0/meshSize;
	butterflyTextures = new Array(numFFTStages);
	
	for(var n = 0; n < butterflyTextures.length; ++n)
	{
		var butterflyArray = new Float32Array(meshSize*meshSize*4);
		var k = 0, k0 = 0;
		var exp = Math.pow(2, numFFTStages - n - 1);
		var stepNext = Math.pow(2, n+1);
		var stepThis = 0.5*stepNext;
		// compute for the first row		
		for(var m = 0; m < stepThis; ++m) // loop through butterflies with different weights
		{
			k = m*4;
			for(var l = m; l < meshSize; l += stepNext, k += stepNext*4) // loop through butterflies with same weights
			{
				if(n != 0)
				{
					// indices for upper operand of butterfly
					butterflyArray[k]   = (l + 0.5)*delta ;   		  // index (stored as texture coordinates) of Source1
					butterflyArray[k+1] = (l + stepThis + 0.5)*delta;   // index (stored as texture coordinates) of Source2	
					// indices for lower operand of butterfly
					butterflyArray[k+stepThis*4]   = (l + 0.5)*delta ;   		  // index (stored as texture coordinates) of Source1
					butterflyArray[k+stepThis*4+1] = (l + stepThis + 0.5)*delta;   // index (stored as texture coordinates) of Source2	
				}
				else // scramble the index order for the first stage based on bit reversal
				{
					// indices for upper operand of butterfly
					butterflyArray[k]   = (bitReverse(l, numFFTStages)+ 0.5)*delta ;   		  // index (stored as texture coordinates) of Source1
					butterflyArray[k+1] = (bitReverse(l + stepThis, numFFTStages) + 0.5)*delta;   // index (stored as texture coordinates) of Source2			
					// indices for lower operand of butterfly
					butterflyArray[k+stepThis*4]   = (bitReverse(l, numFFTStages) + 0.5)*delta ;   		  // index (stored as texture coordinates) of Source1
					butterflyArray[k+stepThis*4+1] = (bitReverse(l + stepThis, numFFTStages) + 0.5)*delta;   // index (stored as texture coordinates) of Source2
				}						
			}
		}
		
		k = 2;
		for(var i = 0; i < meshSize; i++, k += 2) 
		{
			
			/*
			 *   Source1 ----------				- += Output1
			 * 			 			-		-	
			 * 			 				- 	
			 *  		    		-		-
			 *   Source2 * weight--				- += Output2
			 *   
			 * 	 For Source1, weight is stored as it is
			 * 	 For Source2, weight is stored as -weight
			 * 
			 */
			var r = (i * exp) % meshSize;		
			butterflyArray[k++] =  Math.cos(2*Math.PI*r/meshSize);   // real part of weight
			butterflyArray[k++] =  Math.sin(2*Math.PI*r/meshSize);   // imaginary part of weight
		}
		// copy the first row to every row
		k = 4*meshSize;
		for(var j = 1; j < meshSize; j++)
		{
			k0 = 0;
			for(var i = 0; i < meshSize; i++) 
			{
				butterflyArray[k++] = butterflyArray[k0++];   // index (stored as texture coordinates) of Source1
				butterflyArray[k++] = butterflyArray[k0++];   // index (stored as texture coordinates) of Source2
				butterflyArray[k++] = butterflyArray[k0++];   // real part of weight
				butterflyArray[k++] = butterflyArray[k0++];   // imaginary part of weight
			}
		}
		butterflyTextures[n] = gl.createTexture();
	    gl.bindTexture(gl.TEXTURE_2D, butterflyTextures[n]);
	    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
	    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
	    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
	    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
	    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, meshSize, meshSize, 0, gl.RGBA, gl.FLOAT, butterflyArray);
	}

}

function executeFFTStage(sourceData, butterflyData, framebuffer)
{
	

}