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

/**
 * Class representing a Grace note modification.
 * 
 * A Grace note modification causes a note to either begin sounding earlier than anticipated (an
 * acciaccatura) or to rob time from the duration of the next note (an appoggiatura). An
 * appoggiatura will also be lightly accented in loudness, while an accacciatura will be slightly
 * deaccented.
 * 
 * @extends ModificationBase
 */
export class Grace extends ModificationBase {

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

   /**
    * Constructs a new {@link Grace} modification object.
    * 
    * @param {boolean} isAppoggiatura - Whether this modification represents an appoggiatura (vs. an accacciatura)
    */
   constructor(isAppoggiatura, tempo, key, details) {
      super(tempo, key, details);
      this.#isAppoggiatura = isAppoggiatura;
   }

   /**
    * 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: ['graceNoteValue'],
            sequence: []
         },
         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 false;
   }

   static inferParametersFromSequence() {
      throw new WebAudioApiErrors.WebAudioValueError('The "Grace" modification cannot infer any parameters from a sequence of notes');
   }

   /**
    * Returns a list of all modified notes, durations, and velocities as generated by the
    * corresponding modification class.
    * 
    * The `details` variable must contain the following key:
    * 
    * `graceNoteValue`:  MIDI note number of the grace note
    * 
    * @param {Object<string, number>} details - Information about the grace note value
    * @returns {NoteDetails[]} List of {@link NoteDetails} to replace the original note
    */
   getModifiedNoteDetails(details) {
      if (!('graceNoteValue' in details)) {
         if (!('implicit' in details))
            throw new WebAudioApiErrors.WebAudioValueError('The "details" variable must contain the following keys: graceNoteValue');
         details.graceNoteValue = details.implicit;
      }
      if (!Number.isInteger(details.graceNoteValue) || (Number(details.graceNoteValue) < 1))
         throw new WebAudioApiErrors.WebAudioValueError(`The grace note value (${details.graceNoteValue}) must be a positive integer representing a valid MIDI note`);
      let primaryNoteStartTimeOffset = 0.0;
      const fullDuration = ((this.unmodifiedDetails.duration < 0) ?
         this.unmodifiedDetails.duration : (-60.0 / ((this.unmodifiedDetails.duration / this.tempo.beatBase) * this.tempo.beatsPerMinute)));
      const graceDuration = -60.0 / ((16.0 / this.tempo.beatBase) * this.tempo.beatsPerMinute);
      const graceNote = new NoteDetails(Number(details.graceNoteValue), this.unmodifiedDetails.velocity, Math.max(graceDuration, fullDuration / 3.0), 0.0, 0.0);
      if (this.#isAppoggiatura) {
         primaryNoteStartTimeOffset -= graceNote.duration;
         graceNote.velocity *= 1.5;
      }
      else {
         graceNote.startTimeOffset = graceNote.duration;
         graceNote.velocity *= 0.75;
      }
      const primaryNote = new NoteDetails(
         this.unmodifiedDetails.note,
         this.unmodifiedDetails.velocity,
         fullDuration - (this.#isAppoggiatura ? graceNote.duration : 0.0),
         primaryNoteStartTimeOffset,
         this.unmodifiedDetails.duration
      );
      return [graceNote, primaryNote];
   }
}