import { GUI } from '../../examples/jsm/libs/lil-gui.module.min.js';

{

	function outlineText( ctx, msg, x, y ) {

		ctx.strokeText( msg, x, y );
		ctx.fillText( msg, x, y );

	}

	function arrow( ctx, x1, y1, x2, y2, start, end, size ) {

		size = size || 1;
		const dx = x1 - x2;
		const dy = y1 - y2;
		const rot = - Math.atan2( dx, dy );
		const len = Math.sqrt( dx * dx + dy * dy );
		ctx.save();
		{

			ctx.translate( x1, y1 );
			ctx.rotate( rot );
			ctx.beginPath();
			ctx.moveTo( 0, 0 );
			ctx.lineTo( 0, - ( len - 10 * size ) );
			ctx.stroke();

		}

		ctx.restore();
		if ( start ) {

			arrowHead( ctx, x1, y1, rot, size );

		}

		if ( end ) {

			arrowHead( ctx, x2, y2, rot + Math.PI, size );

		}

	}

	function arrowHead( ctx, x, y, rot, size ) {

		ctx.save();
		{

			ctx.translate( x, y );
			ctx.rotate( rot );
			ctx.scale( size, size );
			ctx.translate( 0, - 10 );
			ctx.beginPath();
			ctx.moveTo( 0, 0 );
			ctx.lineTo( - 5, - 2 );
			ctx.lineTo( 0, 10 );
			ctx.lineTo( 5, - 2 );
			ctx.closePath();
			ctx.fill();

		}

		ctx.restore();

	}

	const THREE = {
		MathUtils: {
			radToDeg( rad ) {

				return rad * 180 / Math.PI;

			},
			degToRad( deg ) {

				return deg * Math.PI / 180;

			},
		},
	};

	class DegRadHelper {

		constructor( obj, prop ) {

			this.obj = obj;
			this.prop = prop;

		}
		get value() {

			return THREE.MathUtils.radToDeg( this.obj[ this.prop ] );

		}
		set value( v ) {

			this.obj[ this.prop ] = THREE.MathUtils.degToRad( v );

		}

	}

	function dot( x1, y1, x2, y2 ) {

		return x1 * x2 + y1 * y2;

	}

	function distance( x1, y1, x2, y2 ) {

		const dx = x1 - x2;
		const dy = y1 - y2;
		return Math.sqrt( dx * dx + dy * dy );

	}

	function normalize( x, y ) {

		const l = distance( 0, 0, x, y );
		if ( l > 0.00001 ) {

			return [ x / l, y / l ];

		} else {

			return [ 0, 0 ];

		}

	}

	function resizeCanvasToDisplaySize( canvas, pixelRatio = 1 ) {

		const width = canvas.clientWidth * pixelRatio | 0;
		const height = canvas.clientHeight * pixelRatio | 0;
		const needResize = canvas.width !== width || canvas.height !== height;
		if ( needResize ) {

			canvas.width = width;
			canvas.height = height;

		}

		return needResize;

	}

	const diagrams = {
		dotProduct: {
			create( info ) {

				const { elem } = info;
				const div = document.createElement( 'div' );
				div.style.position = 'relative';
				div.style.width = '100%';
				div.style.height = '100%';
				elem.appendChild( div );

				const ctx = document.createElement( 'canvas' ).getContext( '2d' );
				div.appendChild( ctx.canvas );
				const settings = {
					rotation: 0.3,
				};

				const gui = new GUI( { autoPlace: false } );
				gui.add( new DegRadHelper( settings, 'rotation' ), 'value', - 180, 180 ).name( 'rotation' ).onChange( render );
				gui.domElement.style.position = 'absolute';
				gui.domElement.style.top = '0';
				gui.domElement.style.right = '0';
				div.appendChild( gui.domElement );

				const darkColors = {
					globe: 'green',
					camera: '#AAA',
					base: '#DDD',
					label: '#0FF',
				};
				const lightColors = {
					globe: '#0C0',
					camera: 'black',
					base: '#000',
					label: 'blue',
				};

				const darkMatcher = window.matchMedia( '(prefers-color-scheme: dark)' );
				darkMatcher.addEventListener( 'change', render );

				function render() {

					const { rotation } = settings;
					const isDarkMode = darkMatcher.matches;
					const colors = isDarkMode ? darkColors : lightColors;

					const pixelRatio = window.devicePixelRatio;
					resizeCanvasToDisplaySize( ctx.canvas, pixelRatio );

					ctx.clearRect( 0, 0, ctx.canvas.width, ctx.canvas.height );
					ctx.save();
					{

						const width = ctx.canvas.width / pixelRatio;
						const height = ctx.canvas.height / pixelRatio;
						const min = Math.min( width, height );
						const half = min / 2;

						const r = half * 0.4;
						const x = r * Math.sin( - rotation );
						const y = r * Math.cos( - rotation );

						const camDX = x - 0;
						const camDY = y - ( half - 40 );

						const labelDir = normalize( x, y );
						const camToLabelDir = normalize( camDX, camDY );

						const dp = dot( ...camToLabelDir, ...labelDir );

						ctx.scale( pixelRatio, pixelRatio );
						ctx.save();
						{

							{

								ctx.translate( width / 2, height / 2 );
								ctx.beginPath();
								ctx.arc( 0, 0, half * 0.4, 0, Math.PI * 2 );
								ctx.fillStyle = colors.globe;
								ctx.fill();

								ctx.save();
								{

									ctx.fillStyle = colors.camera;
									ctx.translate( 0, half );
									ctx.fillRect( - 15, - 30, 30, 30 );
									ctx.beginPath();
									ctx.moveTo( 0, - 25 );
									ctx.lineTo( - 25, - 50 );
									ctx.lineTo( 25, - 50 );
									ctx.closePath();
									ctx.fill();

								}

								ctx.restore();

								ctx.save();
								{

									ctx.lineWidth = 4;
									ctx.strokeStyle = colors.camera;
									ctx.fillStyle = colors.camera;
									arrow( ctx, 0, half - 40, x, y, false, true, 2 );

									ctx.save();
									{

										ctx.strokeStyle = colors.label;
										ctx.fillStyle = colors.label;
										arrow( ctx, 0, 0, x, y, false, true, 2 );

									}

									ctx.restore();

									{

										ctx.lineWidth = 3;
										ctx.strokeStyle = 'black';
										ctx.fillStyle = dp < 0 ? 'white' : 'red';
										ctx.font = '20px sans-serif';
										ctx.textAlign = 'center';
										ctx.textBaseline = 'middle';
										outlineText( ctx, 'label', x, y );

									}

								}

								ctx.restore();

							}

							ctx.restore();

						}

						ctx.lineWidth = 3;
						ctx.font = '24px sans-serif';
						ctx.strokeStyle = 'black';
						ctx.textAlign = 'left';
						ctx.textBaseline = 'middle';
						ctx.save();
						{

							ctx.translate( width / 4, 80 );
							const textColor = dp < 0 ? colors.base : 'red';
							advanceText( ctx, textColor, 'dot( ' );
							ctx.save();
							{

								ctx.fillStyle = colors.camera;
								ctx.strokeStyle = colors.camera;
								ctx.rotate( Math.atan2( camDY, camDX ) );
								arrow( ctx, - 8, 0, 8, 0, false, true, 1 );

							}

							ctx.restore();
							advanceText( ctx, textColor, ' ,  ' );
							ctx.save();
							{

								ctx.fillStyle = colors.label;
								ctx.strokeStyle = colors.label;
								ctx.rotate( rotation + Math.PI * 0.5 );
								arrow( ctx, - 8, 0, 8, 0, false, true, 1 );

							}

							ctx.restore();
							advanceText( ctx, textColor, ` ) = ${dp.toFixed( 2 )}` );

						}

						ctx.restore();

					}

					ctx.restore();

				}

				render();
				window.addEventListener( 'resize', render );

			},
		},
	};

	function advanceText( ctx, color, str ) {

		ctx.fillStyle = color;
		ctx.fillText( str, 0, 0 );
		ctx.translate( ctx.measureText( str ).width, 0 );

	}

	[ ...document.querySelectorAll( '[data-diagram]' ) ].forEach( createDiagram );

	function createDiagram( base ) {

		const name = base.dataset.diagram;
		const info = diagrams[ name ];
		if ( ! info ) {

			throw new Error( `no diagram ${name}` );

		}

		info.create( { elem: base } );

	}

}


