text: const RelativePitch = require('./relative-pitch');
  const { mod } = require('../utils');
  
  function findUniqueOctaveOffset(relativePitches, scaleLength, direction) {
    if (direction < 0) relativePitches = relativePitches.slice().reverse();
    for (let octave=direction; true; octave += direction) { // eslint-disable-line no-constant-condition
      for (const {degree,shift} of relativePitches) {
        const invertedDegree = degree + (octave * scaleLength);
        if (!relativePitches.find(({degree:d,shift:s}) => (d === invertedDegree && s === shift))) {
          return new RelativePitch(invertedDegree, shift);
        }
      }
    }
  }
  
  function relativePitchesForInversion(relativePitches, inversion, scaleLength) {
    relativePitches = relativePitches.slice(); // make a copy
    for (let i =  1; i <= inversion; i++) {
      relativePitches.push(findUniqueOctaveOffset(relativePitches, scaleLength, 1));
      relativePitches.shift();
    }
    for (let i = -1; i >= inversion; i--) {
      relativePitches.unshift(findUniqueOctaveOffset(relativePitches, scaleLength, -1));
      relativePitches.pop();
    }
    return relativePitches;
  }
  
  /**
   * A chord
   */
  class Chord {
  
    /**
     *
     * @param relativePitches
     * @param inversion
     */
    constructor(relativePitches, { inversion=0, scale }={}) {
      relativePitches = relativePitches.map(rp => rp instanceof RelativePitch ? rp : new RelativePitch(rp));
      this.relativePitches = Object.freeze(relativePitches);
      this.inversion = inversion;
      this.scale = scale;
      Object.freeze(this);
    }
  
    /**
     *
     * @param scale
     * @param octave
     * @param inversion
     * @param offset
     * @returns {Array}
     */
    pitches({ scale=this.scale, octave=4, inversion=this.inversion, offset=0, }={}) {
      const relativePitches = relativePitchesForInversion(this.relativePitches, inversion, scale.length);
      const pitches = relativePitches.map(relativePitch =>
        // Only add the additional offset if it's non-zero offset, because it causes the relativePitch's shift to be lost
        scale.pitch(offset ? relativePitch.add(offset) : relativePitch, { octave }));
      return pitches;
    }
  
    /**
     *
     * @param position {Number|RelativePitch}
     * @param inversion
     * @returns {*}
     */
    pitch(position, { scale=this.scale, octave=4, inversion=this.inversion, offset=0 }={}) {
      const shift = position.shift || 0;
      position = position.degree || Number(position);
      const pitches = this.pitches({ scale, octave, inversion, offset });
      const pitch = pitches[mod(position, pitches.length)];
      const octaveOffset = Math.floor(position / pitches.length);
      return pitch.add(octaveOffset * scale.semitones + shift);
    }
  
    inv(inversion) {
      if (!inversion) return this;
      return new Chord(this.relativePitches, { inversion, scale: this.scale });
    }
  }
  
  module.exports = Chord;
  