import { PIXELFORMAT_RGBA8, PIXELFORMAT_DXT5, TEXTURETYPE_SWIZZLEGGGR } from '../../platform/graphics/constants.js';
import { SHADERDEF_TANGENTS, SHADERDEF_SCREENSPACE, SHADERDEF_SKIN, SHADERDEF_BATCH, SHADERDEF_INSTANCING, SHADERDEF_MORPH_POSITION, SHADERDEF_MORPH_NORMAL, SHADERDEF_MORPH_TEXTURE_BASED_INT, BLEND_NONE, DITHER_NONE, REFLECTIONSRC_NONE, FOG_NONE, TONEMAP_NONE, REFLECTIONSRC_ENVATLASHQ, REFLECTIONSRC_ENVATLAS, REFLECTIONSRC_CUBEMAP, REFLECTIONSRC_SPHEREMAP, AMBIENTSRC_AMBIENTSH, AMBIENTSRC_ENVALATLAS, AMBIENTSRC_CONSTANT, SHADERDEF_NOSHADOW, SHADERDEF_LM, SHADERDEF_DIRLM, SHADERDEF_LMAMBIENT, MASK_AFFECT_DYNAMIC, LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI, LIGHTTYPE_SPOT, SHADERDEF_UV0, SHADERDEF_UV1, SHADERDEF_VCOLOR, SHADER_PREPASS } from '../constants.js';
import { _matTex2D } from '../shader-lib/programs/standard.js';
import { LitMaterialOptionsBuilder } from './lit-material-options-builder.js';

const arraysEqual = (a, b)=>{
		if (a.length !== b.length) {
				return false;
		}
		for(let i = 0; i < a.length; ++i){
				if (a[i] !== b[i]) {
						return false;
				}
		}
		return true;
};
const notWhite = (color)=>{
		return color.r !== 1 || color.g !== 1 || color.b !== 1;
};
const notBlack = (color)=>{
		return color.r !== 0 || color.g !== 0 || color.b !== 0;
};
class StandardMaterialOptionsBuilder {
		constructor(){
				this._mapXForms = null;
		}
		updateMinRef(options, scene, stdMat, objDefs, pass, sortedLights) {
				this._updateSharedOptions(options, scene, stdMat, objDefs, pass);
				this._updateMinOptions(options, stdMat, pass);
				this._updateUVOptions(options, stdMat, objDefs, true);
		}
		updateRef(options, scene, cameraShaderParams, stdMat, objDefs, pass, sortedLights) {
				this._updateSharedOptions(options, scene, stdMat, objDefs, pass);
				this._updateEnvOptions(options, stdMat, scene, cameraShaderParams);
				this._updateMaterialOptions(options, stdMat, scene);
				options.litOptions.hasTangents = objDefs && (objDefs & SHADERDEF_TANGENTS) !== 0;
				this._updateLightOptions(options, scene, stdMat, objDefs, sortedLights);
				this._updateUVOptions(options, stdMat, objDefs, false, cameraShaderParams);
		}
		_updateSharedOptions(options, scene, stdMat, objDefs, pass) {
				options.forceUv1 = stdMat.forceUv1;
				if (stdMat.userAttributes) {
						options.litOptions.userAttributes = Object.fromEntries(stdMat.userAttributes.entries());
				}
				options.litOptions.shaderChunks = stdMat.shaderChunks;
				options.litOptions.pass = pass;
				options.litOptions.alphaTest = stdMat.alphaTest > 0;
				options.litOptions.blendType = stdMat.blendType;
				options.litOptions.screenSpace = objDefs && (objDefs & SHADERDEF_SCREENSPACE) !== 0;
				options.litOptions.skin = objDefs && (objDefs & SHADERDEF_SKIN) !== 0;
				options.litOptions.batch = objDefs && (objDefs & SHADERDEF_BATCH) !== 0;
				options.litOptions.useInstancing = objDefs && (objDefs & SHADERDEF_INSTANCING) !== 0;
				options.litOptions.useMorphPosition = objDefs && (objDefs & SHADERDEF_MORPH_POSITION) !== 0;
				options.litOptions.useMorphNormal = objDefs && (objDefs & SHADERDEF_MORPH_NORMAL) !== 0;
				options.litOptions.useMorphTextureBasedInt = objDefs && (objDefs & SHADERDEF_MORPH_TEXTURE_BASED_INT) !== 0;
				options.litOptions.nineSlicedMode = stdMat.nineSlicedMode || 0;
				if (scene.clusteredLightingEnabled && stdMat.useLighting) {
						options.litOptions.clusteredLightingEnabled = true;
						options.litOptions.clusteredLightingCookiesEnabled = scene.lighting.cookiesEnabled;
						options.litOptions.clusteredLightingShadowsEnabled = scene.lighting.shadowsEnabled;
						options.litOptions.clusteredLightingShadowType = scene.lighting.shadowType;
						options.litOptions.clusteredLightingAreaLightsEnabled = scene.lighting.areaLightsEnabled;
				} else {
						options.litOptions.clusteredLightingEnabled = false;
						options.litOptions.clusteredLightingCookiesEnabled = false;
						options.litOptions.clusteredLightingShadowsEnabled = false;
						options.litOptions.clusteredLightingAreaLightsEnabled = false;
				}
		}
		_updateUVOptions(options, stdMat, objDefs, minimalOptions, cameraShaderParams) {
				let hasUv0 = false;
				let hasUv1 = false;
				let hasVcolor = false;
				if (objDefs) {
						hasUv0 = (objDefs & SHADERDEF_UV0) !== 0;
						hasUv1 = (objDefs & SHADERDEF_UV1) !== 0;
						hasVcolor = (objDefs & SHADERDEF_VCOLOR) !== 0;
				}
				options.litOptions.vertexColors = false;
				this._mapXForms = [];
				const uniqueTextureMap = {};
				for(const p in _matTex2D){
						this._updateTexOptions(options, stdMat, p, hasUv0, hasUv1, hasVcolor, minimalOptions, uniqueTextureMap);
				}
				this._mapXForms = null;
				options.litOptions.ssao = cameraShaderParams?.ssaoEnabled;
				options.useAO = options.litOptions.ssao;
				options.litOptions.lightMapEnabled = options.lightMap;
				options.litOptions.dirLightMapEnabled = options.dirLightMap;
				options.litOptions.useHeights = options.heightMap;
				options.litOptions.useNormals = options.normalMap;
				options.litOptions.useClearCoatNormals = options.clearCoatNormalMap;
				options.litOptions.useAo = options.aoMap || options.aoVertexColor || options.litOptions.ssao;
				options.litOptions.diffuseMapEnabled = options.diffuseMap;
		}
		_updateTexOptions(options, stdMat, p, hasUv0, hasUv1, hasVcolor, minimalOptions, uniqueTextureMap) {
				const isOpacity = p === 'opacity';
				if (!minimalOptions || isOpacity) {
						const mname = `${p}Map`;
						const vname = `${p}VertexColor`;
						const vcname = `${p}VertexColorChannel`;
						const cname = `${mname}Channel`;
						const tname = `${mname}Transform`;
						const uname = `${mname}Uv`;
						const iname = `${mname}Identifier`;
						if (p !== 'light') {
								options[mname] = false;
								options[iname] = undefined;
								options[cname] = '';
								options[tname] = 0;
								options[uname] = 0;
						}
						options[vname] = false;
						options[vcname] = '';
						if (isOpacity && stdMat.blendType === BLEND_NONE && stdMat.alphaTest === 0.0 && !stdMat.alphaToCoverage && stdMat.opacityDither === DITHER_NONE) {
								return;
						}
						if (p !== 'height' && stdMat[vname]) {
								if (hasVcolor) {
										options[vname] = stdMat[vname];
										options[vcname] = stdMat[vcname];
										options.litOptions.vertexColors = true;
								}
						}
						if (stdMat[mname]) {
								let allow = true;
								if (stdMat[uname] === 0 && !hasUv0) allow = false;
								if (stdMat[uname] === 1 && !hasUv1) allow = false;
								if (allow) {
										const mapId = stdMat[mname].id;
										let identifier = uniqueTextureMap[mapId];
										if (identifier === undefined) {
												uniqueTextureMap[mapId] = p;
												identifier = p;
										}
										options[mname] = !!stdMat[mname];
										options[iname] = identifier;
										options[tname] = this._getMapTransformID(stdMat.getUniform(tname), stdMat[uname]);
										options[cname] = stdMat[cname];
										options[uname] = stdMat[uname];
								}
						}
				}
		}
		_updateMinOptions(options, stdMat, pass) {
				const isPrepass = pass === SHADER_PREPASS;
				options.litOptions.opacityShadowDither = isPrepass ? stdMat.opacityDither : stdMat.opacityShadowDither;
				options.litOptions.linearDepth = isPrepass;
				options.litOptions.lights = [];
		}
		_updateMaterialOptions(options, stdMat, scene) {
				const useSpecular = !!(stdMat.useMetalness || stdMat.specularMap || stdMat.sphereMap || stdMat.cubeMap || notBlack(stdMat.specular) || stdMat.specularityFactor > 0 && stdMat.useMetalness || stdMat.enableGGXSpecular || stdMat.clearCoat > 0);
				const useSpecularColor = !stdMat.useMetalness || stdMat.useMetalnessSpecularColor;
				const specularTint = useSpecular && (stdMat.specularTint || !stdMat.specularMap && !stdMat.specularVertexColor) && notWhite(stdMat.specular);
				const specularityFactorTint = useSpecular && stdMat.useMetalnessSpecularColor && (stdMat.specularityFactorTint || stdMat.specularityFactor < 1 && !stdMat.specularityFactorMap);
				const isPackedNormalMap = (texture)=>texture ? texture.format === PIXELFORMAT_DXT5 || texture.type === TEXTURETYPE_SWIZZLEGGGR : false;
				const equalish = (a, b)=>Math.abs(a - b) < 1e-4;
				options.specularTint = specularTint;
				options.specularityFactorTint = specularityFactorTint;
				options.metalnessTint = stdMat.useMetalness && stdMat.metalness < 1;
				options.glossTint = true;
				options.diffuseEncoding = stdMat.diffuseMap?.encoding;
				options.diffuseDetailEncoding = stdMat.diffuseDetailMap?.encoding;
				options.emissiveEncoding = stdMat.emissiveMap?.encoding;
				options.lightMapEncoding = stdMat.lightMap?.encoding;
				options.packedNormal = isPackedNormalMap(stdMat.normalMap);
				options.refractionTint = equalish(stdMat.refraction, 1.0);
				options.refractionIndexTint = equalish(stdMat.refractionIndex, 1.0 / 1.5);
				options.thicknessTint = stdMat.useDynamicRefraction && stdMat.thickness !== 1.0;
				options.specularEncoding = stdMat.specularMap?.encoding;
				options.sheenEncoding = stdMat.sheenMap?.encoding;
				options.aoMapUv = stdMat.aoUvSet;
				options.aoDetail = !!stdMat.aoDetailMap;
				options.diffuseDetail = !!stdMat.diffuseDetailMap;
				options.normalDetail = !!stdMat.normalMap;
				options.normalDetailPackedNormal = isPackedNormalMap(stdMat.normalDetailMap);
				options.diffuseDetailMode = stdMat.diffuseDetailMode;
				options.aoDetailMode = stdMat.aoDetailMode;
				options.clearCoatGloss = !!stdMat.clearCoatGloss;
				options.clearCoatPackedNormal = isPackedNormalMap(stdMat.clearCoatNormalMap);
				options.iorTint = equalish(stdMat.refractionIndex, 1.0 / 1.5);
				if (scene.forcePassThroughSpecular) {
						options.specularEncoding = 'linear';
						options.sheenEncoding = 'linear';
				}
				options.iridescenceTint = stdMat.iridescence !== 1.0;
				options.glossInvert = stdMat.glossInvert;
				options.sheenGlossInvert = stdMat.sheenGlossInvert;
				options.clearCoatGlossInvert = stdMat.clearCoatGlossInvert;
				options.useSpecularColor = useSpecularColor;
				options.litOptions.separateAmbient = false;
				options.litOptions.pixelSnap = stdMat.pixelSnap;
				options.litOptions.ambientSH = !!stdMat.ambientSH;
				options.litOptions.twoSidedLighting = stdMat.twoSidedLighting;
				options.litOptions.occludeSpecular = stdMat.occludeSpecular;
				options.litOptions.occludeSpecularFloat = stdMat.occludeSpecularIntensity !== 1.0;
				options.litOptions.useMsdf = !!stdMat.msdfMap;
				options.litOptions.msdfTextAttribute = !!stdMat.msdfTextAttribute;
				options.litOptions.alphaToCoverage = stdMat.alphaToCoverage;
				options.litOptions.opacityFadesSpecular = stdMat.opacityFadesSpecular;
				options.litOptions.opacityDither = stdMat.opacityDither;
				options.litOptions.cubeMapProjection = stdMat.cubeMapProjection;
				options.litOptions.occludeDirect = stdMat.occludeDirect;
				options.litOptions.useSpecular = useSpecular;
				options.litOptions.useSpecularityFactor = (specularityFactorTint || !!stdMat.specularityFactorMap) && stdMat.useMetalnessSpecularColor;
				options.litOptions.enableGGXSpecular = stdMat.enableGGXSpecular;
				options.litOptions.fresnelModel = stdMat.fresnelModel;
				options.litOptions.useRefraction = (stdMat.refraction || !!stdMat.refractionMap) && (stdMat.useDynamicRefraction || options.litOptions.reflectionSource !== REFLECTIONSRC_NONE);
				options.litOptions.useClearCoat = !!stdMat.clearCoat;
				options.litOptions.useSheen = stdMat.useSheen;
				options.litOptions.useIridescence = stdMat.useIridescence && stdMat.iridescence !== 0.0;
				options.litOptions.useMetalness = stdMat.useMetalness;
				options.litOptions.useDynamicRefraction = stdMat.useDynamicRefraction;
				options.litOptions.dispersion = stdMat.dispersion > 0;
				options.litOptions.shadowCatcher = stdMat.shadowCatcher;
		}
		_updateEnvOptions(options, stdMat, scene, cameraShaderParams) {
				options.litOptions.fog = stdMat.useFog ? cameraShaderParams.fog : FOG_NONE;
				options.litOptions.gamma = cameraShaderParams.shaderOutputGamma;
				options.litOptions.toneMap = stdMat.useTonemap ? cameraShaderParams.toneMapping : TONEMAP_NONE;
				let usingSceneEnv = false;
				if (stdMat.envAtlas && stdMat.cubeMap) {
						options.litOptions.reflectionSource = REFLECTIONSRC_ENVATLASHQ;
						options.litOptions.reflectionEncoding = stdMat.envAtlas.encoding;
						options.litOptions.reflectionCubemapEncoding = stdMat.cubeMap.encoding;
				} else if (stdMat.envAtlas) {
						options.litOptions.reflectionSource = REFLECTIONSRC_ENVATLAS;
						options.litOptions.reflectionEncoding = stdMat.envAtlas.encoding;
				} else if (stdMat.cubeMap) {
						options.litOptions.reflectionSource = REFLECTIONSRC_CUBEMAP;
						options.litOptions.reflectionEncoding = stdMat.cubeMap.encoding;
				} else if (stdMat.sphereMap) {
						options.litOptions.reflectionSource = REFLECTIONSRC_SPHEREMAP;
						options.litOptions.reflectionEncoding = stdMat.sphereMap.encoding;
				} else if (stdMat.useSkybox && scene.envAtlas && scene.skybox) {
						options.litOptions.reflectionSource = REFLECTIONSRC_ENVATLASHQ;
						options.litOptions.reflectionEncoding = scene.envAtlas.encoding;
						options.litOptions.reflectionCubemapEncoding = scene.skybox.encoding;
						usingSceneEnv = true;
				} else if (stdMat.useSkybox && scene.envAtlas) {
						options.litOptions.reflectionSource = REFLECTIONSRC_ENVATLAS;
						options.litOptions.reflectionEncoding = scene.envAtlas.encoding;
						usingSceneEnv = true;
				} else if (stdMat.useSkybox && scene.skybox) {
						options.litOptions.reflectionSource = REFLECTIONSRC_CUBEMAP;
						options.litOptions.reflectionEncoding = scene.skybox.encoding;
						usingSceneEnv = true;
				} else {
						options.litOptions.reflectionSource = REFLECTIONSRC_NONE;
						options.litOptions.reflectionEncoding = null;
				}
				if (stdMat.ambientSH) {
						options.litOptions.ambientSource = AMBIENTSRC_AMBIENTSH;
						options.litOptions.ambientEncoding = null;
				} else {
						const envAtlas = stdMat.envAtlas || (stdMat.useSkybox && scene.envAtlas ? scene.envAtlas : null);
						if (envAtlas && !stdMat.sphereMap) {
								options.litOptions.ambientSource = AMBIENTSRC_ENVALATLAS;
								options.litOptions.ambientEncoding = envAtlas.encoding;
						} else {
								options.litOptions.ambientSource = AMBIENTSRC_CONSTANT;
								options.litOptions.ambientEncoding = null;
						}
				}
				options.litOptions.skyboxIntensity = usingSceneEnv;
				options.litOptions.useCubeMapRotation = usingSceneEnv && scene._skyboxRotationShaderInclude;
		}
		_updateLightOptions(options, scene, stdMat, objDefs, sortedLights) {
				options.lightMap = false;
				options.lightMapChannel = '';
				options.lightMapUv = 0;
				options.lightMapTransform = 0;
				options.litOptions.lightMapWithoutAmbient = false;
				options.dirLightMap = false;
				if (objDefs) {
						options.litOptions.noShadow = (objDefs & SHADERDEF_NOSHADOW) !== 0;
						if ((objDefs & SHADERDEF_LM) !== 0) {
								options.lightMapEncoding = scene.lightmapPixelFormat === PIXELFORMAT_RGBA8 ? 'rgbm' : 'linear';
								options.lightMap = true;
								options.lightMapChannel = 'rgb';
								options.lightMapUv = 1;
								options.lightMapTransform = 0;
								options.litOptions.lightMapWithoutAmbient = !stdMat.lightMap;
								if ((objDefs & SHADERDEF_DIRLM) !== 0) {
										options.dirLightMap = true;
								}
								if ((objDefs & SHADERDEF_LMAMBIENT) !== 0) {
										options.litOptions.lightMapWithoutAmbient = false;
								}
						}
				}
				if (stdMat.useLighting) {
						const lightsFiltered = [];
						const mask = objDefs ? objDefs >> 16 : MASK_AFFECT_DYNAMIC;
						options.litOptions.lightMaskDynamic = !!(mask & MASK_AFFECT_DYNAMIC);
						if (sortedLights) {
								LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_DIRECTIONAL, sortedLights[LIGHTTYPE_DIRECTIONAL], lightsFiltered, mask);
								if (!scene.clusteredLightingEnabled) {
										LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_OMNI, sortedLights[LIGHTTYPE_OMNI], lightsFiltered, mask);
										LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_SPOT, sortedLights[LIGHTTYPE_SPOT], lightsFiltered, mask);
								}
						}
						options.litOptions.lights = lightsFiltered;
				} else {
						options.litOptions.lights = [];
				}
				if (options.litOptions.lights.length === 0 && !scene.clusteredLightingEnabled) {
						options.litOptions.noShadow = true;
				}
		}
		_getMapTransformID(xform, uv) {
				if (!xform) return 0;
				let xforms = this._mapXForms[uv];
				if (!xforms) {
						xforms = [];
						this._mapXForms[uv] = xforms;
				}
				for(let i = 0; i < xforms.length; i++){
						if (arraysEqual(xforms[i][0].value, xform[0].value) && arraysEqual(xforms[i][1].value, xform[1].value)) {
								return i + 1;
						}
				}
				return xforms.push(xform);
		}
}

export { StandardMaterialOptionsBuilder };
