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

import { MidiInstrument } from "../MusicalScore/VoiceData/Instructions/ClefInstruction";
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> {

  public ac: AudioContext = new AudioContext();
  // 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 = {
    from: undefined,
    nameToUrl: undefined // will be overwritten in constructor
  }; // e.g. set { from: "server.com/soundfonts/" } for soundfont fetching url, and set nameToUrl to undefined.

  /** Multiplicator for gain (volume output). 1 represents the maximum volume in OSMD (100%),
   *  but it looks like soundfont-player is louder with volumes > 1.
   *  E.g. set osmd.PlaybackManager.audioPlayer.GainMultiplier to 3 if you think the player is too quiet. */
  public GainMultiplier: number = 1;

  constructor() {
    this.SoundfontInstrumentOptions.nameToUrl = this.nameToSoundfontUrl;
  }

  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 * this.GainMultiplier },
    ]);
  }

  public stopSound(instrumentChannel: number, volume: number): void {
    //this.memoryLoadedSoundFonts.get(this.channelToSoundFont.get(instrumentChannel))?.stop(); // abrupt
    //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);
    }

    let nameOrUrl: any = midiNames[soundId].toLowerCase();
    if (soundId === MidiInstrument.Percussion) {
      // percussion unfortunately doesn't exist in the original soundfonts
      nameOrUrl = "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/percussion-mp3.js";
    }
    const player: SoundfontPlayer.Player = await SoundfontPlayer.instrument(
      this.ac as unknown as AudioContext,
      nameOrUrl,
      this.SoundfontInstrumentOptions
    );
    this.memoryLoadedSoundFonts.set(soundId, player);
    return player;
  }

  /** Returns the url for the instrument name's soundfont to be loaded. */
  public nameToSoundfontUrl(name: string, font: string, format: string): string {
    // nameToUrl function from soundfont-player.js / lib/index.js
    format = format === "ogg" ? format : "mp3";
    font = font === "FluidR3_GM" ? font : "MusyngKite";
    let url: string =  "https://gleitz.github.io/midi-js-soundfonts/" + font + "/" + name + "-" + format + ".js";
    // end soundfont-player nameToUrl function. The following fixes bad links.

    const urlReplacements: Object = {
      "honky_tonk_piano-mp3.js": "honkytonk_piano-mp3.js",
      "synth_voice-mp3.js": "synth_choir-mp3.js",
      "lead_8_bass_lead-mp3.js": "lead_8_bass__lead-mp3.js",
    };
    for (const key of Object.keys(urlReplacements)) {
      url = url.replace(key, urlReplacements[key]);
    }
    return url;
  }

  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()];
  }
}
