import * as Cesium from "cesium";

const boxSize = 25;

// Create a Material Appearance using custom shader code.
function createMaterialAppearance() {
  // Create custom materials.
  const customMaterial = new Cesium.Material({
    translucent: false,
    fabric: {
      type: "CustomBoxShader",
      // NOTE: Uniforms in the Material fabric can only be used directly in the fabric shader source.
      // In the final shader code, the variable name will be overwritten.
      uniforms: {
        time: 0.0,
      },
      source: `
                    // Uniform variables added by the fabric need to be explicitly declared.
                    uniform float time;

                    // Define a function to return the value of the fabric uniform.
                    // This makes it accessible in the final shader code, where the modified uniform name is unknown.
                    float getUniformTimeOfMaterial(){
                        return time;
                    }

                    czm_material czm_getMaterial(czm_materialInput materialInput) {
                        czm_material material = czm_getDefaultMaterial(materialInput);
                        
                        // Within the Material fabric source, you can directly use the uniform names as declared.
                        // material.alpha = 0.33 + (sin(time * 0.03) + 1.0)  / 3.0;
                        
                        return material;
                    }
                    `,
    },
  });

  const appearance = new Cesium.MaterialAppearance({
    material: customMaterial,
    flat: false,
    faceForward: true,
    translucent: true,
    closed: true,
    vertexShaderSource: `
                in vec3 position3DHigh;
                in vec3 position3DLow;
                in vec3 normal;
                in vec2 st;
                in float batchId;

                out vec3 v_positionEC;
                out vec3 v_normalEC;
                out vec2 v_st;
                out vec3 v_position;

                // The variables passed by appearance.uniforms need to be explicitly declared.
                uniform float frameNumber;

                mat3 rotateZ(float angle) {
                    float c = cos(angle);
                    float s = sin(angle);
                    return mat3(
                        c, -s, 0.0,   
                        s,  c, 0.0,  
                        0.0, 0.0, 1.0
                    );
                }

                void main()
                {
                    vec4 p = czm_computePosition();
                    // Get the origin Model Coordinates (MC) from the position. This will lose some precision.
                    vec3 cameraPositionMC = czm_encodedCameraPositionMCHigh + czm_encodedCameraPositionMCLow;
                    vec3 originMC = p.xyz + cameraPositionMC;

                    // Use uniform frameNumber from Appearance to rotate the box.
                    mat3 rotation = rotateZ(frameNumber * 0.01);
                    originMC = rotation * originMC;

                    v_position = originMC;

                    // Restore to coordinates relative to the camera.
                    originMC = originMC - cameraPositionMC;
                    p.xyz = originMC;
                    
                    v_positionEC = (czm_modelViewRelativeToEye * p).xyz;
                    v_normalEC = czm_normal * normal;
                    v_st = st;

                    gl_Position = czm_modelViewProjectionRelativeToEye * p;
                }`,
    fragmentShaderSource: `
                in vec3 v_positionEC;
                in vec3 v_normalEC;
                in vec2 v_st;
                in vec3 v_position;
                
                // The variables passed by appearance.uniforms need to be explicitly declared.
                uniform vec3 customColor; 
                uniform float boxSize;

                void main()
                {
                    vec3 positionToEyeEC = -v_positionEC;
                    vec3 normalEC = normalize(v_normalEC);

                #ifdef FACE_FORWARD
                    normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);
                #endif

                    czm_materialInput materialInput;
                    materialInput.normalEC = normalEC;
                    materialInput.positionToEyeEC = positionToEyeEC;
                    materialInput.st = v_st;
                    czm_material material = czm_getMaterial(materialInput);
                    
                    // Use uniform customColor from Appearance to change color
                    material.diffuse  = customColor; 

                    // Two random transparent bright lines that are far apart from each other.
                    // The value of the uniform variable in Material.uniforms can be obtained through the helper function in Material here.
                    int uniformOfMaterial = int(getUniformTimeOfMaterial() * 4.0);
                    float s1 =  boxSize / 10.0; 
                    float delta = abs(abs(v_position.z) - float(uniformOfMaterial % int((boxSize / 2.0 - s1) * 100.0)) / 100.0);
                    if(delta < s1)
                    {
                        float scale = 1.0 - delta / s1;
                        material.diffuse  = vec3(1.0) * scale; 
                        material.alpha = scale;
                    }

                #ifdef FLAT
                    out_FragColor = vec4(material.diffuse + material.emission, material.alpha);
                #else
                    out_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC);
                #endif              
                }
                `,
    materialCacheKey: "my-box-material-appearance",
  });

  // Add uniform variables in the MaterialAppearance layer.
  // These can be used directly in both vertex and fragment shaders.
  // The name of the variable will not change in the final shader.
  const color = new Cesium.Color(1.0, 1.0, 0.0, 1.0);
  appearance.uniforms = {
    frameNumber: 1.0, // Used in vertex shader.
    customColor: color, // Used in fragment shader.
    boxSize: boxSize, // Used in fragment shader.
  };

  return appearance;
}

// Create a Box primitive with custom material appearance.
function createBoxPrimitive(destination, appearance) {
  const boxGeometry = Cesium.BoxGeometry.fromDimensions({
    dimensions: new Cesium.Cartesian3(boxSize, boxSize, boxSize),
  });

  const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(destination);

  // Wrap the geometry as GeometryInstance.
  const boxInstance = new Cesium.GeometryInstance({
    geometry: boxGeometry,
  });

  // Create a Primitive and add it to the scene.
  const primitive = new Cesium.Primitive({
    geometryInstances: boxInstance,
    appearance: appearance,
    asynchronous: false,
    modelMatrix: modelMatrix,
  });

  return primitive;
}

// Update uniforms every frame.
function updateAppearance(appearance) {
  const t = appearance.material.uniforms.time++;
  appearance.uniforms.frameNumber++;

  const { customColor } = appearance.uniforms;
  customColor.red = Math.sin(t * 0.01) ** 2 / 1.5;
  customColor.green = Math.sin(t * 0.01 + (2 * Math.PI) / 3) ** 2 / 1.5;
  customColor.blue = Math.sin(t * 0.01 + (4 * Math.PI) / 3) ** 2 / 1.5;
}

// Initialize Viewer.
const viewer = new Cesium.Viewer("cesiumContainer");
const scene = viewer.scene;

viewer.clock.currentTime.secondsOfDay = 65398;
scene.globe.enableLighting = true;
scene.fog.enabled = true;

const destination = {
  x: -2280236.925141378,
  y: 5006991.049189922,
  z: 3215839.258024074,
};

const appearance = createMaterialAppearance();
const primitive = createBoxPrimitive(destination, appearance);
scene.preRender.addEventListener(() => {
  updateAppearance(appearance);
});

viewer.scene.primitives.add(primitive);
viewer.camera.lookAt(
  destination,
  new Cesium.HeadingPitchRange(6.283185307179577, -0.4706003213405664, 100),
);
