# Source: mapping.js

    const _attrs = Symbol('attrs')

    /**
     * Transformation of a range of stored values into real world values in a
     * defined unit. The transformation may either be described by a lookup table
     * (LUT) or alternatively by the slope and intercept parameters if the
     * transformation can be described by a linear function.
     *
     * @class
     * @memberof mapping
     */
    class Transformation {
      /**
       * @param {Object} options - Options
       * @param {string} options.label - LUT Label
       * @param {number} options.firstValueMapped - First value mapped by LUT
       * @param {number} options.lastValueMapped - Last value mapped by LUT
       * @param {lut} options.lut - LUT data
       * @param {number} options.intercept - Intercept of linear function
       * @param {number} options.slope - Slope of linear function
       */
      constructor ({
        label,
        firstValueMapped,
        lastValueMapped,
        lut,
        intercept,
        slope
      }) {
        if (label === undefined) {
          throw new Error('LUT Label is required.')
        }
        this[_attrs].label = label

        if (firstValueMapped === undefined) {
          throw new Error('Real World Value First Value Mapped is required.')
        }
        this[_attrs].firstValueMapped = firstValueMapped
        if (lastValueMapped === undefined) {
          throw new Error('Real World Value Last Value Mapped is required.')
        }
        this[_attrs].lastValueMapped = lastValueMapped

        if ((intercept === undefined || slope === undefined) && lut === undefined) {
          throw new Error(
            'Either LUT Data or Real World Value Slope and ' +
            'Real World Value Intercept must be provided.'
          )
        }
        if (slope === undefined) {
          throw new Error('Real World Value Slope is required.')
        }
        this[_attrs].slope = slope
        if (intercept === undefined) {
          throw new Error('Real World Value Intercept is required.')
        }
        this[_attrs].intercept = intercept

        if (lut === undefined) {
          throw new Error('LUT Data is required.')
        }
        this[_attrs].lut = lut
      }
    }

    /**
     * Parameter Mapping.
     *
     * Describes an individual parameter encoded in a DICOM Parametric Map instance.
     *
     * @class
     * @memberof mapping
     */
    class ParameterMapping {
      /**
       * @param {Object} options
       * @param {string} options.uid - Unique tracking identifier
       * @param {number} options.number - Parameter Number (one-based index value)
       * @param {string} options.label - Parameter Label
       * @param {string} options.description - Parameter Description
       * @param {string} options.studyInstanceUID - Study Instance UID of DICOM
       * Parametric Map instances
       * @param {string} options.seriesInstanceUID - Series Instance UID of DICOM
       * Parametric Map instances
       * @param {string[]} options.sopInstanceUIDs - SOP Instance UIDs of DICOM
       * Parametric Map instances
       * @param {string|undefined} options.paletteColorLookupTableUID - Palette
       * Color Lookup Table UID
       */
      constructor ({
        uid,
        number,
        label,
        description,
        studyInstanceUID,
        seriesInstanceUID,
        sopInstanceUIDs,
        paletteColorLookupTableUID
      }) {
        this[_attrs] = {}
        if (uid === undefined) {
          throw new Error('Unique Tracking Identifier is required.')
        } else {
          this[_attrs].uid = uid
        }

        if (number === undefined) {
          throw new Error('Parameter Number is required.')
        }
        this[_attrs].number = number

        if (label === undefined) {
          throw new Error('Parameter Label is required.')
        }
        this[_attrs].label = label

        if (description === undefined) {
          throw new Error('Parameter Description is required.')
        }
        this[_attrs].description = description

        if (studyInstanceUID === undefined) {
          throw new Error('Study Instance UID is required.')
        }
        this[_attrs].studyInstanceUID = studyInstanceUID

        if (seriesInstanceUID === undefined) {
          throw new Error('Series Instance UID is required.')
        }
        this[_attrs].seriesInstanceUID = seriesInstanceUID

        if (sopInstanceUIDs === undefined) {
          throw new Error('SOP Instance UIDs are required.')
        }
        this[_attrs].sopInstanceUIDs = sopInstanceUIDs

        this[_attrs].paletteColorLookupTableUID = paletteColorLookupTableUID

        Object.freeze(this)
      }

      /**
       * Unique Tracking Identifier
       *
       * @type string
       */
      get uid () {
        return this[_attrs].uid
      }

      /**
       * Parameter Number
       *
       * @type number
       */
      get number () {
        return this[_attrs].number
      }

      /**
       * Parameter Label
       *
       * @type string
       */
      get label () {
        return this[_attrs].label
      }

      /**
       * Parameter Description
       *
       * @type string
       */
      get description () {
        return this[_attrs].description
      }

      /**
       * Study Instance UID of DICOM Parametric Map instances.
       *
       * @type string
       */
      get studyInstanceUID () {
        return this[_attrs].studyInstanceUID
      }

      /**
       * Series Instance UID of DICOM Parametric Map instances.
       *
       * @type string
       */
      get seriesInstanceUID () {
        return this[_attrs].seriesInstanceUID
      }

      /**
       * SOP Instance UIDs of DICOM Parametric Map instances.
       *
       * @type string[]
       */
      get sopInstanceUIDs () {
        return this[_attrs].sopInstanceUIDs
      }

      /**
       * Palette Color Lookup Table UID.
       *
       * @type string
       */
      get paletteColorLookupTableUID () {
        return this[_attrs].paletteColorLookupTableUID
      }
    }

    function _groupFramesPerMapping (metadata) {
      const mappings = {}
      const sharedItem = metadata.SharedFunctionalGroupsSequence[0]
      if (sharedItem.RealWorldValueMappingSequence !== undefined) {
        const labels = sharedItem.RealWorldValueMappingSequence.map(
          item => item.LUTLabel
        )
        const key = labels.join('-')
        const numFrames = Number(metadata.NumberOfFrames)
        mappings[key] = {
          frameNumbers: [...Array(numFrames).keys()].map(index => index + 1),
          realWorldValueMappings: sharedItem.RealWorldValueMappingSequence
        }
      } else {
        // Dimension Organization TILED_FULL is not defined for Parametric Map
        if (metadata.PerFrameFunctionalGroupsSequence !== undefined) {
          metadata.PerFrameFunctionalGroupsSequence.forEach((frameItem, i) => {
            if (frameItem.RealWorldValueMappingSequence !== undefined) {
              const labels = frameItem.RealWorldValueMappingSequence.map(
                item => item.LUTLabel
              )
              const key = labels.join('-')
              if (key in mappings) {
                mappings[key].frameNumbers.push(i + 1)
              } else {
                mappings[key] = {
                  frameNumbers: [i + 1],
                  realWorldValueMappings: frameItem.RealWorldValueMappingSequence
                }
              }
            }
          })
        }
      }

      const frameNumberToMappingNumber = {}
      const mappingNumberToFrameNumbers = {}
      const mappingNumberToDescriptions = {}
      Object.values(mappings).forEach((mapping, mappingIndex) => {
        const mappingNumber = mappingIndex + 1
        mapping.frameNumbers.forEach(frameNumber => {
          frameNumberToMappingNumber[frameNumber] = mappingNumber
          if (mappingNumber in mappingNumberToFrameNumbers) {
            mappingNumberToFrameNumbers[mappingNumber].push(frameNumber)
          } else {
            mappingNumberToFrameNumbers[mappingNumber] = [frameNumber]
          }
        })
        mappingNumberToDescriptions[mappingNumber] = mapping.realWorldValueMappings
      })

      return {
        frameNumberToMappingNumber,
        mappingNumberToFrameNumbers,
        mappingNumberToDescriptions
      }
    }

    export {
      _groupFramesPerMapping,
      ParameterMapping,
      Transformation
    }