/**
 * @author clockworkgeek / https://github.com/clockworkgeek
 * @author timothypratley / https://github.com/timothypratley
 * @author WestLangley / http://github.com/WestLangley
 * @author Mugen87 / https://github.com/Mugen87
 */

import { Geometry } from '../core/Geometry.js';
import { BufferGeometry } from '../core/BufferGeometry.js';
import { Float32BufferAttribute } from '../core/BufferAttribute.js';
import { Vector3 } from '../math/Vector3.js';
import { Vector2 } from '../math/Vector2.js';

// PolyhedronGeometry

function PolyhedronGeometry( vertices, indices, radius, detail ) {

	Geometry.call( this );

	this.type = 'PolyhedronGeometry';

	this.parameters = {
		vertices: vertices,
		indices: indices,
		radius: radius,
		detail: detail
	};

	this.fromBufferGeometry( new PolyhedronBufferGeometry( vertices, indices, radius, detail ) );
	this.mergeVertices();

}

PolyhedronGeometry.prototype = Object.create( Geometry.prototype );
PolyhedronGeometry.prototype.constructor = PolyhedronGeometry;

// PolyhedronBufferGeometry

function PolyhedronBufferGeometry( vertices, indices, radius, detail ) {

	BufferGeometry.call( this );

	this.type = 'PolyhedronBufferGeometry';

	this.parameters = {
		vertices: vertices,
		indices: indices,
		radius: radius,
		detail: detail
	};

	radius = radius || 1;
	detail = detail || 0;

	// default buffer data

	var vertexBuffer = [];
	var uvBuffer = [];

	// the subdivision creates the vertex buffer data

	subdivide( detail );

	// all vertices should lie on a conceptual sphere with a given radius

	applyRadius( radius );

	// finally, create the uv data

	generateUVs();

	// build non-indexed geometry

	this.setAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) );
	this.setAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) );
	this.setAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) );

	if ( detail === 0 ) {

		this.computeVertexNormals(); // flat normals

	} else {

		this.normalizeNormals(); // smooth normals

	}

	// helper functions

	function subdivide( detail ) {

		var a = new Vector3();
		var b = new Vector3();
		var c = new Vector3();

		// iterate over all faces and apply a subdivison with the given detail value

		for ( var i = 0; i < indices.length; i += 3 ) {

			// get the vertices of the face

			getVertexByIndex( indices[ i + 0 ], a );
			getVertexByIndex( indices[ i + 1 ], b );
			getVertexByIndex( indices[ i + 2 ], c );

			// perform subdivision

			subdivideFace( a, b, c, detail );

		}

	}

	function subdivideFace( a, b, c, detail ) {

		var cols = Math.pow( 2, detail );

		// we use this multidimensional array as a data structure for creating the subdivision

		var v = [];

		var i, j;

		// construct all of the vertices for this subdivision

		for ( i = 0; i <= cols; i ++ ) {

			v[ i ] = [];

			var aj = a.clone().lerp( c, i / cols );
			var bj = b.clone().lerp( c, i / cols );

			var rows = cols - i;

			for ( j = 0; j <= rows; j ++ ) {

				if ( j === 0 && i === cols ) {

					v[ i ][ j ] = aj;

				} else {

					v[ i ][ j ] = aj.clone().lerp( bj, j / rows );

				}

			}

		}

		// construct all of the faces

		for ( i = 0; i < cols; i ++ ) {

			for ( j = 0; j < 2 * ( cols - i ) - 1; j ++ ) {

				var k = Math.floor( j / 2 );

				if ( j % 2 === 0 ) {

					pushVertex( v[ i ][ k + 1 ] );
					pushVertex( v[ i + 1 ][ k ] );
					pushVertex( v[ i ][ k ] );

				} else {

					pushVertex( v[ i ][ k + 1 ] );
					pushVertex( v[ i + 1 ][ k + 1 ] );
					pushVertex( v[ i + 1 ][ k ] );

				}

			}

		}

	}

	function applyRadius( radius ) {

		var vertex = new Vector3();

		// iterate over the entire buffer and apply the radius to each vertex

		for ( var i = 0; i < vertexBuffer.length; i += 3 ) {

			vertex.x = vertexBuffer[ i + 0 ];
			vertex.y = vertexBuffer[ i + 1 ];
			vertex.z = vertexBuffer[ i + 2 ];

			vertex.normalize().multiplyScalar( radius );

			vertexBuffer[ i + 0 ] = vertex.x;
			vertexBuffer[ i + 1 ] = vertex.y;
			vertexBuffer[ i + 2 ] = vertex.z;

		}

	}

	function generateUVs() {

		var vertex = new Vector3();

		for ( var i = 0; i < vertexBuffer.length; i += 3 ) {

			vertex.x = vertexBuffer[ i + 0 ];
			vertex.y = vertexBuffer[ i + 1 ];
			vertex.z = vertexBuffer[ i + 2 ];

			var u = azimuth( vertex ) / 2 / Math.PI + 0.5;
			var v = inclination( vertex ) / Math.PI + 0.5;
			uvBuffer.push( u, 1 - v );

		}

		correctUVs();

		correctSeam();

	}

	function correctSeam() {

		// handle case when face straddles the seam, see #3269

		for ( var i = 0; i < uvBuffer.length; i += 6 ) {

			// uv data of a single face

			var x0 = uvBuffer[ i + 0 ];
			var x1 = uvBuffer[ i + 2 ];
			var x2 = uvBuffer[ i + 4 ];

			var max = Math.max( x0, x1, x2 );
			var min = Math.min( x0, x1, x2 );

			// 0.9 is somewhat arbitrary

			if ( max > 0.9 && min < 0.1 ) {

				if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1;
				if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1;
				if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1;

			}

		}

	}

	function pushVertex( vertex ) {

		vertexBuffer.push( vertex.x, vertex.y, vertex.z );

	}

	function getVertexByIndex( index, vertex ) {

		var stride = index * 3;

		vertex.x = vertices[ stride + 0 ];
		vertex.y = vertices[ stride + 1 ];
		vertex.z = vertices[ stride + 2 ];

	}

	function correctUVs() {

		var a = new Vector3();
		var b = new Vector3();
		var c = new Vector3();

		var centroid = new Vector3();

		var uvA = new Vector2();
		var uvB = new Vector2();
		var uvC = new Vector2();

		for ( var i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) {

			a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] );
			b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] );
			c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] );

			uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] );
			uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] );
			uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] );

			centroid.copy( a ).add( b ).add( c ).divideScalar( 3 );

			var azi = azimuth( centroid );

			correctUV( uvA, j + 0, a, azi );
			correctUV( uvB, j + 2, b, azi );
			correctUV( uvC, j + 4, c, azi );

		}

	}

	function correctUV( uv, stride, vector, azimuth ) {

		if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) {

			uvBuffer[ stride ] = uv.x - 1;

		}

		if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) {

			uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5;

		}

	}

	// Angle around the Y axis, counter-clockwise when looking from above.

	function azimuth( vector ) {

		return Math.atan2( vector.z, - vector.x );

	}


	// Angle above the XZ plane.

	function inclination( vector ) {

		return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) );

	}

}

PolyhedronBufferGeometry.prototype = Object.create( BufferGeometry.prototype );
PolyhedronBufferGeometry.prototype.constructor = PolyhedronBufferGeometry;


export { PolyhedronGeometry, PolyhedronBufferGeometry };
