import { GLShader, defaultValue } from "../../glCore";
import { mapType, generateUniformAccessObject } from "../../glCore/shader";

export enum shaderReplaceStr {
    POINT_LIGHTS_NUM = "POINT_LIGHTS_NUM",
    DIR_LIGHTS_NUM = "DIR_LIGHTS_NUM",
}

//着色器变量统一驼峰
const lightVert = [
    "attribute vec4 aPosition;",//顶点位置信息
    "attribute vec4 aColor;",//顶点颜色数据
    "attribute vec2 aTextureCoord;",//顶点纹理坐标，
    "attribute vec3 aNormal;",//顶点法向量，对于模型得特殊计算

    "uniform mat4 uViewMatrix;",//视图矩阵，camera的worldMatrixInverse
    "uniform mat4 uProjectionMatrix;",//投影矩阵，camera的projectionMatrix
    "uniform mat4 uModelMatrix;",//模型矩阵
    "uniform mat4 uNormalMatrix;",//模型矩阵uModelMatrix的逆的转置，若正交就是模型矩阵，估计还要乘上视图举证，外面乘，先乘再逆转置

    "varying vec4 vColor;",//只有颜色数据需要传，或者考虑是否放材质里
    "varying vec2 vTextureCoord;",//传到着色器的纹理坐标
    "varying vec3 vNormal;",//计算所得法向量
    "varying vec3 vPosition;",//每个顶点的光线方向

    "void main() {",
    "gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * aPosition;",
    "vColor = aColor;",
    "vTextureCoord = aTextureCoord;",
    // "vPosition = vec3(uModelMatrix * aPosition);",
    "vPosition = vec3(uViewMatrix * uModelMatrix * aPosition);",
    "vNormal = normalize(vec3(uNormalMatrix * vec4(aNormal,1.0)));",
    "}"
].join("\n")


const lightFrag = [
    "precision mediump float;",//片元得加精度

    //点光源参数
    `#if ${shaderReplaceStr.POINT_LIGHTS_NUM} > 0`,
    "#define saturate(a) clamp( a, 0.0, 1.0 )",
    "float calcLightAttenuation( float lightDistance, float cutoffDistance, float decayExponent ) {",
    "if ( decayExponent > 0.0 ) {",
    "return pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );",
    "}",
    "return 1.0;",
    "}",

    "struct PointLight {",
    "vec3 color;",//光源颜色
    "vec3 position;",//点光源位置
    "float distance;",//最大光源距离
    "float decay;",//衰减系数，默认1，最佳2
    "};",
    `uniform PointLight pointLights[${shaderReplaceStr.POINT_LIGHTS_NUM}];`,
    "#endif",

    //方向光参数
    `#if ${shaderReplaceStr.DIR_LIGHTS_NUM} > 0`,
    "struct DirectionalLight {",//反向光不会衰减
    "vec3 direction;",
    "vec3 color;",
    "};",
    `uniform DirectionalLight directionalLights[${shaderReplaceStr.DIR_LIGHTS_NUM}];`,
    "#endif",

    //环境光
    "uniform vec3 uAmbientLightColor;",//环境光颜色，多个环境光都是一起计算好传入的，不循环计算

    //材质参数
    'uniform sampler2D uMap;',//材质上的贴图纹理
    "uniform vec3 uMatColor;", //材质上的颜色，以后区分发散颜色等等
    "uniform float uMatAlpha;", //材质上的透明度


    "varying vec4 vColor;",
    "varying vec2 vTextureCoord;",
    "varying vec3 vNormal;",
    "varying vec3 vPosition;",

    "void main() {",
    //计算纹理颜色
    "vec4 mapColor = texture2D(uMap, vTextureCoord);",
    // "mapColor.rgb *=  mapColor.rgb;",//为啥rgb乘一下
    //计算顶点颜色
    "vec4 color = mapColor * vColor;",//叠加顶点颜色和纹理颜色
    //计算材质颜色
    "vec4 matColor = vec4( uMatColor, uMatAlpha );",
    //总颜色
    "color *=matColor;",

    //声明光源颜色  现在两种计算光源颜色的方法不是正规的，先调试效果，效果好就不管了，效果不好调回原来的
    "vec3 totalLightColor = vec3( 0.0 );",
    //计算点光源
    `#if ${shaderReplaceStr.POINT_LIGHTS_NUM} > 0`,
    `for ( int i = 0; i < ${shaderReplaceStr.POINT_LIGHTS_NUM}; i ++ ) {`,
    "vec3 lightDirection = normalize(pointLights[i].position - vPosition);",
    "float attenuation = calcLightAttenuation( length( lightDirection ), pointLights[ i ].distance, pointLights[ i ].decay );",
    "lightDirection = normalize(lightDirection);",
    "float pointDiffuseWeightFull = max( dot( vNormal, lightDirection ), 0.0 );",
    "float pointDiffuseWeightHalf = max( 0.5 * dot( vNormal, lightDirection ) + 0.5, 0.0 );",
    "vec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), vec3(0.75, 0.375, 0.1875) );",
    "totalLightColor += pointLights[i].color * (pointDiffuseWeight * attenuation);",
    "}",
    "#endif",

    //计算方向光
    `#if ${shaderReplaceStr.DIR_LIGHTS_NUM} > 0`,
    `for( int i = 0; i < ${shaderReplaceStr.POINT_LIGHTS_NUM}; i++ ) {`,
    "vec3 dirVector = directionalLights[ i ].direction;",
    "float dirDiffuseWeightFull = max( dot( vNormal, dirVector ), 0.0 );",
    "float dirDiffuseWeightHalf = max( 0.5 * dot( vNormal, dirVector ) + 0.5, 0.0 );",
    "vec3 dirDiffuseWeight = mix( vec3 ( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), vec3(0.75, 0.375, 0.1875) );",
    "totalLightColor += directionalLights[ i ].color * dirDiffuseWeight;",
    "}",
    "#endif",

    //计算环境光颜色，是否要乘材质本身颜色，待定，效果待调整
    "totalLightColor += uAmbientLightColor;",

    "gl_FragColor = vec4(totalLightColor * color.rgb, color.a);",
    "}"
].join("\n")

/**
 * 纯色的，带光照的，带贴图的着色器，根据传入的
 * 基本不能缓存，只能根据场景光照改变判断是否需要重新初始化着色器
 * 如果只是光照参数改变（颜色，位置等）不初始化着色器，只改相应uniform值
 * 如果有点光源添加或移除，需要初始化着色器
 * 环境光的添加和移除也不初始化着色器，没有就是0，0，0
 * 初始化着色器后，尽量销毁原先着色器，缓存几率不大，或者按照光源数量缓存着色器程序，是否有必要？
 * 每次一种材质，基本只会是一个着色器，所以是否把着色器挂到材质的属性上去
 */
export class LightShader extends GLShader {
    /**
     * 作为该着色器的标识
     */
    _glShaderKey: string;
    constructor(
        gl: WebGLRenderingContext,
        pointLightsNum: number = 0,
        dirLightsNum: number = 0
    ) {
        //替换光源数据
        var frag = lightFrag
            .replace(new RegExp(shaderReplaceStr.POINT_LIGHTS_NUM, "gm"), pointLightsNum + "")
            // .replace(/DIR_LIGHTS_NUM/g, dirLightsNum + "")
            .replace(new RegExp(shaderReplaceStr.DIR_LIGHTS_NUM, "gm"), dirLightsNum + "")

        super(gl, lightVert, frag)
        this._glShaderKey = "LightShader" + pointLightsNum + dirLightsNum;//唯一标识

        //这两个需要重写下
        //修改uniform
        this.uniformData = extractUniforms(gl, this.program);
        //是否需要按照链式的点下去，链式的能点下去吧，但是数组索引自己拼字段
        this.uniforms = generateUniformAccessObject(gl, this.uniformData);
    }
}


//复制一个不清除中括号的
function extractUniforms(gl: WebGLRenderingContext, program: WebGLProgram) {
    var uniforms = {};

    var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);

    for (var i = 0; i < totalUniforms; i++) {
        var uniformData: WebGLActiveInfo = gl.getActiveUniform(program, i);
        var name = uniformData.name//.replace(/\[.*?\]/, "");
        var type = mapType(gl, uniformData.type);

        uniforms[name] = {
            type: type,
            size: uniformData.size,
            location: gl.getUniformLocation(program, name),
            value: defaultValue(type, uniformData.size)
        };
    }

    return uniforms;
};