import {Component} from '@angular/core';
import {BabylonCanvas, RenderConfig, SceneEventData} from '../../../shared/components/render-canvas/babylon-canvas.component';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
import {ComputeShader, ShaderLanguage, StorageBuffer} from '@babylonjs/core';
import {PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL, PENDULUM_RENDER_COMPUTE_SHADER_WGSL, PENDULUM_VERTEX_SHADER_WGSL} from './pendulum.shader';
import {FormsModule} from '@angular/forms';
import {NgxSliderModule, Options} from '@angular-slider/ngx-slider';
import {DEFAULT_DAMPING, DEFAULT_G, DEFAULT_L1_LENGTH, DEFAULT_M1_MASS, DEFAULT_L2_LENGTH, DEFAULT_M2_MASS, DEFAULT_TRAIL_DECAY, MAX_DAMPING, MAX_G, MAX_LENGTH, MAX_MASS, MAX_TRAIL_DECAY, MIN_DAMPING, MIN_G, MIN_LENGTH, MIN_MASS, MIN_TRAIL_DECAY, IMPULSE_M2, IMPULSE_M1} from './pendulum.model';
import {TranslatePipe} from '@ngx-translate/core';
import {MatButton} from '@angular/material/button';
import {Information} from '../information/information';
import {AlgorithmInformation} from '../information/information.models';
import {UrlConstants} from '../../../constants/UrlConstants';

@Component({
  selector: 'app-pendulum',
  imports: [
    BabylonCanvas,
    MatCard,
    MatCardContent,
    MatCardHeader,
    MatCardTitle,
    FormsModule,
    NgxSliderModule,
    TranslatePipe,
    MatButton,
    Information,
  ],
  templateUrl: './pendulum.component.html',
  styleUrl: './pendulum.component.scss',
})
class PendulumComponent {

  // --- CONFIGURATION ---
  algoInformation: AlgorithmInformation = {
    title: 'PENDULUM.EXPLANATION.TITLE',
    entries: [
      {
        name: '',
        description: 'PENDULUM.EXPLANATION.EXPLANATION',
        link: UrlConstants.DOUBLE_PENDULUM_WIKI
      }
    ],
    disclaimer: 'PENDULUM.EXPLANATION.DISCLAIMER',
    disclaimerBottom: 'PENDULUM.EXPLANATION.DISCLAIMER_BOTTOM',
    disclaimerListEntry: ['PENDULUM.EXPLANATION.DISCLAIMER_1', 'PENDULUM.EXPLANATION.DISCLAIMER_2', 'PENDULUM.EXPLANATION.DISCLAIMER_3', 'PENDULUM.EXPLANATION.DISCLAIMER_4']
  };


  renderConfig: RenderConfig = {
    mode: '2D',
    initialViewSize: 2,
    shaderLanguage: ShaderLanguage.WGSL,
    vertexShader: PENDULUM_VERTEX_SHADER_WGSL,
    fragmentShader: PENDULUM_FRAGMENT_SHADER_WGSL,
    uniformNames: [],
    uniformBufferNames: []
  };

  trailDecayOptions: Options = {
    floor: MIN_TRAIL_DECAY,
    ceil: MAX_TRAIL_DECAY,
    logScale: false,
    step: 0.001,
    showTicks: false,
    hideLimitLabels: false,
    hidePointerLabels: false
  };

  gravityOptions: Options = {
    floor: MIN_G,
    ceil: MAX_G,
    logScale: false,
    step: 0.01,
    showTicks: false,
    hideLimitLabels: false,
    hidePointerLabels: false
  };

  dampingOptions: Options = {
    floor: MAX_DAMPING,
    ceil: MIN_DAMPING,
    logScale: false,
    step: 0.001,
    showTicks: false,
    hideLimitLabels: false,
    hidePointerLabels: false
  };

  lengthOptions: Options = {
    floor: MIN_LENGTH,
    ceil: MAX_LENGTH,
    logScale: false,
    step: 0.1,
    showTicks: false,
    hideLimitLabels: false,
    hidePointerLabels: false
  };

  massOptions: Options = {
    floor: MIN_MASS,
    ceil: MAX_MASS,
    logScale: false,
    step: 0.1,
    showTicks: false,
    hideLimitLabels: false,
    hidePointerLabels: false
  };

  // Central management of physics parameters
  readonly simParams = {
    time: 0,
    dt: 0.015,
    g: DEFAULT_G,
    m1: DEFAULT_M1_MASS,
    m2: DEFAULT_M2_MASS,
    l1: DEFAULT_L1_LENGTH,
    l2: DEFAULT_L2_LENGTH,
    damping: DEFAULT_DAMPING,
    trailDecay: DEFAULT_TRAIL_DECAY,
    impulseM1: 0.0,
    impulseM2: 0.0,
  };

  private currentSceneData: SceneEventData | null = null;

  onSceneReady(event: SceneEventData) {
    this.currentSceneData = event;
    this.createSimulation();
  }

  private createSimulation() {
    if (!this.currentSceneData){
      return;
    }
    const {engine, scene} = this.currentSceneData;
    engine.resize();

    const width = engine.getRenderWidth();
    const height = engine.getRenderHeight();
    const totalPixels = width * height;

    // --- 1. BUFFERS ---
    const pixelBuffer = new StorageBuffer(engine, totalPixels * 4);

    const stateBuffer = new StorageBuffer(engine, 4 * 4);
    stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); // Initial angles

    const paramsBuffer = new StorageBuffer(engine, 14 * 4);
    const paramsData = new Float32Array(14);

    // --- 2. SHADERS ---
    const csPhysics = new ComputeShader("physics", engine,
      {computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL},
      {bindingsMapping: {"state": {group: 0, binding: 0}, "p": {group: 0, binding: 1}}}
    );
    csPhysics.setStorageBuffer("state", stateBuffer);
    csPhysics.setStorageBuffer("p", paramsBuffer);

    const csRender = new ComputeShader("render", engine,
      {computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL},
      {bindingsMapping: {"pixelBuffer": {group: 0, binding: 0}, "p": {group: 0, binding: 1}, "state": {group: 0, binding: 2}}}
    );
    csRender.setStorageBuffer("pixelBuffer", pixelBuffer);
    csRender.setStorageBuffer("p", paramsBuffer);
    csRender.setStorageBuffer("state", stateBuffer);

    // --- 3. MATERIAL ---
    const plane = scene.getMeshByName("plane");
    if (plane?.material) {
      const mat = plane.material as any;
      mat.setStorageBuffer("pixelBuffer", pixelBuffer);
      mat.setStorageBuffer("p", paramsBuffer);
    }

    //remove old observables if available
    scene.onBeforeRenderObservable.clear();
    // --- 4. RENDER LOOP ---
    scene.onBeforeRenderObservable.add(() => {
      this.simParams.time += this.simParams.dt;

      const currentWidth = engine.getRenderWidth();
      const currentHeight = engine.getRenderHeight();

      // Fill parameter array (must match the exact order of the WGSL struct!)
      paramsData[0] = currentWidth;
      paramsData[1] = currentHeight;
      paramsData[2] = this.simParams.time;
      paramsData[3] = this.simParams.dt;
      paramsData[4] = this.simParams.g;
      paramsData[5] = this.simParams.m1;
      paramsData[6] = this.simParams.m2;
      paramsData[7] = this.simParams.l1;
      paramsData[8] = this.simParams.l2;
      paramsData[9] = this.simParams.damping;
      paramsData[10] = this.simParams.trailDecay;
      paramsData[11] = this.simParams.impulseM1;
      paramsData[12] = this.simParams.impulseM2;
      paramsData[13] = 0; // Pad

      this.resetImpulses();

      paramsBuffer.update(paramsData);

      // Trigger simulation and rendering
      csPhysics.dispatch(1, 1, 1);

      const dispatchCount = Math.ceil((currentWidth * currentHeight) / 64);
      csRender.dispatch(dispatchCount, 1, 1);
    });
  }

  private resetImpulses() {
    if (this.simParams.impulseM1 !== 0.0) {
      this.simParams.impulseM1 = 0;
    }

    if (this.simParams.impulseM2 !== 0.0) {
      this.simParams.impulseM2 = 0;
    }
  }

  pushPendulum(m1: boolean) {
    if (m1)
    {
      this.simParams.impulseM1 = IMPULSE_M1;
      return;
    }

    this.simParams.impulseM2 = IMPULSE_M2;
  }

  resetPendulum() {
    this.createSimulation();
  }
}



export default PendulumComponent
