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

/**
 * Class representing a Glissando modification.
 * 
 * A Glissando modification causes an implicit sequence of notes to be played rapidly and
 * individually between a starting note and the next printed note. The duration of a
 * glissando spans the entire printed duration of the starting note.
 * 
 * @extends ModificationBase
 */
export class Glissando extends ModificationBase {

   /**
    * Constructs a new {@link Glissando} modification object.
    */
   constructor(tempo, key, details) {
      super(tempo, key, details);
   }

   /**
    * 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: [],
            sequence: []
         },
         optional: {
            singleNote: ['nextNoteValue'],
            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) {
      return (index < sequence.length) ?
         { 'nextNoteValue': (Array.isArray(sequence[index][0]) ? sequence[index][0][0] : sequence[index][0]) } :
         null;
   }

   /**
    * Returns a list of all modified notes, durations, and velocities as generated by the
    * corresponding modification class.
    * 
    * The `details` variable may contain the following optional key:
    * 
    * `nextNoteValue`:  MIDI number of the note that follows this note
    * 
    * @param {Object<string, number>} details - Information about the ending note of the glissando
    * @returns {NoteDetails[]} List of {@link NoteDetails} to replace the original note
    */
   getModifiedNoteDetails(details) {
      if (!('nextNoteValue' in details)) {
         if (!('implicit' in details))
            throw new WebAudioApiErrors.WebAudioValueError('The "details" variable must contain the following keys: nextNoteValue');
         details.nextNoteValue = details.implicit;
      }
      if (!Number.isInteger(details.nextNoteValue))
         throw new WebAudioApiErrors.WebAudioValueError(`The next note value (${details.nextNoteValue}) must be a positive integer representing a valid MIDI note`);
      else if (Number(details.nextNoteValue) <= this.unmodifiedDetails.note)
         throw new WebAudioApiErrors.WebAudioValueError(`The next note (${details.nextNoteValue}) must be higher than the current note (${this.unmodifiedDetails.note})`);
      const glissando = [];
      const totalDurationSeconds = (this.unmodifiedDetails.duration < 0) ?
         -this.unmodifiedDetails.duration : (60.0 / ((this.unmodifiedDetails.duration / this.tempo.beatBase) * this.tempo.beatsPerMinute));
      const noteDuration = totalDurationSeconds / (Number(details.nextNoteValue) - this.unmodifiedDetails.note);
      for (let i = 0, note = this.unmodifiedDetails.note; note < Number(details.nextNoteValue); ++note, ++i)
         glissando.push(new NoteDetails(
            note,
            this.unmodifiedDetails.velocity,
            -noteDuration,
            i * noteDuration
         ));
      return glissando;
   }
}