/// <reference path="util.ts" />
/// <reference path="level.ts" />
/// <reference path="audio.ts" />

namespace editor {
  export class Editor {
    audioManager: audio.AudioManager;
    audioElement: HTMLInputElement;
    barElement: HTMLElement;
    markerListElement: HTMLElement;
    intervalListElement: HTMLElement;
    kanaElement: HTMLElement;
    kanjiElement: HTMLElement;
    jsonElement: HTMLInputElement;
    track: audio.Track | null = null;
    markers: Marker[] = [];
    rafId: number;

    constructor() {
      this.audioManager = new audio.AudioManager();
      this.audioElement = document.querySelector('#audio');
      this.audioElement.addEventListener('change', event => {
        this.loadAudio();
      });
      this.barElement = document.querySelector('.bar-overlay');
      this.markerListElement = document.querySelector('.markers');
      this.intervalListElement = document.querySelector('#intervals');
      this.kanaElement = document.querySelector('#kana');
      this.kanjiElement = document.querySelector('#kanji');
      this.jsonElement = document.querySelector('#json');

      this.markerListElement.addEventListener('click', (event: MouseEvent) => this.markersClick(event));
      document.querySelector('#play').addEventListener('click', () => this.play());
      document.querySelector('#pause').addEventListener('click', () => this.pause());
      document.querySelector('#insert-marker').addEventListener('click', () => this.insertMarker());
      document.querySelector('.bar').addEventListener('click', (event: MouseEvent) => this.scrubberClick(event));
      document.querySelector('#import').addEventListener('click', () => this.import());
      document.querySelector('#export').addEventListener('click', () => this.export());
    }

    loadAudio(): void {
      let file = this.audioElement.files[0];
      if (file != null) {
        if (this.track != null) {
          this.track.stop();
        }
        this.clearMarkers();
        this.audioManager.loadTrackFromFile(file).then(t => this.track = t);
      }
    }

    update(): void {
      let percentage = this.track.getTime() / this.track.getDuration() * 100;
      this.barElement.style.width = `${percentage}%`;
      if (this.track.isPlaying()) {
        this.rafId = requestAnimationFrame(() => this.update());
      }
    }

    scrubberClick(event: MouseEvent): void {
      let pos = event.clientX - this.markerListElement.offsetLeft;
      let percentage = pos / this.markerListElement.clientWidth;
      let targetTime = percentage * this.track.getDuration();
      this.track.stop();
      this.track.resumeTime = targetTime;
      this.play();
    }

    markersClick(event: MouseEvent): void {
      let pos = event.clientX - this.markerListElement.offsetLeft;
      let percentage = pos / this.markerListElement.clientWidth;
      let targetTime = percentage * this.track.getDuration();
      this.insertMarker(targetTime);
    }

    insertMarker(time: number = undefined): void {
      let marker = new Marker(
        this.track.getDuration(),
        (marker: Marker) => this.removeMarker(marker),
        (marker: Marker) => this.playMarker(marker)
      );
      if (time !== undefined) {
        marker.time = time;
      } else {
        marker.time = this.track.getTime();
      }
      let insertIndex = this.markers.findIndex(m => m.time > marker.time);
      if (insertIndex >= 0) {
        this.markers.splice(insertIndex, 0, marker);
      } else {
        this.markers.push(marker);
      }
      this.markerListElement.appendChild(marker.markerElement);
      if (insertIndex >= 0) {
        this.intervalListElement.insertBefore(marker.liElement, this.markers[insertIndex+1].liElement);
      } else {
        this.intervalListElement.appendChild(marker.liElement);
      }
    }

    play(duration: number = undefined): void {
      window.cancelAnimationFrame(this.rafId);
      this.track.start(duration);
      this.update();
    }

    pause(): void {
      this.track.pause();
    }

    playMarker(marker: Marker): void {
      let start = 0;
      let index = this.markers.findIndex(m => m == marker);
      if (index > 0) {
        start = this.markers[index - 1].time;
      }
      let duration = marker.time - start;
      this.track.stop();
      this.track.resumeTime = start;
      this.play(duration);
    }

    removeMarker(marker: Marker): void {
      let index = this.markers.findIndex(m => m == marker);
      this.markers.splice(index, 1);
      this.markerListElement.removeChild(marker.markerElement);
      this.intervalListElement.removeChild(marker.liElement);
    }

    clearMarkers(): void {
      this.markers.forEach(m => {
        this.markerListElement.removeChild(m.markerElement);
        this.intervalListElement.removeChild(m.liElement);
      });
      this.markers = [];
    }

    import(): void {
      this.clearMarkers();
      let lines: level.Line[] = JSON.parse(this.jsonElement.value);
      let kanji = '';
      let kana = '';

      lines.forEach(line => {
        kanji += line.kanji + '<br>';
        kana += line.kana + '<br>';
        if (line.end != undefined) {
          this.insertMarker(line.end);
        }
      });

      this.kanjiElement.innerHTML = kanji;
      this.kanaElement.innerHTML = kana;
    }

    export(): void {
      let kanji = this.kanjiElement.innerHTML.split('<br>');
      let kana = this.kanaElement.innerHTML.split('<br>');
      kanji.pop();
      kana.pop();
      let length = Math.max(kanji.length, kana.length, this.markers.length);

      let lines = [];
      let lastStart = 0;
      for (let i = 0; i < length; ++i) {
        let data: level.Line = {
          kanji: kanji[i] || '@',
          kana: kana[i] || '@',
        }
        if (this.markers[i]) {
          data.start = lastStart;
          data.end = this.markers[i].time;
          lastStart = data.end;
        }
        lines.push(data);
      }

      this.jsonElement.value = JSON.stringify(lines);
    }

    start(): void {
      this.loadAudio();
    }
  }

  class Marker {
    markerElement: HTMLElement;
    liElement: HTMLElement;
    inputElement: HTMLInputElement;

    constructor(
      readonly duration: number,
      readonly remove: (marker: Marker) => void,
      readonly play: (marker: Marker) => void
    ) {
      this.markerElement = document.createElement('div');
      this.markerElement.className = 'marker';

      let fragment = util.loadTemplate('interval');
      this.liElement = fragment.querySelector('*');
      this.inputElement = fragment.querySelector('.interval');
      this.inputElement.addEventListener('change', () => {
        this.time = parseFloat(this.inputElement.value);
      });

      fragment.querySelector('.play-section').addEventListener('click', () => play(this));
      fragment.querySelector('.remove-section').addEventListener('click', () => remove(this));
    }

    get time(): number {
      return parseFloat(this.inputElement.value);
    }

    set time(t: number) {
      this.inputElement.value = t.toFixed(1);
      let percentage = t * 100 / this.duration;
      this.markerElement.style.left = `${percentage}%`;
    }
  }
}