import { ModificationBase, NoteDetails } from './ModificationBase.mjs';
import { loadModification } from '../modules/Modification.mjs';
import { ModificationType } from '../modules/Constants.mjs';
import * as WebAudioApiErrors from '../modules/Errors.mjs';

/**
 * Class representing a Crescendo modification.
 * 
 * A Crescendo modification causes a sequence of notes to be played with increasing loudness
 * over time.
 * 
 * @extends ModificationBase
 */
export class Crescendo extends ModificationBase {

   // Effect-specific private variables
   /** @type {boolean} */
   #isDecrescendo;

   /**
    * Constructs a new {@link Crescendo} modification object.
    * 
    * @param {boolean} isDecrescendo - Whether this modification represents a decrescendo/diminuendo
    */
   constructor(isDecrescendo, tempo, key, details) {
      super(tempo, key, details);
      this.#isDecrescendo = isDecrescendo;
   }

   /**
    * Returns a list of all parameters available for use in this modification, including whether
    * the parameter is required or optional when playing back either a "sequence" or just a
    * single "note".
    * 
    * @returns {Object<string,Object<string,string[]>>} List of modification-specific parameter keys and when they are required
    */
   static getParameters() {
      return {
         required: {
            singleNote: ['noteIndex', 'totalNumNotes', 'endingDynamic'],
            sequence: ['endingDynamic']
         },
         optional: {
            singleNote: [],
            sequence: []
         }
      };
   }

   /**
    * Returns whether this modification can be used to modify a sequence of notes.
    * 
    * @returns {boolean} Whether this modification can be used to modify a sequence of notes
    */
   static canModifySequence() {
      return true;
   }

   static inferParametersFromSequence(sequence, index, params) {
      if (!params || !('endingDynamic' in params))
         throw new WebAudioApiErrors.WebAudioValueError('The "endingDynamic" parameter cannot be automatically inferred from a note sequence');
      return {
         'noteIndex': index,
         'totalNumNotes': sequence.length,
         'endingDynamic': params.endingDynamic
      };
   }

   /**
    * Returns a list of all modified notes, durations, and velocities as generated by the
    * corresponding modification class.
    * 
    * The `details` variable must contain the following two keys:
    * 
    * `noteIndex`: Which note this modification applies to within the total modified phrase (starting at 0)
    * `totalNumNotes`: Total number of notes present in the modified phrase
    * `endingDynamic`: Desired dynamic at the end of the modified phrase
    * 
    * @param {Object<string, number>} details - Information about the total modified phrase and this note's place in it
    * @returns {NoteDetails[]} List of {@link NoteDetails} to replace the original note
    */
   getModifiedNoteDetails(details) {
      if (!('noteIndex' in details) || !('totalNumNotes' in details) || !('endingDynamic' in details))
         throw new WebAudioApiErrors.WebAudioValueError('The "details" variable must contain the following keys: noteIndex, totalNumNotes, endingDynamic');
      else if (!Number.isInteger(details.noteIndex) || (Number(details.noteIndex) < 1))
         throw new WebAudioApiErrors.WebAudioValueError(`The "noteIndex" value (${details.noteIndex}) must be a positive integer > 0`);
      else if (!Number.isInteger(details.totalNumNotes) || (Number(details.totalNumNotes) < 2) || (Number(details.totalNumNotes) < Number(details.noteIndex)))
         throw new WebAudioApiErrors.WebAudioValueError(`The "totalNumNotes" value (${details.totalNumNotes}) must be a positive integer >= noteIndex and > 1`);
      else if (!Number.isInteger(details.endingDynamic) || (Number(details.endingDynamic) < ModificationType.Piano) || (Number(details.endingDynamic) > ModificationType.Fortississimo))
         throw new WebAudioApiErrors.WebAudioValueError(`The "endingDynamic" value (${details.endingDynamic}) must be a valid dynamic value (e.g., ModificationType.Piano)`);
      const endingVelocity = loadModification(details.endingDynamic, this.tempo, this.key, new NoteDetails(null, null, null)).getModifiedNoteDetails()[0].velocity;
      const velocityModification = (endingVelocity - this.unmodifiedDetails.velocity) / (1 + details.totalNumNotes - details.noteIndex);
      return [new NoteDetails(
         this.unmodifiedDetails.note,
         this.unmodifiedDetails.velocity + ((details.noteIndex == 1) ? 0 : velocityModification),
         this.unmodifiedDetails.duration
      )];
   }
}