import {ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
import { CommonModule } from '@angular/common';
import {MatCardModule} from "@angular/material/card";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatSelectModule} from "@angular/material/select";
import {MatButtonModule} from "@angular/material/button";
import {MatIconModule} from "@angular/material/icon";
import {TranslateModule} from "@ngx-translate/core";
import { SortingService } from './service/sorting.service';
import { SortingAudioService } from './service/sorting-audio.service';
import {SortData, SortSnapshot} from './sorting.models';
import { FormsModule } from '@angular/forms';
import {MatInput} from '@angular/material/input';
import {UrlConstants} from '../../../constants/UrlConstants';
import {AlgorithmInformation} from '../information/information.models';
import {Information} from '../information/information';
@Component({
  selector: 'app-sorting',
  imports: [CommonModule, MatCardModule, MatFormFieldModule, MatSelectModule, MatButtonModule, MatIconModule, TranslateModule, FormsModule, MatInput, Information],
  templateUrl: './sorting.component.html',
  styleUrl: './sorting.component.scss'
})
export class SortingComponent implements OnInit {

  private readonly sortingService: SortingService = inject(SortingService);
  private readonly audioService: SortingAudioService = inject(SortingAudioService);
  private readonly cdr: ChangeDetectorRef = inject(ChangeDetectorRef);

  readonly MAX_ARRAY_SIZE: number = 200;
  readonly MIN_ARRAY_SIZE: number = 20;

  algoInformation: AlgorithmInformation = {
    title: 'SORTING.EXPLANATION.TITLE',
    entries: [
      {
        name: 'Bubble Sort',
        description: 'SORTING.EXPLANATION.BUBBLE_SORT_EXPLANATION',
        link: UrlConstants.BUBBLE_SORT_WIKI
      },
      {
        name: 'Cocktail Sort',
        description: 'SORTING.EXPLANATION.COCKTAIL_SORT_EXPLANATION',
        link: UrlConstants.SHAKE_SORT_WIKI
      },
      {
        name: 'Quick Sort',
        description: 'SORTING.EXPLANATION.QUICK_SORT_EXPLANATION',
        link: UrlConstants.QUICK_SORT_WIKI
      },
      {
        name: 'Heap Sort',
        description: 'SORTING.EXPLANATION.HEAP_SORT_EXPLANATION',
        link: UrlConstants.HEAP_SORT_WIKI
      },
      {
        name: 'Tim Sort',
        description: 'SORTING.EXPLANATION.TIMSORT_EXPLANATION',
        link: UrlConstants.TIM_SORT_WIKI
      }
    ],
    disclaimer: 'SORTING.EXPLANATION.DISCLAIMER',
    disclaimerBottom: 'SORTING.EXPLANATION.DISCLAIMER_4',
    disclaimerListEntry: [
      'SORTING.EXPLANATION.DISCLAIMER_1',
      'SORTING.EXPLANATION.DISCLAIMER_2',
      'SORTING.EXPLANATION.DISCLAIMER_3'
    ]
  };

  private timeoutIds: number[] = [];
  sortArray: SortData[] = [];
  unsortedArrayCopy: SortData[] = [];
  arraySize = 50;
  maxArrayValue = 100;
  animationSpeed = 100; // Milliseconds per step
  selectedAlgorithm: string = this.algoInformation.entries[0].name;
  executionTime = 0;
  isSoundEnabled = false;

  ngOnInit(): void {
    this.generateNewArray();
  }

  newArraySizeSet()
  {
    this.arraySize = Math.min(Math.max(this.arraySize, this.MIN_ARRAY_SIZE), this.MAX_ARRAY_SIZE);
    if (this.arraySize == this.sortArray.length)
    {
      return;
    }
    this.generateNewArray();
  }

  generateNewArray(): void {
    this.resetSorting();
    this.executionTime = 0;
    this.unsortedArrayCopy = [];
    this.sortArray = [];

    for (let i = 0; i < this.arraySize; i++) {
      const randomValue  = Math.floor(Math.random() * this.maxArrayValue) + 1;
      this.sortArray.push({
        value: randomValue,
        state: 'unsorted'
      });

      this.unsortedArrayCopy.push({
        value: randomValue,
        state: 'unsorted'
      });
    }
  }

  private resetSortState() {
    for (let i = 0; i < this.sortArray.length; i++) {
      const element = this.sortArray[i];
      const unsortedElement = this.unsortedArrayCopy[i];
      element.value = unsortedElement.value;
      element.state = 'unsorted';
    }
  }

  toggleSound(): void {
    this.isSoundEnabled = !this.isSoundEnabled;
  }

  async startSorting(): Promise<void> {
    this.resetSorting();
    // Init the AudioContext on this user gesture so the browser allows sound
    this.audioService.initOnUserGesture();
    const startTime = performance.now();
    let snapshots: SortSnapshot[] = [];

    switch (this.selectedAlgorithm) {
      case 'Bubble Sort':
        snapshots = this.sortingService.bubbleSort(this.sortArray);
        break;
      case 'Quick Sort':
        snapshots = this.sortingService.quickSort(this.sortArray);
        break;
      case 'Heap Sort':
        snapshots = this.sortingService.heapSort(this.sortArray);
        break;
      case 'Cocktail Sort':
        snapshots = this.sortingService.cocktailSort(this.sortArray);
        break;
      case 'Tim Sort':
        snapshots = this.sortingService.timSort(this.sortArray);
        break;
    }

    const endTime = performance.now();
    this.executionTime = Number.parseFloat((endTime - startTime).toFixed(4));

    this.animateSorting(snapshots);
  }

  private animateSorting(snapshots: SortSnapshot[]): void {
    snapshots.forEach((snapshot, index) => {
      const id = setTimeout(() => {
        for (let i = 0; i < this.sortArray.length; i++) {
          if (snapshot.array[i]) {
            this.sortArray[i].value = snapshot.array[i].value;
            this.sortArray[i].state = snapshot.array[i].state;
          }
        }

        if (this.isSoundEnabled) {
          // Play a tone for each comparing bar (max 2 at once to avoid noise)
          snapshot.array
            .filter(item => item.state === 'comparing')
            .slice(0, 2)
            .forEach(item => this.audioService.playTone(item.value, this.maxArrayValue, this.animationSpeed));
        }

        this.cdr.detectChanges();

        if (index === snapshots.length - 1) {
          this.sortArray.forEach(item => item.state = 'sorted');
          this.cdr.detectChanges();
          if (this.isSoundEnabled) {
            this.audioService.playSortedSweep(this.sortArray.map(item => item.value), this.maxArrayValue);
          }
        }
      }, index * this.animationSpeed);

      this.timeoutIds.push(id);
    });
  }

  private stopAnimations(): void {
    this.timeoutIds.forEach(id => clearTimeout(id));
    this.timeoutIds = [];
  }

  resetSorting(): void {
    this.stopAnimations();
    this.resetSortState();
  }
}
