/*
  Partial IAudioPlayer implementation using the high level "soundfont-player" library.
 */

import { MidiInstrument } from "..";
import { IAudioPlayer } from "../Common/Interfaces/IAudioPlayer";
import { AudioContext as SAudioContext } from "standardized-audio-context";
import * as SoundfontPlayer from "soundfont-player";
import midiNames from "./midiNames";

export class BasicAudioPlayer implements IAudioPlayer<SoundfontPlayer.Player> {

  private ac: SAudioContext = new SAudioContext();
  // private mainTuningRatio: number = 1.0;
  private channelVolumes: number[] = [];
  // private activeSamples: Map<number, any> = new Map();
  private piano: SoundfontPlayer.Player;

  protected memoryLoadedSoundFonts: Map<MidiInstrument, SoundfontPlayer.Player> = new Map();
  protected channelToSoundFont: Map<number, number> = new Map();

  public SoundfontInstrumentOptions = {}; // e.g. set { from: 'server.com/soundfonts/' } for soundfont fetching url

  public async open(uniqueInstruments: number[], numberOfinstruments: number = 16): Promise<void> {
    if (this.piano === undefined) {
      this.piano = await SoundfontPlayer.instrument(
        this.ac as unknown as AudioContext,
        midiNames[MidiInstrument.Acoustic_Grand_Piano].toLowerCase() as any,
        this.SoundfontInstrumentOptions
      );
    }

    for (let i: number = 0; i < numberOfinstruments; i++) {
      this.channelVolumes[i] = 0.8;
    }
  }

  public close(): void {
    // _activeSamples.Clear();
  }

  public tuningChanged(tuningInHz: number): void {
    console.warn("BasicAudioPlayer tuningChanged not implemented");
    //this.mainTuningRatio = tuningInHz / 440;
  }

  public playSound(
    instrumentChannel: number,
    key: number,
    volume: number,
    lengthInMs: number
  ): void {
    if (key >= 128) { return; }

    const sampleVolume: number = Math.min(
      1,
      this.channelVolumes[instrumentChannel] * volume
    );

    const soundFont: SoundfontPlayer.Player = this.memoryLoadedSoundFonts.get(
      this.channelToSoundFont.get(instrumentChannel)
    );
    soundFont.schedule(0, [
      { note: key, duration: lengthInMs / 1000, gain: sampleVolume },
    ]);
  }

  public stopSound(instrumentChannel: number, volume: number): void {
      //console.warn("BasicAudioPlayer stopSound not implemented");
  }

  public async setSound(
    instrumentChannel: number,
    soundId: MidiInstrument
  ): Promise<void> {
    if (this.memoryLoadedSoundFonts.get(soundId) === undefined) {
      await this.loadSoundFont(soundId);
    }
    this.channelToSoundFont.set(instrumentChannel, soundId);
  }

  public async loadSoundFont(soundId: MidiInstrument): Promise<SoundfontPlayer.Player> {

    if (this.memoryLoadedSoundFonts.get(soundId) !== undefined) {
      return this.memoryLoadedSoundFonts.get(soundId);
    }

    const player: SoundfontPlayer.Player = await SoundfontPlayer.instrument(
      this.ac as unknown as AudioContext,
      midiNames[soundId].toLowerCase() as any,
      this.SoundfontInstrumentOptions
    );
    this.memoryLoadedSoundFonts.set(soundId, player);
    return player;
  }

  public setVolume(instrumentChannel: number, volume: number): void {
    this.channelVolumes[instrumentChannel] = volume;
  }

  public setSoundFontFilePath(soundId: MidiInstrument, path: string): void {
    // TODO: Remove function, not needed for web. If not used to load different soundfonts from URLs?
  }

  public playbackHasStopped(): void {
    //console.warn("BasicAudioPlayer playbackHasStopped not implemented");
  }

  public getMemoryLoadedSoundFonts(): SoundfontPlayer.Player[] {
    return [...this.memoryLoadedSoundFonts.values()];
  }
}
