"use strict";
(() => {
	const makeCylinder = (meridians = 3, parallels = 3) => {
		// The number of meridians dividing the sphere's surface.
		// Two meridians would make an XZ plane.
		if (meridians < 3) {
			throw "meridians must be ≥ 3";
		}

		// The number of parallels, including the poles, dividing the sphere's surface.
		// Two parallels would make a vertical line segment.
		if (parallels < 3) {
			throw "parallels must be ≥ 3";
		}

		const radius = 1;

		const positions = new Float32Array(meridians * parallels * 3);
		let positionsIdx = 0;
		for (let p = 0; p < parallels; p++) {
			const y = -radius + p * 2 * radius / parallels;
			for (let m = 0; m < meridians; m++) {
				const θ = m * 2 * Math.PI / meridians;
				const x = Math.sin(θ) * radius;
				const z = Math.cos(θ) * radius;

				const currPosition = [
					Math.sin(θ) * radius,
					y,
					Math.cos(θ) * radius,
				];
				for (const coord of currPosition) {
					positions[positionsIdx++] = coord;
				}
			}
		}

		const indices = new Uint16Array(meridians * (parallels - 1) * 6);
		const textureCoords = new Float32Array(meridians * (parallels - 1) * 12);
		let indicesIdx = 0;
		let textureCoordsIdx = 0;
		for (let p = 0; p < parallels - 1; p++) {
			for (let m = 0; m < meridians; m++) {
				const lowerLeft = p * meridians + m;
				const lowerRight = p * meridians + (m + 1) % meridians;
				const upperLeft = (p + 1) * meridians + m;
				const upperRight = (p + 1) * meridians + (m + 1) % meridians;
				const currIndices = [
					// Lower triangle
					lowerLeft, lowerRight, upperRight,

					// Upper triangle
					lowerLeft, upperRight, upperLeft,
				];
				for (const index of currIndices) {
					indices[indicesIdx++] = index;
				}

				const currTextureCoords = [
					// Lower triangle
					m / meridians, p / parallels,
					(m + 1) / meridians, p / parallels,
					(m + 1) / meridians, (p + 1) / parallels,

					// Upper triangle
					m / meridians, p / parallels,
					(m + 1) / meridians, (p + 1) / parallels,
					m / meridians, (p + 1) / parallels,
				];
				for (const coord of currTextureCoords) {
					textureCoords[textureCoordsIdx++] = coord;
				}
			}
		}

		for (let p = 0; p < parallels; p++) {
			for (let m = 0; m < meridians; m++) {
				/*
				
				*/
			}
		}

		return { positions, textureCoords, indices };
	}
	console.log(makeCylinder());

	const initShaderProgram = (gl) => {
		const vShaderSrc = `
			attribute vec3 position;
			attribute vec2 textureCoord;

			uniform mat4 modelMatrix;

			varying highp vec2 vTextureCoord;

			void main() {
				gl_Position = modelMatrix * vec4(position, 1);
				vTextureCoord = textureCoord;
			}
		`;

		const fShaderSrc = `
			varying highp vec2 vTextureCoord;
			uniform sampler2D sampler;

			void main() {
				gl_FragColor = texture2D(sampler, vTextureCoord);
			}
		`;

		const compileShader = (src, type) => {
			let shader = gl.createShader(type);
			gl.shaderSource(shader, src);
			gl.compileShader(shader);
			let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
			if (!success) {
				throw gl.getShaderInfoLog(shader);
			}
			return shader;
		}

		let vShader = compileShader(vShaderSrc, gl.VERTEX_SHADER);
		let fShader = compileShader(fShaderSrc, gl.FRAGMENT_SHADER);

		let program = gl.createProgram();
		gl.attachShader(program, vShader);
		gl.attachShader(program, fShader);
		gl.linkProgram(program);
		let success = gl.getProgramParameter(program, gl.LINK_STATUS);
		if (!success) {
			throw gl.getProgramInfoLog(program);
		}
		return program;
	};

	const loadTexture = (gl, image, textureUnit = 0) => {
		const textureUnitName = `TEXTURE${textureUnit}`
		const texture = gl.createTexture();
		const samplerLocation = gl.getUniformLocation(program, "sampler");
		image.addEventListener("load", () => {
			gl.activeTexture(gl[textureUnitName]);
			gl.bindTexture(gl.TEXTURE_2D, texture);
			gl.uniform1i(samplerLocation, textureUnit);
			gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
			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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
		});
		return texture;
	};

	let viewer = document.getElementById("viewer");
	let gl = viewer.getContext("webgl2");
	let program = initShaderProgram(gl);
	let modelMatrixLocation = gl.getUniformLocation(program, "modelMatrix");

	// Set up mouse-panning state machine.
	let θy = 0;
	let θx = 0.25;
	const PanState = Object.freeze({
		idle: (event) => {
			if (event.type === "mousedown") {
				return PanState.panning;
			}
			return PanState.idle;
		},
		panning: (event) => {
			switch (event.type) {
				case "mouseup":
					return PanState.idle;
				case "mouseover":
					if ((event.buttons & 1) == 0) {
						return PanState.idle;
					}
					break;
				case "mousemove":
					θy += event.movementX * 0.025;
					θx += event.movementY * 0.025;
					break
			}
			return PanState.panning;
		},
	});
	const runPanState = (mouseEvent) => {
		panState = panState(mouseEvent);
	}
	var panState = PanState.idle;
	viewer.addEventListener("mousedown", runPanState);
	viewer.addEventListener("mouseup", runPanState);
	viewer.addEventListener("mouseover", runPanState);
	viewer.addEventListener("mousemove", runPanState);

	gl.useProgram(program);
	let vertexBuffer = gl.createBuffer();
	let vertices = new Float32Array([
		// x, y, z, u, v
		0, 0, 0, 0, 0,
		1, 0, 0, 1, 0,
		1, 1, 0, 1, 1,
		0, 1, 0, 0, 1,
	]);
	const vertexStride = 4 * 5;
	const positionOffset = 0;
	const textureCoordOffset = 4 * 3;
	gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
	const positionLocation = gl.getAttribLocation(program, "position");
	const textureCoordLocation = gl.getAttribLocation(program, "textureCoord");

	let indexBuffer = gl.createBuffer();
	let indices = new Uint16Array([
		0, 1, 2,
		3, 0, 2,
	]);
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

	// Load texture
	let image = document.getElementById("image");
	loadTexture(gl, image);
	gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

	const render = () => {
		gl.useProgram(program);

		// Create the transformation matrix.
		let modelMatrix = mat4.create();
		mat4.rotateX(modelMatrix, modelMatrix, θx);
		mat4.rotateY(modelMatrix, modelMatrix, θy);
		gl.uniformMatrix4fv(modelMatrixLocation, false, modelMatrix);

		gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
		gl.enableVertexAttribArray(positionLocation);
		gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, vertexStride, positionOffset);
		gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

		gl.enableVertexAttribArray(textureCoordLocation);
		gl.vertexAttribPointer(textureCoordLocation, 2, gl.FLOAT, false, vertexStride, textureCoordOffset);

		requestAnimationFrame(render);
	};

	requestAnimationFrame(render);
})();