import { GLShader, defaultValue } from "../../glCore";
import { WebglRenderer } from "../../2d/renderers/WebglRenderer";
import { BaseMaterial } from "../materials/BaseMaterial";
import { LightsConfig } from "../Scene3D"
import { generateUniformAccessObject, mapType } from "../../glCore/shader";


/**
 * 根据渲染器，材质，灯光获取着色器
 * @param render 
 * @param material 
 * @param lights 
 */
export function getCusShader(render: WebglRenderer, material: BaseMaterial, lights: LightsConfig) {
    //所有参数
    var parameters: ShaderParametersInt = {
        pointLightsNum: lights.pointLights.length,
        dirLightsNum: lights.directionalLights.length,
        morphTargets: material.morphTargets,
        morphNormals: material.morphNormals,
        lightAffect: material._lightAffect,
    }
    //计算code，
    var shaderKey = getShaderKey(parameters);
    var cusShader: CusShader;

    for (var i = 0; i < cacheShaders.length; i++) {
        if (cacheShaders[i]._glShaderKey === shaderKey) {
            cusShader = cacheShaders[i];
            ++cusShader.usedTimes;
            break;
        }
    }
    if (!cusShader) {
        cusShader = new CusShader(render.gl, parameters)
        cusShader._glShaderKey = shaderKey;
        //存下
        cacheShaders.push(cusShader)
    }
    return cusShader
}

/**
 * 根据参数获取shaderKey
 * @param parameters 
 */
function getShaderKey(parameters: ShaderParametersInt) {
    var array = [];
    for (var i = 0; i < parameterNames.length; i++) {
        array.push(parameters[parameterNames[i]]);
    }
    return array.join();
}



class CusShader extends GLShader {
    /**
     * 以后回收时用
     */
    usedTimes: number;
    /**
     * 作为该着色器的标识
     */
    _glShaderKey: string;
    /**
     * 
     * @param gl 
     * @param parameters 
     */
    constructor(
        gl: WebGLRenderingContext,
        parameters: ShaderParametersInt
    ) {
        //预处理参数
        var frontVert = [
            parameters.lightAffect ? '#define USE_LIGHT' : '',
            parameters.morphTargets ? '#define USE_MORPHTARGETS' : '',
            parameters.morphNormals ? '#define USE_MORPHNORMALS' : '',
        ].filter(e => e !== '').join('\n');
        //不为空加个换行
        if (frontVert) frontVert += "\n";

        var frontFrag = [
            parameters.lightAffect ? '#define USE_LIGHT' : '',
        ].filter(e => e !== '').join('\n');
        if (frontFrag) frontFrag += "\n";

        var frag = FRAG
            .replace(new RegExp(shaderReplaceStr.POINT_LIGHTS_NUM, "gm"), parameters.pointLightsNum + "")
            .replace(new RegExp(shaderReplaceStr.DIR_LIGHTS_NUM, "gm"), parameters.dirLightsNum + "")

        super(gl, frontVert + VERT, frontFrag + frag)

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

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

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

    for (var i = 0; i < totalUniforms; i++) {//morphTargetInfluences
        var uniformData: WebGLActiveInfo = gl.getActiveUniform(program, i);

        //morphTargetInfluences直接传一个数组吧，还是说不区分，也直接按morphTargetInfluences[0]传
        var name = uniformData.name.indexOf("morphTargetInfluences") == 0 ?
            uniformData.name.replace(/\[.*?\]/, "") : uniformData.name;
        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;
};


/**
 * 存储用过着色器程序
 * 根据自己的code取，code根据参数拼
 * 用数组吧，考虑为啥不用对象键值
 */
const cacheShaders: CusShader[] = [];

/**
 * 影响着色器编译的所有的参数
 * 
 * 正反面影响法线和光照，还需要定义正反面光照及两面光照，以后再说
 * 环境贴图也需要法线，不止是光照需要，以后再说，到时normal必传吧
 */
const parameterNames = [
    "pointLightsNum",
    "dirLightsNum",
    "morphTargets",
    "morphNormals",
    "lightAffect",
]

/**
 * 着色器编译参数接口
 */
interface ShaderParametersInt {
    pointLightsNum: number,
    dirLightsNum: number,
    morphTargets: boolean,
    morphNormals: boolean,
    lightAffect: boolean
}

/**
 * 着色器替换的一些文案
 */
enum shaderReplaceStr {
    POINT_LIGHTS_NUM = "POINT_LIGHTS_NUM",
    DIR_LIGHTS_NUM = "DIR_LIGHTS_NUM",
}

const VERT = [

    "attribute vec3 aPosition;",//顶点位置信息
    "attribute vec3 aColor;",//顶点颜色数据
    "attribute vec2 aTextureCoord;",//纹理坐标，
    // 'attribute vec3 aNormal;',//法线数据会和上述三个放在同一个数组里，但是不一定进attribute通道

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

    "varying vec3 vColor;",//只有颜色数据需要传，或者考虑是否放材质里
    "varying vec2 vTextureCoord;",//传到着色器的纹理坐标
    //灯光的加一起了
    '#ifdef USE_LIGHT',
    '   attribute vec3 aNormal;',//法线数据会和上述三个放在同一个数组里，但是不一定进attribute通道
    "   uniform mat4 uNormalMatrix;",//模型矩阵uModelMatrix的逆的转置，若正交就是模型矩阵，估计还要乘上视图举证，外面乘，先乘再逆转置
    "   varying vec3 vNormal;",//计算所得法向量
    "   varying vec3 vPosition;",//每个顶点的光线方向
    "   varying vec3 vViewPosition;", //传入计算镜面光
    '#endif',

    //变形顶点数据，单独建数组传，还是放入vao
    '#ifdef USE_MORPHTARGETS',
    '	attribute vec3 morphTarget0;',
    '	attribute vec3 morphTarget1;',
    '	attribute vec3 morphTarget2;',
    '	attribute vec3 morphTarget3;',
    '	#ifdef USE_MORPHNORMALS',
    '		attribute vec3 morphNormal0;',
    '		attribute vec3 morphNormal1;',
    '		attribute vec3 morphNormal2;',
    '		attribute vec3 morphNormal3;',
    "       uniform float morphTargetInfluences[ 4 ];",
    '	#else',
    '		attribute vec3 morphTarget4;',
    '		attribute vec3 morphTarget5;',
    '		attribute vec3 morphTarget6;',
    '		attribute vec3 morphTarget7;',
    "       uniform float morphTargetInfluences[ 8 ];",
    '	#endif',
    '#endif',

    "void main() {",
    //需要变形的
    "vec3 transformed = vec3( aPosition );",
    //考虑变形,顶点位置
    "#ifdef USE_MORPHTARGETS",
    "   transformed += ( morphTarget0 - aPosition ) * morphTargetInfluences[ 0 ];",
    "   transformed += ( morphTarget1 - aPosition ) * morphTargetInfluences[ 1 ];",
    "   transformed += ( morphTarget2 - aPosition ) * morphTargetInfluences[ 2 ];",
    "   transformed += ( morphTarget3 - aPosition ) * morphTargetInfluences[ 3 ];",
    "   #ifndef USE_MORPHNORMALS",
    "   transformed += ( morphTarget4 - aPosition ) * morphTargetInfluences[ 4 ];",
    "   transformed += ( morphTarget5 - aPosition ) * morphTargetInfluences[ 5 ];",
    "   transformed += ( morphTarget6 - aPosition ) * morphTargetInfluences[ 6 ];",
    "   transformed += ( morphTarget7 - aPosition ) * morphTargetInfluences[ 7 ];",
    "   #endif",
    "#endif",

    //常规的
    "vec4 mvPosition = uViewMatrix * uModelMatrix * vec4( transformed, 1.0 );",
    "gl_Position = uProjectionMatrix * mvPosition;",
    "vColor = aColor;",
    "vTextureCoord = aTextureCoord;",

    //光照的
    '#ifdef USE_LIGHT',
    //考虑变形，法线
    "   vec3 objectNormal = vec3( aNormal );",
    "   #ifdef USE_MORPHNORMALS",
    "   objectNormal += ( morphNormal0 - aNormal ) * morphTargetInfluences[ 0 ];",
    "   objectNormal += ( morphNormal1 - aNormal ) * morphTargetInfluences[ 1 ];",
    "   objectNormal += ( morphNormal2 - aNormal ) * morphTargetInfluences[ 2 ];",
    "   objectNormal += ( morphNormal3 - aNormal ) * morphTargetInfluences[ 3 ];",
    "   #endif",
    "   vViewPosition = -mvPosition.xyz;",
    "   vPosition = vec3(uModelMatrix * vec4(transformed,1.0));",
    "   vNormal = normalize(vec3(uNormalMatrix * vec4(objectNormal,1.0)));",
    '#endif',

    "}"
].join("\n")


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

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

    "varying vec3 vColor;",
    "varying vec2 vTextureCoord;",
    //灯光参数
    '#ifdef USE_LIGHT',
    "   varying vec3 vNormal;",
    "   varying vec3 vPosition;",
    "   varying vec3 vViewPosition;",
    //点光源参数
    `   #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;",//环境光颜色，多个环境光都是一起计算好传入的，不循环计算

    '#endif',

    "void main() {",
    //颜色计算，通用
    //计算纹理颜色
    "   vec4 mapColor = texture2D(uMap, vTextureCoord);",
    //计算顶点颜色
    "   vec4 color = mapColor *vec4(vColor,1.0);",//叠加顶点颜色和纹理颜色
    //计算材质颜色
    "   vec4 matColor = vec4( uMatColor, uMatAlpha );",
    //总颜色
    "   color *=matColor;",
    //先把颜色算了
    "   gl_FragColor = color;",
    //光照颜色计算
    '   #ifdef USE_LIGHT',
    "      vec3 totalDiffuseLight = vec3( 0.0 );",
    "      vec3 totalSpecularLight = vec3( 0.0 );",
    "      vec3 viewDir = normalize(vViewPosition);",// 计算观察方向向量
    //计算点光源
    `      #if ${shaderReplaceStr.POINT_LIGHTS_NUM} > 0`,
    `         for ( int i = 0; i < ${shaderReplaceStr.POINT_LIGHTS_NUM}; i ++ ) {`,
    //漫反射
    "            vec3 lightDirection = pointLights[i].position + vViewPosition;", //-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) );",
    "            totalDiffuseLight += pointLights[i].color * (pointDiffuseWeightFull * attenuation);",

    //镜面反射
    "            vec3 reflectDir = reflect(-lightDirection, vNormal);",// 计算反射方向向量
    "            float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);",//发散系数待定
    "            totalSpecularLight += 1.0 * spec * pointLights[i].color;",//反射强度系数待定
    "         }",
    "      #endif",

    //计算方向光
    `      #if ${shaderReplaceStr.DIR_LIGHTS_NUM} > 0`,
    `         for( int i = 0; i < ${shaderReplaceStr.DIR_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) );",
    "            totalDiffuseLight += directionalLights[ i ].color * dirDiffuseWeightFull;",

    //镜面反射，貌似没有，
    // "vec3 reflectDir1 = reflect(-directionalLights[ i ].direction, vNormal);",//待测试，是否加负号
    // "float spec1 = pow(max(dot(viewDir, reflectDir1), 0.0), 32.0);",
    // "totalSpecularLight += 1.0 * spec1 * directionalLights[i].color;",

    "         }",
    "      #endif",

    //计算环境光颜色，是否要乘材质本身颜色，待定，效果待调整，镜面反射是否要乘color.rgb，
    "      gl_FragColor.rgb = (totalDiffuseLight + uAmbientLightColor + totalSpecularLight) * color.rgb;",
    '   #endif',
    "}"
].join("\n")



