import {AfterViewInit, Component, signal, ViewChild} from '@angular/core';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from "@angular/material/card";
import {TranslatePipe} from "@ngx-translate/core";
import {UrlConstants} from '../../../constants/UrlConstants';
import {Information} from '../information/information';
import {AlgorithmInformation} from '../information/information.models';
import {MatButton} from '@angular/material/button';
import {MatIcon} from '@angular/material/icon';
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS, DEFAULT_TIME_PER_GENERATION, LIVE_SPAWN_PROBABILITY, MAX_GRID_PX, MAX_GRID_SIZE, MAX_TIME_PER_GENERATION, MIN_GRID_SIZE, MIN_TIME_PER_GENERATION, Node, Scenario} from './conway-gol.models';
import {GenericGridComponent, GridPos} from '../../../shared/components/generic-grid/generic-grid';

@Component({
  selector: 'app-conway-gol',
  imports: [
    MatCard,
    MatCardContent,
    MatCardHeader,
    MatCardTitle,
    TranslatePipe,
    Information,
    MatButton,
    MatIcon,
    MatFormField,
    MatInput,
    MatLabel,
    ReactiveFormsModule,
    FormsModule,
    GenericGridComponent
  ],
  templateUrl: './conway-gol.component.html',
})
export class ConwayGolComponent implements AfterViewInit {

  algoInformation: AlgorithmInformation = {
    title: 'GOL.EXPLANATION.TITLE',
    entries: [
      {
        name: '',
        description: 'GOL.EXPLANATION.EXPLANATION',
        link: UrlConstants.CONWAYS_WIKI
      }
    ],
    disclaimer: 'GOL.EXPLANATION.DISCLAIMER',
    disclaimerBottom: '',
    disclaimerListEntry: ['GOL.EXPLANATION.DISCLAIMER_1', 'GOL.EXPLANATION.DISCLAIMER_2', 'GOL.EXPLANATION.DISCLAIMER_3', 'GOL.EXPLANATION.DISCLAIMER_4']
  };

  protected gridCols = DEFAULT_GRID_COLS;
  protected gridRows = DEFAULT_GRID_ROWS;
  protected lifeSpeed = DEFAULT_TIME_PER_GENERATION;
  protected readonly MIN_GRID_SIZE = MIN_GRID_SIZE;
  protected readonly MAX_GRID_SIZE = MAX_GRID_SIZE;
  protected readonly MAX_GRID_PX = MAX_GRID_PX;

  readGrid: Node[][] = [];
  writeGrid: Node[][] = [];
  executionTime = 0;
  currentScenario: Scenario = Scenario.SIMPLE;
  readonly gameStarted  = signal(false);

  @ViewChild(GenericGridComponent) genericGridComponent!: GenericGridComponent;

  ngAfterViewInit(): void {
    if (this.genericGridComponent) {
      this.genericGridComponent.initializationFn = this.initializeConwayGrid;
      this.genericGridComponent.createNodeFn = this.createConwayNode;
      this.genericGridComponent.getNodeColorFn = this.getConwayNodeColor;
      this.genericGridComponent.applySelectionFn = this.applyConwaySelection;
      this.genericGridComponent.gridRows = this.gridRows;
      this.genericGridComponent.gridCols = this.gridCols;
      this.genericGridComponent.minGridSize = this.MIN_GRID_SIZE;
      this.genericGridComponent.maxGridSize = this.MAX_GRID_SIZE;
      this.genericGridComponent.maxGridPx = this.MAX_GRID_PX;
      this.genericGridComponent.initializeGrid();
    }
    this.gameStarted.set(false);
  }

  generate(scene: Scenario): void {
    this.currentScenario = scene;
    this.genericGridComponent.initializationFn = this.initializeConwayGrid;
    this.genericGridComponent.initializeGrid();
  }

  applySpeed(): void {
    this.lifeSpeed = Math.min(Math.max(this.lifeSpeed, MIN_TIME_PER_GENERATION), MAX_TIME_PER_GENERATION);
  }

  // --- Callbacks for GenericGridComponent ---
  createConwayNode = (row: number, col: number): Node => {
    return {
      row,
      col,
      alive: false
    };
  };

  getConwayNodeColor = (node: Node): string => {
    return node.alive ? 'black' : 'lightgray';
  };

  applyConwaySelection = (pos: GridPos, grid: Node[][]): void => {
    this.readGrid = grid; // Keep internal grid in sync
    const node = grid[pos.row][pos.col];
    node.alive = !node.alive; // Toggle alive status
  };

  initializeConwayGrid = (grid: Node[][]): void => {
    this.gameStarted.set(false);
    this.readGrid = grid;
    switch(this.currentScenario) {
      case Scenario.RANDOM: this.setupRandomLives(); break;
      case Scenario.SIMPLE: this.setupSimpleLive(); break;
      case Scenario.PULSAR: this.setupPulsar(); break;
      case Scenario.GUN:    this.setupGliderGun(); break;
    }
    this.writeGrid = structuredClone(this.readGrid);
  };

  // --- Conway-specific logic (kept local) ---
  setupRandomLives(): void {
    for (let row = 0; row < this.gridRows; row++) {
      for (let col = 0; col < this.gridCols; col++) {
        this.readGrid[row][col].alive = Math.random() <= LIVE_SPAWN_PROBABILITY;
      }
    }
  }

  setupSimpleLive(): void {
    this.readGrid[3][4].alive = true;
    this.readGrid[4][5].alive = true;
    this.readGrid[5][3].alive = true;
    this.readGrid[5][4].alive = true;
    this.readGrid[5][5].alive = true;
  }

  setupPulsar(): void {
    const centerRow = Math.floor(this.gridRows / 2);
    const centerCol = Math.floor(this.gridCols / 2);

    const rows = [-6, -1, 1, 6];
    const offsets = [2, 3, 4];

    rows.forEach(r => {
      offsets.forEach(c => {
        this.setAlive(centerRow + r, centerCol + c);
        this.setAlive(centerRow + r, centerCol - c);
        this.setAlive(centerRow + c, centerCol + r);
        this.setAlive(centerRow - c, centerCol + r);
      });
    });
  }

  setupGliderGun(): void {
    const r = 5;
    const c = 5;

    const dots = [
      [r+4, c], [r+4, c+1], [r+5, c], [r+5, c+1], // Block links
      [r+4, c+10], [r+5, c+10], [r+6, c+10], [r+3, c+11], [r+7, c+11],
      [r+2, c+12], [r+8, c+12], [r+2, c+13], [r+8, c+13], [r+5, c+14],
      [r+3, c+15], [r+7, c+15], [r+4, c+16], [r+5, c+16], [r+6, c+16], [r+5, c+17],
      [r+2, c+20], [r+3, c+20], [r+4, c+20], [r+2, c+21], [r+3, c+21], [r+4, c+21],
      [r+1, c+22], [r+5, c+22], [r+0, c+24], [r+1, c+24], [r+5, c+24], [r+6, c+24],
      [r+2, c+34], [r+3, c+34], [r+2, c+35], [r+3, c+35]
    ];

    dots.forEach(([row, col]) => this.setAlive(row, col));
  }

  // --- The rules of the game

  pauseGame(): void {
    this.gameStarted.set(false);
  }

  async startGame(): Promise<void> {
    this.gameStarted.set(true);
    while (this.gameStarted()){
      const startTime = performance.now();
      let lifeIsDead = true;
      for (let row = 0; row < this.gridRows; row++) {
        for (let col = 0; col < this.gridCols; col++) {
          lifeIsDead = this.checkLifeRules(row, col, this.writeGrid) && lifeIsDead;
        }
      }

      this.swapGrids();
      const endTime = performance.now();
      this.executionTime = Number.parseFloat((endTime - startTime).toFixed(4));
      if (lifeIsDead){
        this.gameStarted.set(false);
      }
      const delta = Math.max(this.lifeSpeed - this.executionTime, 0);
      await this.delay(delta);
    }
    this.executionTime = 0;
  }

  private checkLifeRules(row: number, col: number, writeGrid: Node[][]): boolean {
    const currentCell = this.readGrid[row][col];
    const aliveNeighbors = this.howManyNeighborsAreLiving(row, col);
    const oldLifeState = currentCell.alive;

    const nextStateAlive = (currentCell.alive && (aliveNeighbors === 2 || aliveNeighbors === 3)) || (!currentCell.alive && aliveNeighbors === 3);
    writeGrid[row][col].alive = nextStateAlive;

    //only if at least one cell changes the game is still alive
    return (nextStateAlive == oldLifeState);
  }

  private swapGrids() {
    const tmp = this.readGrid;
    this.readGrid = this.writeGrid;
    this.writeGrid = tmp;
    if (this.genericGridComponent) {
      this.genericGridComponent.grid = this.readGrid;
      this.genericGridComponent.drawGrid();
    }
  }

  private howManyNeighborsAreLiving(row: number, col: number): number {

    let aliveNeighborCount = 0;
    const minRow = Math.max(row - 1, 0);
    const minCol = Math.max(col - 1, 0);
    const maxRow = Math.min(row + 1, this.gridRows - 1);
    const maxCol = Math.min(col + 1, this.gridCols - 1);

    for (let nRow = minRow; nRow <= maxRow; nRow++) {
      for (let nCol = minCol; nCol <= maxCol; nCol++) {
        if (nRow == row && nCol == col) {
          continue;
        }
        if (this.readGrid[nRow][nCol].alive) {
          aliveNeighborCount++;
        }
      }
    }
    return aliveNeighborCount;
  }

  // --- Other methods ---
  protected readonly Scenario = Scenario;
  protected readonly MIN_TIME_PER_GENERATION = MIN_TIME_PER_GENERATION;
  protected readonly MAX_TIME_PER_GENERATION = MAX_TIME_PER_GENERATION;

  delay(ms: number) {
    return new Promise( resolve => setTimeout(resolve, ms) );
  }

  private setAlive(r: number, c: number): void {
    if (r >= 0 && r < this.gridRows && c >= 0 && c < this.gridCols) {
      this.readGrid[r][c].alive = true;
    }
  }
}
