/**
 * Module containing functionality to apply and update {@link WebAudioAPI} effects.
 * @module Effect
 */

/**
 * Object containing all effect-specific {@link WebAudioAPI} functionality.
 * @namespace Effect
 * @global
 */

import { EffectType } from './Constants.mjs';
import { BandPassFilter } from '../effects/BandPassFilter.mjs';
import { BandRejectFilter } from '../effects/BandRejectFilter.mjs';
import { Chorus } from '../effects/Chorus.mjs';
import { Compression } from '../effects/Compression.mjs';
import { Delay } from '../effects/Delay.mjs';
import { Distortion } from '../effects/Distortion.mjs';
import { Doppler } from '../effects/Doppler.mjs';
import { Echo } from '../effects/Echo.mjs';
import { Equalization } from '../effects/Equalization.mjs';
import { Flanger } from '../effects/Flanger.mjs';
import { HighPassFilter } from '../effects/HighPassFilter.mjs';
import { LowPassFilter } from '../effects/LowPassFilter.mjs';
import { Panning } from '../effects/Panning.mjs';
import { PitchShift } from '../effects/PitchShift.mjs';
import { Phaser } from '../effects/Phaser.mjs';
import { Reverb } from '../effects/Reverb.mjs';
import { Tremolo } from '../effects/Tremolo.mjs';
import { Vibrato } from '../effects/Vibrato.mjs';
import { Volume } from '../effects/Volume.mjs';

const EffectClasses = {
   [EffectType.Reverb]: Reverb, [EffectType.Delay]: Delay, [EffectType.Echo]: Echo, [EffectType.Chorus]: Chorus, [EffectType.Doppler]: Doppler,
   [EffectType.Tremolo]: Tremolo, [EffectType.Vibrato]: Vibrato, [EffectType.Flanger]: Flanger, [EffectType.Phaser]: Phaser,
   [EffectType.Panning]: Panning, [EffectType.Equalization]: Equalization, [EffectType.Volume]: Volume, [EffectType.Compression]: Compression,
   [EffectType.Distortion]: Distortion, [EffectType.LowPassFilter]: LowPassFilter, [EffectType.HighPassFilter]: HighPassFilter,
   [EffectType.BandPassFilter]: BandPassFilter, [EffectType.BandRejectFilter]: BandRejectFilter, [EffectType.PitchShift]: PitchShift
};


/**
 * Returns a list of effect-specific {@link EffectParameter EffectParameters} for manipulation
 * in the corresponding {@link module:Constants.EffectType EffectType}.
 * 
 * Note that the `effectType` parameter must be the **numeric value** associated with a certain
 * {@link module:Constants.EffectType EffectType}, not a string-based key.
 * 
 * @param {number} effectType - {@link module:Constants.EffectType EffectType} for which to return a parameter list
 * @returns {EffectParameter[]} List of effect-specific parameters available for updating
 * @see {@link module:Constants.EffectType EffectType}
 * @see {@link EffectParameter}
 */
export function getEffectParameters(effectType) {
   return EffectClasses[effectType].getParameters();
}


/**
 * Loads a pre-defined {@link Effect} capable of being applied to an individual {@link Track} or
 * to the aggregate output of all tracks.
 * 
 * @param {AudioContext} audioContext - Reference to the global browser {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}
 * @param {string} effectName - User-defined name to assign to the newly loaded effect
 * @param {number} effectType - Numeric value corresponding to the desired {@link module:Constants.EffectType EffectType}
 * @returns {Promise<Effect>} Newly created audio {@link Effect}
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}
 * @see {@link module:Constants.EffectType EffectType}
 * @see {@link Effect}
 * @async
 */
export async function loadEffect(audioContext, effectName, effectType) {

   // Load the requested concrete effect type
   const effect = new EffectClasses[effectType](audioContext);
   await effect.load();

   // Returns an object containing functions and attributes within the public Effect namespace
   return {
      /**
       * User-defined name of the {@link Effect}.
       * @memberof Effect
       * @instance
       */
      name: effectName,
   
      /**
       * Numeric value corresponding to the {@link module:Constants.EffectType EffectType} of the {@link Effect}.
       * @memberof Effect
       * @instance
       */
      type: effectType,
   
      /**
       * Reference to an {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}
       * to which all source {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNodes}
       * should be connected.
       * @memberof Effect
       * @instance
       */
      input: effect.getInputNode(),
   
      /**
       * Reference to an {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}
       * from which all effect-modified output audio is produced, and which should be connected to all
       * destination {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNodes}.
       * @memberof Effect
       * @instance
       */
      output: effect.getOutputNode(),

      /**
       * List of effect-specific {@link EffectParameter EffectParameters} for manipulation in the
       * `effectOptions` parameter of the {@link Effect#update update()} function.
       * @memberof Effect
       * @instance
       */
      parameters: EffectClasses[effectType].getParameters(),

      /**
       * Retrieves the values for all effect-specific parameters at the current time, with keys as
       * specified by the {@link Effect#parameters parameters} member of this {@link Effect}.
       * 
       * @function
       * @returns {Object} Effect-specific parameter values with keys as returned by {@link Effect#parameters parameters}
       * @memberof Effect
       * @instance
       */
      currentParameterValues: effect.currentParameterValues.bind(effect),

      /**
       * Updates the parameters of the effect at the specified time.
       * 
       * Note that the `updateTime` parameter can be omitted to immediately cause the requested
       * changes to take effect.
       * 
       * @function
       * @param {Object} effectOptions - Effect-specific options as returned by {@link WebAudioAPI#getAvailableEffectParameters getAvailableEffectParameters()}
       * @param {number} [updateTime] - Global API time at which to update the effect
       * @param {number} [timeConstant] - Time constant defining an exponential approach to the target
       * @returns {Promise<boolean>} Whether the effect update was successfully applied
       * @memberof Effect
       * @instance
       * @async
       */
      update: effect.update.bind(effect)
   };
}