/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * Check if the position is within this function location
 *
 * @param {object} functionLocation
 * @param {object} position
 * @returns {boolean}
 */
function containsPosition(functionLocation, position) {
  // Start
  return (
    (functionLocation.start.line < position.line ||
      // If the start line is equal check the columns
      (functionLocation.start.line == position.line &&
        functionLocation.start.column <= position.column)) &&
    // End
    (functionLocation.end.line > position.line ||
      // If the end line is equal check the columns
      (functionLocation.end.line == position.line &&
        functionLocation.end.column >= position.column))
  );
}

function containsLocation(parentLocation, childLocation) {
  return (
    containsPosition(parentLocation, childLocation.start) &&
    containsPosition(parentLocation, childLocation.end)
  );
}

function getInnerLocations(locations, position) {
  // First, find the function which directly contains the specified position (line / column)
  let parentIndex;
  for (let i = locations.length - 1; i >= 0; i--) {
    if (containsPosition(locations[i], position)) {
      parentIndex = i;
      break;
    }
  }

  if (parentIndex == undefined) {
    return [];
  }

  const parentLoc = locations[parentIndex];

  // Then, from the nearest location, loop locations again and put locations into
  // the innerLocations array until we get to a location not enclosed by the nearest location.
  const innerLocations = [];
  for (let i = parentIndex + 1; i < locations.length; i++) {
    const loc = locations[i];
    if (!containsLocation(parentLoc, loc)) {
      break;
    }
    innerLocations.push(loc);
  }

  return innerLocations;
}

/**
 * Sort based on the start line
 *
 * @param {Array} locations
 * @returns
 */
function sortByStart(locations) {
  return locations.sort((a, b) => {
    if (a.startLine < b.startLine) {
      return -1;
    } else if (a.startLine === b.startLine) {
      return b.endLine - a.endLine;
    }
    return 1;
  });
}

/**
 * Return a new locations array which excludes
 * items that are completely enclosed by another location in the input locations
 *
 * @param locations Notice! The locations MUST be sorted by `sortByStart`
 *                  so that we can do linear time complexity operation.
 */
function removeOverlapLocations(locations) {
  if (!locations.length) {
    return [];
  }
  const firstParent = locations[0];
  return locations.reduce(deduplicateNode, [firstParent]);
}

function deduplicateNode(nodes, location) {
  const parent = nodes[nodes.length - 1];
  if (!containsLocation(parent, location)) {
    nodes.push(location);
  }
  return nodes;
}

function getOutOfScopeLines(outOfScopeLocations) {
  if (!outOfScopeLocations) {
    return new Set();
  }

  const uniqueLines = new Set();
  for (const location of outOfScopeLocations) {
    for (let i = location.startLine; i < location.endLine; i++) {
      uniqueLines.add(i);
    }
  }

  return uniqueLines;
}

module.exports = {
  containsPosition,
  containsLocation,
  getInnerLocations,
  removeOverlapLocations,
  getOutOfScopeLines,
  sortByStart,
};
