import {
	Object3D,
	Material,
	Vector2,
	Vector4,
	Camera,
	WebGLRenderTarget,
	NearestFilter,
	RGBAFormat,
	FloatType,
	WebGLRenderer,
	Scene,
	NoToneMapping,
	NoColorSpace,
	Color,
	ColorSpace,
	ToneMapping,
	DepthTexture,
	UnsignedInt248Type,
} from 'three';
import {coreGetDefaultCamera} from './CoreGetDefautCamera';
import {PolyScene} from '../../../../src/engine/scene/PolyScene';
import {setupDepthReadScene, updateDepthSetup} from './DepthRead';

export function coreCursorToUv(cursor: Vector2, target: Vector2) {
	target.x = 0.5 * (cursor.x + 1);
	target.y = 0.5 * (1 - cursor.y);
}

interface Object3DRestoreContext {
	parent: Object3D | null;
}

interface RendererRestoreContext {
	toneMapping: ToneMapping;
	outputColorSpace: ColorSpace;
}
interface RestoreContext {
	object: Object3DRestoreContext;
	renderer: RendererRestoreContext;
}
function _createDepthTexture() {
	const texture = new DepthTexture(1, 1);
	texture.type = UnsignedInt248Type;
	return texture;
}

export class RenderPixelController {
	// Note for this to work on iOS:
	// The materials used for picking should have their transparency OFF.
	// This could potentially be done automatically by traversing the scene first.
	private _colorWriteRenderTarget: WebGLRenderTarget = new WebGLRenderTarget(1, 1, {
		minFilter: NearestFilter,
		magFilter: NearestFilter,
		format: RGBAFormat,
		type: FloatType,
		colorSpace: NoColorSpace,
		depthTexture: _createDepthTexture(),
	});
	private _depthReadRenderTarget: WebGLRenderTarget = new WebGLRenderTarget(1, 1, {
		minFilter: NearestFilter,
		magFilter: NearestFilter,
		format: RGBAFormat,
		type: FloatType,
		colorSpace: NoColorSpace,
	});
	private _renderScene = new Scene();
	private _depthReadSetup = setupDepthReadScene();
	private _restoreContext: RestoreContext = {
		object: {
			parent: null,
		},
		// scene: {
		// 	overrideMaterial: null,
		// },
		renderer: {
			toneMapping: NoToneMapping,
			outputColorSpace: NoColorSpace,
		},
	};
	private _read = new Float32Array(4);

	renderColor(
		scene: PolyScene,
		object3D: Object3D,
		material: Material | null,
		camera: Camera,
		backgroundColor: Color | null,
		uv: Vector2,
		target: Vector4
	): Vector4 {
		this._doRender(scene, object3D, camera, material, backgroundColor, uv, target, false);

		return target;
	}
	renderDepth(
		scene: PolyScene,
		object3D: Object3D,
		camera: Camera,
		backgroundColor: Color | null,
		uv: Vector2,
		target: Vector4
	): Vector4 {
		this._doRender(scene, object3D, camera, null, backgroundColor, uv, target, true);

		return target;
	}
	private _doRender(
		scene: PolyScene,
		object3D: Object3D,
		camera: Camera,
		material: Material | null,
		backgroundColor: Color | null,
		uv: Vector2,
		target: Vector4,
		renderDepth: boolean
	) {
		const renderer = scene.renderersRegister.lastRegisteredRenderer();
		if (!renderer) {
			return target;
		}
		if (!(renderer instanceof WebGLRenderer)) {
			console.log('renderPixel: renderer found is not WebGLRenderer');
			return target;
		}
		if (camera == null) {
			camera = coreGetDefaultCamera(scene);
		}

		this._prepare(object3D, material, backgroundColor, renderer);
		this._render(uv, camera, renderer, target, renderDepth);
		this._restore(object3D, renderer);

		return target;
	}

	private _prepare(
		object3D: Object3D,
		material: Material | null,
		backgroundColor: Color | null,
		renderer: WebGLRenderer
	) {
		// save context
		this._restoreContext.renderer.outputColorSpace = renderer.outputColorSpace;
		this._restoreContext.renderer.toneMapping = renderer.toneMapping;
		this._restoreContext.object.parent = object3D.parent;

		// set context
		this._renderScene.background = backgroundColor;
		this._renderScene.overrideMaterial = material || null;
		this._renderScene.attach(object3D);
		renderer.toneMapping = NoToneMapping;
		renderer.outputColorSpace = NoColorSpace;
	}
	private _render(uv: Vector2, camera: Camera, renderer: WebGLRenderer, target: Vector4, readDepth: boolean) {
		(camera as any).setViewOffset(
			renderer.domElement.width,
			renderer.domElement.height,
			uv.x * renderer.domElement.width,
			uv.y * renderer.domElement.height,
			1,
			1
		);

		renderer.setRenderTarget(this._colorWriteRenderTarget);
		renderer.clear();
		renderer.render(this._renderScene, camera);

		if (readDepth) {
			// read depth
			updateDepthSetup(this._depthReadSetup, camera, this._colorWriteRenderTarget);
			renderer.setRenderTarget(this._depthReadRenderTarget);
			renderer.render(this._depthReadSetup.scene, this._depthReadSetup.camera);
			renderer.readRenderTargetPixels(this._depthReadRenderTarget, 0, 0, 1, 1, this._read);
		} else {
			// There are some cases where .readRenderTargetPixels is slow,
			// and this seems to be due to the calls to _gl.getParameters.
			// Here we are bypassing it.
			// Note: this attempt to bypass needs "properties", which is internal to WebGLRenderer.
			// const context = renderer.getContext();
			// const textureFormat = context.RGBA; // RGBAFormat see three/WebGLUtils.js
			// const textureType = context.FLOAT; // FloatType see three/WebGLUtils.js
			// context.readPixels(0, 0, 1, 1, textureFormat, textureType, this._read);
			renderer.readRenderTargetPixels(this._colorWriteRenderTarget, 0, 0, 1, 1, this._read);
		}

		renderer.setRenderTarget(null);
		(camera as any).clearViewOffset();

		// read buffer into target vector
		target.fromArray(this._read);
	}
	private _restore(object3D: Object3D, renderer: WebGLRenderer) {
		renderer.outputColorSpace = this._restoreContext.renderer.outputColorSpace;
		renderer.toneMapping = this._restoreContext.renderer.toneMapping;
		this._restoreContext.object.parent?.attach(object3D);
	}
}
