text: import cornerstoneTools from 'cornerstone-tools';
  import log from '../../log';
  import getLabel from '../lib/getLabel';
  import getDescription from '../lib/getDescription';
  import getImageIdForImagePath from '../lib/getImageIdForImagePath';
  import guid from '../../utils/guid';
  import studyMetadataManager from '../../utils/studyMetadataManager';
  
  const configuration = {};
  
  export default class MeasurementApi {
    static Instance;
  
    static setConfiguration(config) {
      Object.assign(configuration, config);
    }
  
    static getConfiguration() {
      return configuration;
    }
  
    static getToolsGroupsMap() {
      const toolsGroupsMap = {};
      configuration.measurementTools.forEach(toolGroup => {
        toolGroup.childTools.forEach(
          tool => (toolsGroupsMap[tool.id] = toolGroup.id)
        );
      });
  
      return toolsGroupsMap;
    }
  
    static getToolGroupTools(toolsGroupsMap) {
      const result = {};
      Object.keys(toolsGroupsMap).forEach(toolType => {
        const toolGroupId = toolsGroupsMap[toolType];
        if (!result[toolGroupId]) {
          result[toolGroupId] = [];
        }
  
        result[toolGroupId].push(toolType);
      });
  
      return result;
    }
  
    static getToolConfiguration(toolType) {
      const configuration = MeasurementApi.getConfiguration();
      const toolsGroupsMap = MeasurementApi.getToolsGroupsMap();
  
      const toolGroupId = toolsGroupsMap[toolType];
      const toolGroup = configuration.measurementTools.find(
        toolGroup => toolGroup.id === toolGroupId
      );
  
      let tool;
      if (toolGroup) {
        tool = toolGroup.childTools.find(tool => tool.id === toolType);
      }
  
      return {
        toolGroupId,
        toolGroup,
        tool,
      };
    }
  
    static syncMeasurementAndToolData(measurement) {
      log.info('syncMeasurementAndToolData');
  
      const measurementLabel = getLabel(measurement);
      if (measurementLabel) {
        measurement.labels = [measurementLabel];
      }
  
      const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
  
      // Stop here if the metadata for the measurement's study is not loaded yet
      const { studyInstanceUid } = measurement;
      const metadata = studyMetadataManager.get(studyInstanceUid);
      if (!metadata) return;
  
      // Iterate each child tool if the current tool has children
      const toolType = measurement.toolType;
      const { tool } = MeasurementApi.getToolConfiguration(toolType);
      if (Array.isArray(tool.childTools)) {
        tool.childTools.forEach(childToolKey => {
          const childMeasurement = measurement[childToolKey];
          if (!childMeasurement) return;
          childMeasurement._id = measurement._id;
          childMeasurement.measurementNumber = measurement.measurementNumber;
          childMeasurement.lesionNamingNumber = measurement.lesionNamingNumber;
  
          MeasurementApi.syncMeasurementAndToolData(childMeasurement);
        });
  
        return;
      }
  
      const imageId = getImageIdForImagePath(measurement.imagePath);
  
      // If no tool state exists for this imageId, create an empty object to store it
      if (!toolState[imageId]) {
        toolState[imageId] = {};
      }
  
      const currentToolState = toolState[imageId][toolType];
      const toolData = currentToolState && currentToolState.data;
  
      // Check if we already have toolData for this imageId and toolType
      if (toolData && toolData.length) {
        // If we have toolData, we should search it for any data related to the current Measurement
        const toolData = toolState[imageId][toolType].data;
  
        // Create a flag so we know if we've successfully updated the Measurement in the toolData
        let alreadyExists = false;
  
        // Loop through the toolData to search for this Measurement
        toolData.forEach(tool => {
          // Break the loop if this isn't the Measurement we are looking for
          if (tool._id !== measurement._id) {
            return;
          }
  
          // If we have found the Measurement, set the flag to True
          alreadyExists = true;
  
          // Update the toolData from the Measurement data
          Object.assign(tool, measurement);
          return false;
        });
  
        // If we have found the Measurement we intended to update, we can stop this function here
        if (alreadyExists === true) {
          return;
        }
      } else {
        // If no toolData exists for this toolType, create an empty array to hold some
        toolState[imageId][toolType] = {
          data: [],
        };
      }
  
      // If we have reached this point, it means we haven't found the Measurement we are looking for
      // in the current toolData. This means we need to add it.
  
      // Add the MeasurementData into the toolData for this imageId
      toolState[imageId][toolType].data.push(measurement);
  
      cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
        toolState
      );
    }
  
    static isToolIncluded(tool) {
      return (
        tool.options &&
        tool.options.caseProgress &&
        tool.options.caseProgress.include
      );
    }
  
    constructor(timepointApi, options = {}) {
      if (MeasurementApi.Instance) {
        MeasurementApi.Instance.initialize(timepointApi, options);
        return MeasurementApi.Instance;
      }
  
      this.initialize(timepointApi, options);
      MeasurementApi.Instance = this;
    }
  
    initialize(timepointApi, options = {}) {
      this.timepointApi = timepointApi;
      this.options = options;
      this.toolGroups = {};
      this.tools = {};
      this.toolsGroupsMap = MeasurementApi.getToolsGroupsMap();
      this.toolGroupTools = MeasurementApi.getToolGroupTools(this.toolsGroupsMap);
  
      // Iterate over each tool group and create collection
      configuration.measurementTools.forEach(toolGroup => {
        this.toolGroups[toolGroup.id] = [];
  
        // Iterate over each tool group child tools (e.g. bidirectional, targetCR, etc.) and create collection
        toolGroup.childTools.forEach(tool => {
          this.tools[tool.id] = [];
        });
      });
    }
  
    onMeasurementsUpdated() {
      if (typeof this.options.onMeasurementsUpdated !== 'function') {
        log.warn('Measurements update callback is not defined');
        return;
      }
  
      this.options.onMeasurementsUpdated(Object.assign({}, this.tools));
    }
  
    retrieveMeasurements(patientId, timepointIds) {
      const retrievalFn = configuration.dataExchange.retrieve;
      if (typeof retrievalFn !== 'function') {
        log.error('Measurement retrieval function has not been configured.');
        return;
      }
  
      return new Promise((resolve, reject) => {
        retrievalFn(patientId, timepointIds).then(measurementData => {
          if (measurementData) {
            log.info('Measurement data retrieval');
            log.info(measurementData);
  
            Object.keys(measurementData).forEach(measurementTypeId => {
              const measurements = measurementData[measurementTypeId];
  
              measurements.forEach(measurement => {
                const { toolType } = measurement;
  
                this.addMeasurement(toolType, measurement);
              });
            });
          }
  
          resolve();
  
          // Synchronize the new tool data
          this.syncMeasurementsAndToolData();
  
          // Let others know that the measurements are updated
          this.onMeasurementsUpdated();
        }, reject);
      });
    }
  
    storeMeasurements(timepointId) {
      const storeFn = configuration.dataExchange.store;
      if (typeof storeFn !== 'function') {
        log.error('Measurement store function has not been configured.');
        return;
      }
  
      let measurementData = {};
      configuration.measurementTools.forEach(toolGroup => {
        // Skip the tool groups excluded from case progress
        if (!MeasurementApi.isToolIncluded(toolGroup)) {
          return;
        }
  
        toolGroup.childTools.forEach(tool => {
          // Skip the tools excluded from case progress
          if (!MeasurementApi.isToolIncluded(tool)) {
            return;
          }
  
          if (!measurementData[toolGroup.id]) {
            measurementData[toolGroup.id] = [];
          }
  
          measurementData[toolGroup.id] = measurementData[toolGroup.id].concat(
            this.tools[tool.id]
          );
        });
      });
  
      const timepointFilter = timepointId
        ? tp => tp.timepointId === timepointId
        : null;
      const timepoints = this.timepointApi.all(timepointFilter);
      const timepointIds = timepoints.map(t => t.timepointId);
      const patientId = timepoints[0].patientId;
      const filter = {
        patientId,
        timepointIds,
      };
  
      log.info('Saving Measurements for timepoints:', timepoints);
      return storeFn(measurementData, filter).then(() => {
        log.info('Measurement storage completed');
      });
    }
  
    calculateLesionNamingNumber(measurements) {
      const sortedMeasurements = measurements.sort((a, b) => {
        if (a.lesionNamingNumber > b.lesionNamingNumber) {
          return 1;
        } else if (a.lesionNamingNumber < b.lesionNamingNumber) {
          return -1;
        }
  
        return 0;
      });
  
      //  Calculate lesion naming number starting from 1 not to miss any measurement (as seen in MM)
      //      A measurement from beginning of the list might be deleted, so a new measurement should replace that
      let i;
      for (i = 1; i < sortedMeasurements.length + 1; i++) {
        if (i < sortedMeasurements[i - 1].lesionNamingNumber) {
          break;
        }
      }
  
      return i;
    }
  
    fetch(toolGroupId, filter) {
      if (!this.toolGroups[toolGroupId]) {
        throw new Error(
          `MeasurementApi: No Collection with the id: ${toolGroupId}`
        );
      }
  
      let items;
      if (filter) {
        items = this.toolGroups[toolGroupId].filter(filter);
      } else {
        items = this.toolGroups[toolGroupId];
      }
  
      return items.map(item => {
        if (item.toolId) {
          return this.tools[item.toolId].find(
            tool => tool._id === item.toolItemId
          );
        }
  
        return { lesionNamingNumber: item.lesionNamingNumber };
      });
    }
  
    getFirstMeasurement(timepointId) {
      // Get child tools from all included tool groups
      let childTools = [];
      configuration.measurementTools.forEach(toolGroup => {
        // Skip the tool groups excluded from case progress
        if (!MeasurementApi.isToolIncluded(toolGroup)) {
          return false;
        }
  
        childTools = childTools.concat(toolGroup.childTools);
      });
  
      // Get all included child tools
      const includedChildTools = childTools.filter(tool =>
        MeasurementApi.isToolIncluded(tool)
      );
  
      // Get the first measurement for the given timepoint
      let measurement = undefined;
      includedChildTools.every(tool => {
        measurement = this.tools[tool.id].find(
          t => t.timepointId === timepointId && t.measurementNumber === 1
        );
  
        return !measurement;
      });
  
      // Return the found measurement object or undefined if not found
      return measurement;
    }
  
    lesionExistsAtTimepoints(lesionNamingNumber, toolGroupId, timepointIds) {
      // Retrieve all the data for the given tool group (e.g. 'targets')
      const measurementsAtTimepoint = this.fetch(toolGroupId, tool =>
        timepointIds.includes(tool.timepointId)
      );
  
      // Return whether or not any lesion at this timepoint has the same lesionNamingNumber
      return !!measurementsAtTimepoint.find(
        m => m.lesionNamingNumber === lesionNamingNumber
      );
    }
  
    isNewLesionsMeasurement(measurementData) {
      if (!measurementData) {
        return;
      }
  
      const toolConfig = MeasurementApi.getToolConfiguration(
        measurementData.toolType
      );
      const toolType = toolConfig.tool.parentTool || measurementData.toolType;
      const { timepointApi } = this;
      const currentMeasurement =
        this.tools[toolType].find(tool => tool._id === measurementData._id) || {};
      const timepointId =
        currentMeasurement.timepointId || measurementData.timepointId;
      const lesionNamingNumber =
        currentMeasurement.lesionNamingNumber ||
        measurementData.lesionNamingNumber;
  
      // Stop here if the needed information is not set
      if (!timepointApi || !timepointId || !toolConfig) {
        return;
      }
  
      const { toolGroupId } = toolConfig;
      const current = timepointApi.timepoints.find(
        tp => tp.timepointId === timepointId
      );
      const initialTimepointIds = timepointApi.initialTimepointIds();
  
      // Stop here if there's no initial timepoint, or if the current is any initial
      if (
        !initialTimepointIds ||
        initialTimepointIds.length < 1 ||
        initialTimepointIds.some(
          initialtpid => initialtpid === current.timepointId
        )
      ) {
        return false;
      }
  
      return (
        this.lesionExistsAtTimepoints(
          lesionNamingNumber,
          toolGroupId,
          initialTimepointIds
        ) === false
      );
    }
  
    calculateLesionMaxMeasurementNumber(groupId, filter) {
      let measurements = [];
      if (groupId) {
        // Get the measurements of the group
        measurements = this.toolGroups[groupId] || [];
      } else {
        // Get all measurements of all groups
        measurements = Object.keys(this.toolGroups).reduce((acc, val) => {
          acc.push(...this.toolGroups[val]);
          return acc;
        }, []);
      }
  
      const sortedMeasurements = measurements.filter(filter).sort((tp1, tp2) => {
        return tp1.measurementNumber < tp2.measurementNumber ? 1 : -1;
      });
  
      for (let i = 0; i < sortedMeasurements.length; i++) {
        const toolGroupMeasurement = sortedMeasurements[i];
        const measurement = this.tools[toolGroupMeasurement.toolId].find(
          tool => tool._id === toolGroupMeasurement.toolItemId
        );
        const isNew = this.isNewLesionsMeasurement(measurement);
        if (!isNew) {
          return measurement.measurementNumber;
        }
      }
  
      return 0;
    }
  
    calculateNewLesionMaxMeasurementNumber(groupId, filter) {
      const sortedMeasurements = this.toolGroups[groupId]
        .filter(filter)
        .sort((tp1, tp2) => {
          return tp1.measurementNumber < tp2.measurementNumber ? 1 : -1;
        });
  
      for (let i = 0; i < sortedMeasurements.length; i++) {
        const toolGroupMeasurement = sortedMeasurements[i];
        const measurement = this.tools[toolGroupMeasurement.toolId].find(
          tool => tool._id === toolGroupMeasurement.toolItemId
        );
        const isNew = this.isNewLesionsMeasurement(measurement);
        if (isNew) {
          return measurement.measurementNumber;
        }
      }
  
      return 0;
    }
  
    calculateMeasurementNumber(measurement) {
      const toolGroupId = this.toolsGroupsMap[measurement.toolType];
  
      const filter = tool => tool._id !== measurement._id;
  
      const isNew = this.isNewLesionsMeasurement(measurement);
  
      if (isNew) {
        const maxTargetMeasurementNumber = this.calculateLesionMaxMeasurementNumber(
          'targets',
          filter
        );
        const maxNonTargetMeasurementNumber = this.calculateLesionMaxMeasurementNumber(
          'nonTargets',
          filter
        );
        const maxNewTargetMeasurementNumber = this.calculateNewLesionMaxMeasurementNumber(
          'targets',
          filter
        );
        if (toolGroupId === 'targets') {
          return Math.max(
            maxTargetMeasurementNumber,
            maxNonTargetMeasurementNumber,
            maxNewTargetMeasurementNumber
          );
        } else if (toolGroupId === 'nonTargets') {
          const maxNewNonTargetMeasurementNumber = this.calculateNewLesionMaxMeasurementNumber(
            'nonTargets',
            filter
          );
          return Math.max(
            maxTargetMeasurementNumber,
            maxNonTargetMeasurementNumber,
            maxNewTargetMeasurementNumber,
            maxNewNonTargetMeasurementNumber
          );
        }
      } else {
        const maxTargetMeasurementNumber = this.calculateLesionMaxMeasurementNumber(
          'targets',
          filter
        );
        if (toolGroupId === 'targets') {
          return maxTargetMeasurementNumber;
        } else if (toolGroupId === 'nonTargets') {
          const maxNonTargetMeasurementNumber = this.calculateLesionMaxMeasurementNumber(
            'nonTargets',
            filter
          );
          return Math.max(
            maxTargetMeasurementNumber,
            maxNonTargetMeasurementNumber
          );
        } else {
          return this.calculateLesionMaxMeasurementNumber(null, filter);
        }
      }
  
      return 0;
    }
  
    getPreviousMeasurement(measurementData) {
      if (!measurementData) {
        return;
      }
  
      const { timepointId, toolType, lesionNamingNumber } = measurementData;
      if (!timepointId || !toolType || !lesionNamingNumber) {
        return;
      }
  
      const toolGroupId = this.toolsGroupsMap[measurementData.toolType];
  
      // TODO: Remove TrialPatientLocationUid from here and override it somehow
      // by dependant applications. Here we should use the location attribute instead of the uid
      let filter;
      const uid =
        measurementData.additionalData &&
        measurementData.additionalData.TrialPatientLocationUid;
      if (uid) {
        filter = tool =>
          tool._id !== measurementData._id &&
          tool.additionalData &&
          tool.additionalData.TrialPatientLocationUid === uid;
      } else {
        filter = tool =>
          tool._id !== measurementData._id &&
          tool.lesionNamingNumber === lesionNamingNumber;
      }
  
      const childToolTypes = this.toolGroupTools[toolGroupId];
      for (let i = 0; i < childToolTypes.length; i++) {
        const childToolType = childToolTypes[i];
        const toolCollection = this.tools[childToolType];
        const item = toolCollection.find(filter);
  
        if (item) {
          return item;
        }
      }
    }
  
    hasDuplicateMeasurementNumber(measurementData) {
      if (!measurementData) {
        return;
      }
  
      const { toolType, measurementNumber } = measurementData;
      if (!toolType || !measurementNumber) {
        return;
      }
  
      const filter = tool =>
        tool._id !== measurementData._id &&
        tool.measurementNumber === measurementData.measurementNumber;
  
      return configuration.measurementTools
        .filter(toolGroup => toolGroup.id !== 'temp')
        .some(toolGroup => {
          if (this.toolGroups[toolGroup.id].find(filter)) {
            return true;
          }
          return toolGroup.childTools.some(tool => {
            if (this.tools[tool.id].find(filter)) {
              return true;
            }
          });
        });
    }
  
    updateNumbering(collectionToUpdate, propertyFilter, propertyName, increment) {
      collectionToUpdate.filter(propertyFilter).forEach(item => {
        item[propertyName] += increment;
      });
    }
  
    updateMeasurementNumberForAllMeasurements(measurement, increment) {
      const filter = tool =>
        tool._id !== measurement._id &&
        tool.measurementNumber >= measurement.measurementNumber;
  
      configuration.measurementTools
        .filter(toolGroup => toolGroup.id !== 'temp')
        .forEach(toolGroup => {
          this.updateNumbering(
            this.toolGroups[toolGroup.id],
            filter,
            'measurementNumber',
            increment
          );
  
          toolGroup.childTools.forEach(tool => {
            this.updateNumbering(
              this.tools[tool.id],
              filter,
              'measurementNumber',
              increment
            );
          });
        });
    }
  
    addMeasurement(toolType, measurement) {
      const toolGroup = this.toolsGroupsMap[toolType];
      const groupCollection = this.toolGroups[toolGroup];
      const collection = this.tools[toolType];
  
      // Get the related measurement by the measurement number and use its location if defined
      const relatedMeasurement = collection.find(
        t =>
          t.lesionNamingNumber === measurement.lesionNamingNumber &&
          t.toolType === measurement.toolType
      );
  
      // Use the related measurement location if found and defined
      if (relatedMeasurement && relatedMeasurement.location) {
        measurement.location = relatedMeasurement.location;
      }
  
      // Use the related measurement description if found and defined
      if (relatedMeasurement && relatedMeasurement.description) {
        measurement.description = relatedMeasurement.description;
      }
  
      measurement._id = guid();
  
      // Get the timepoint
      let timepoint;
      if (measurement.studyInstanceUid) {
        timepoint = this.timepointApi.study(measurement.studyInstanceUid)[0];
      } else {
        const { timepointId } = measurement;
        timepoint = this.timepointApi.timepoints.find(
          t => t.timepointId === timepointId
        );
      }
  
      // Preventing errors thrown when non-associated (standalone) study is opened...
      // @TODO: Make sure this logic is correct.
      if (!timepoint) return;
  
      // Empty Item is the lesion just added in cornerstoneTools, but does not have measurement data yet
      const emptyItem = groupCollection.find(
        groupTool =>
          !groupTool.toolId && groupTool.timepointId === timepoint.timepointId
      );
  
      // Set the timepointId attribute to measurement to make it easier to filter measurements by timepoint
      measurement.timepointId = timepoint.timepointId;
  
      // Check if the measurement data is just added by a cornerstone tool and is still empty
      if (emptyItem) {
        // Set relevant initial data and measurement number to the measurement
        measurement.lesionNamingNumber = emptyItem.lesionNamingNumber;
        measurement.measurementNumber = emptyItem.measurementNumber;
  
        groupCollection
          .filter(
            groupTool =>
              groupTool.timepointId === timepoint.timepointId &&
              groupTool.lesionNamingNumber === measurement.lesionNamingNumber
          )
          .forEach(groupTool => {
            groupTool.toolId = tool.id;
            groupTool.toolItemId = measurement._id;
            groupTool.createdAt = measurement.createdAt;
            groupTool.measurementNumber = measurement.measurementNumber;
          });
      } else {
        // Handle measurements not added by cornerstone tools and update its number
        const measurementsInTimepoint = groupCollection.filter(
          groupTool => groupTool.timepointId === timepoint.timepointId
        );
        measurement.lesionNamingNumber = this.calculateLesionNamingNumber(
          measurementsInTimepoint
        );
        measurement.measurementNumber =
          measurement.measurementNumber ||
          this.calculateMeasurementNumber(measurement) + 1;
      }
  
      // Define an update object to reflect the changes in the collection
      const updateObject = {
        timepointId: timepoint.timepointId,
        lesionNamingNumber: measurement.lesionNamingNumber,
        measurementNumber: measurement.measurementNumber,
      };
  
      // Find the matched measurement from other timepoints
      const found = this.getPreviousMeasurement(measurement);
  
      // Check if a previous related meausurement was found on other timepoints
      if (found) {
        // Use the same number as the previous measurement
        measurement.lesionNamingNumber = found.lesionNamingNumber;
        measurement.measurementNumber = found.measurementNumber;
  
        // TODO: Remove TrialPatientLocationUid from here and override it somehow
        // by dependant applications
  
        // Change the update object to set the same number, additionalData,
        // location, label and description to the current measurement
        updateObject.lesionNamingNumber = found.lesionNamingNumber;
        updateObject.measurementNumber = found.measurementNumber;
        updateObject.additionalData = measurement.additionalData || {};
        updateObject.additionalData.TrialPatientLocationUid =
          found.additionalData && found.additionalData.TrialPatientLocationUid;
        updateObject.location = found.location;
        updateObject.label = found.label;
        updateObject.description = found.description;
        updateObject.isSplitLesion = found.isSplitLesion;
        updateObject.isNodal = found.isNodal;
  
        const description = getDescription(found, measurement);
        if (description) {
          updateObject.description = description;
        }
      } else if (this.hasDuplicateMeasurementNumber(measurement)) {
        // Update measurementNumber for the measurements with masurementNumber greater or equal than
        //  measurementNumber of the added measurement (except the added one)
        //   only if there is another measurement with the same measurementNumber
        this.updateMeasurementNumberForAllMeasurements(measurement, 1);
      }
  
      let addedMeasurement;
  
      // Upsert the measurement in collection
      const toolIndex = collection.findIndex(
        tool => tool._id === measurement._id
      );
      if (toolIndex > -1) {
        addedMeasurement = Object.assign({}, collection[toolIndex], updateObject);
        collection[toolIndex] = addedMeasurement;
      } else {
        addedMeasurement = Object.assign({}, measurement, updateObject);
        collection.push(addedMeasurement);
      }
  
      if (!emptyItem) {
        // Reflect the entry in the tool group collection
        groupCollection.push({
          toolId: toolType,
          toolItemId: addedMeasurement._id,
          timepointId: timepoint.timepointId,
          studyInstanceUid: addedMeasurement.studyInstanceUid,
          createdAt: addedMeasurement.createdAt,
          lesionNamingNumber: addedMeasurement.lesionNamingNumber,
          measurementNumber: addedMeasurement.measurementNumber,
        });
      }
  
      // Let others know that the measurements are updated
      this.onMeasurementsUpdated();
  
      // TODO: Enable reactivity
      // this.timepointChanged.set(timepoint.timepointId);
  
      return addedMeasurement;
    }
  
    updateMeasurement(toolType, measurement) {
      const collection = this.tools[toolType];
  
      const toolIndex = collection.findIndex(
        tool => tool._id === measurement._id
      );
      if (toolIndex < 0) {
        return;
      }
  
      collection[toolIndex] = Object.assign({}, measurement);
  
      // Let others know that the measurements are updated
      this.onMeasurementsUpdated();
  
      // TODO: Enable reactivity
      // this.timepointChanged.set(timepoint.timepointId);
    }
  
    onMeasurementRemoved(toolType, measurement) {
      const { lesionNamingNumber, measurementNumber } = measurement;
  
      const toolGroupId = this.toolsGroupsMap[toolType];
      const groupCollection = this.toolGroups[toolGroupId];
  
      const groupIndex = groupCollection.findIndex(
        group => group.toolItemId === measurement._id
      );
      if (groupIndex < 0) {
        return;
      }
  
      //  Remove the deleted measurement only in its timepoint from the collection
      groupCollection.splice(groupIndex, 1);
  
      //  Check which timepoints have the deleted measurement
      const timepointsWithDeletedMeasurement = groupCollection
        .filter(tool => tool.measurementNumber === measurementNumber)
        .map(tool => tool.timepointId);
  
      //  Update lesionNamingNumber and measurementNumber only if there is no timepoint with that measurement
      if (timepointsWithDeletedMeasurement.length < 1) {
        //  Decrease lesionNamingNumber of all measurements with lesionNamingNumber greater than lesionNamingNumber of the deleted measurement by 1
        const lesionNamingNumberFilter = tool =>
          tool.lesionNamingNumber >= lesionNamingNumber;
        this.updateNumbering(
          groupCollection,
          lesionNamingNumberFilter,
          'lesionNamingNumber',
          -1
        );
  
        const toolGroup = configuration.measurementTools.find(
          tGroup => tGroup.id === toolGroupId
        );
        if (toolGroup && toolGroup.childTools) {
          toolGroup.childTools.forEach(childTool => {
            const collection = this.tools[childTool.id];
            this.updateNumbering(
              collection,
              lesionNamingNumberFilter,
              'lesionNamingNumber',
              -1
            );
          });
        }
  
        //  Decrease measurementNumber of all measurements with measurementNumber greater than measurementNumber of the deleted measurement by 1
        this.updateMeasurementNumberForAllMeasurements(measurement, -1);
      }
  
      // Synchronize the new tool data
      this.syncMeasurementsAndToolData();
  
      // Let others know that the measurements are updated
      this.onMeasurementsUpdated();
  
      // TODO: Enable reactivity
      // this.timepointChanged.set(timepoint.timepointId);
    }
  
    syncMeasurementsAndToolData() {
      configuration.measurementTools.forEach(toolGroup => {
        // Skip the tool groups excluded from case progress
        if (!MeasurementApi.isToolIncluded(toolGroup)) {
          return;
        }
        toolGroup.childTools.forEach(tool => {
          // Skip the tools excluded from case progress
          if (!MeasurementApi.isToolIncluded(tool)) {
            return;
          }
          const measurements = this.tools[tool.id];
          measurements.forEach(measurement => {
            MeasurementApi.syncMeasurementAndToolData(measurement);
          });
        });
      });
    }
  
    deleteMeasurements(toolType, measurementTypeId, filter) {
      const filterKeys = Object.keys(filter);
      const groupCollection = this.toolGroups[measurementTypeId];
  
      // Stop here if it is a temporary toolGroups
      if (!groupCollection) return;
  
      // Get the entries information before removing them
      const groupItems = groupCollection.filter(toolGroup => {
        return filterKeys.every(
          filterKey => toolGroup[filterKey] === filter[filterKey]
        );
      });
      const entries = [];
      groupItems.forEach(groupItem => {
        if (!groupItem.toolId) {
          return;
        }
  
        const collection = this.tools[groupItem.toolId];
        const toolIndex = collection.findIndex(
          tool => tool._id === groupItem.toolItemId
        );
        if (toolIndex > -1) {
          entries.push(collection[toolIndex]);
          collection.splice(toolIndex, 1);
        }
      });
  
      // Stop here if no entries were found
      if (!entries.length) {
        return;
      }
  
      // If the filter doesn't have the measurement number, get it from the first entry
      const lesionNamingNumber =
        filter.lesionNamingNumber || entries[0].lesionNamingNumber;
  
      // Synchronize the new data with cornerstone tools
      const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
  
      entries.forEach(entry => {
        const measurementsData = [];
        const { tool } = MeasurementApi.getToolConfiguration(entry.toolType);
        if (Array.isArray(tool.childTools)) {
          tool.childTools.forEach(key => {
            const childMeasurement = entry[key];
            if (!childMeasurement) return;
            measurementsData.push(childMeasurement);
          });
        } else {
          measurementsData.push(entry);
        }
  
        measurementsData.forEach(measurementData => {
          const { imagePath, toolType } = measurementData;
          const imageId = getImageIdForImagePath(imagePath);
          if (imageId && toolState[imageId]) {
            const toolData = toolState[imageId][toolType];
            const measurementEntries = toolData && toolData.data;
            const measurementEntry = measurementEntries.find(
              mEntry => mEntry._id === entry._id
            );
            if (measurementEntry) {
              const index = measurementEntries.indexOf(measurementEntry);
              measurementEntries.splice(index, 1);
            }
          }
        });
  
        this.onMeasurementRemoved(toolType, entry);
      });
  
      cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
        toolState
      );
  
      // Synchronize the updated measurements with Cornerstone Tools
      // toolData to make sure the displayed measurements show 'Target X' correctly
      const syncFilter = Object.assign({}, filter);
      delete syncFilter.timepointId;
      delete syncFilter.lesionNamingNumber;
  
      const syncFilterKeys = Object.keys(syncFilter);
  
      const toolTypes = [...new Set(entries.map(entry => entry.toolType))];
      toolTypes.forEach(toolType => {
        const collection = this.tools[toolType];
        collection
          .filter(tool => {
            return (
              tool.lesionNamingNumber > lesionNamingNumber - 1 &&
              syncFilterKeys.every(
                syncFilterKey => tool[syncFilterKey] === filter[syncFilterKey]
              )
            );
          })
          .forEach(measurement => {
            MeasurementApi.syncMeasurementAndToolData(measurement);
          });
      });
    }
  }
  