{spliceWithArray} = require 'underscore-plus'

# Used by the display buffer to map screen rows to buffer rows and vice-versa.
# This mapping may not be 1:1 due to folds and soft-wraps. This object maintains
# an array of regions, which contain `bufferRows` and `screenRows` fields.
#
# Rectangular Regions:
# If a region has the same number of buffer rows and screen rows, it is referred
# to as "rectangular", and represents one or more non-soft-wrapped, non-folded
# lines.
#
# Trapezoidal Regions:
# If a region has one buffer row and more than one screen row, it represents a
# soft-wrapped line. If a region has one screen row and more than one buffer
# row, it represents folded lines
module.exports =
class RowMap
  constructor: ->
    @regions = []

  # Public: Returns a copy of all the regions in the map
  getRegions: ->
    @regions.slice()

  # Public: Returns an end-row-exclusive range of screen rows corresponding to
  # the given buffer row. If the buffer row is soft-wrapped, the range may span
  # multiple screen rows. Otherwise it will span a single screen row.
  screenRowRangeForBufferRow: (targetBufferRow) ->
    {region, bufferRows, screenRows} = @traverseToBufferRow(targetBufferRow)

    if region? and region.bufferRows isnt region.screenRows
      [screenRows, screenRows + region.screenRows]
    else
      screenRows += targetBufferRow - bufferRows
      [screenRows, screenRows + 1]

  # Public: Returns an end-row-exclusive range of buffer rows corresponding to
  # the given screen row. If the screen row is the first line of a folded range
  # of buffer rows, the range may span multiple buffer rows. Otherwise it will
  # span a single buffer row.
  bufferRowRangeForScreenRow: (targetScreenRow) ->
    {region, screenRows, bufferRows} = @traverseToScreenRow(targetScreenRow)
    if region? and region.bufferRows isnt region.screenRows
      [bufferRows, bufferRows + region.bufferRows]
    else
      bufferRows += targetScreenRow - screenRows
      [bufferRows, bufferRows + 1]

  # Public: If the given buffer row is part of a folded row range, returns that
  # row range. Otherwise returns a range spanning only the given buffer row.
  bufferRowRangeForBufferRow: (targetBufferRow) ->
    {region, bufferRows} = @traverseToBufferRow(targetBufferRow)
    if region? and region.bufferRows isnt region.screenRows
      [bufferRows, bufferRows + region.bufferRows]
    else
      [targetBufferRow, targetBufferRow + 1]

  # Public: Given a starting buffer row, the number of buffer rows to replace,
  # and an array of regions of shape {bufferRows: n, screenRows: m}, splices
  # the regions at the appropriate location in the map. This method is used by
  # display buffer to keep the map updated when the underlying buffer changes.
  spliceRegions: (startBufferRow, bufferRowCount, regions) ->
    endBufferRow = startBufferRow + bufferRowCount
    {index, bufferRows} = @traverseToBufferRow(startBufferRow)
    precedingRows = startBufferRow - bufferRows

    count = 0
    while region = @regions[index + count]
      count++
      bufferRows += region.bufferRows
      if bufferRows >= endBufferRow
        followingRows = bufferRows - endBufferRow
        break

    if precedingRows > 0
      regions.unshift({bufferRows: precedingRows, screenRows: precedingRows})

    if followingRows > 0
      regions.push({bufferRows: followingRows, screenRows: followingRows})

    spliceWithArray(@regions, index, count, regions)
    @mergeAdjacentRectangularRegions(index - 1, index + regions.length)

  traverseToBufferRow: (targetBufferRow) ->
    bufferRows = 0
    screenRows = 0
    for region, index in @regions
      if (bufferRows + region.bufferRows) > targetBufferRow
        return {region, index, screenRows, bufferRows}
      bufferRows += region.bufferRows
      screenRows += region.screenRows
    {index, screenRows, bufferRows}

  traverseToScreenRow: (targetScreenRow) ->
    bufferRows = 0
    screenRows = 0
    for region, index in @regions
      if (screenRows + region.screenRows) > targetScreenRow
        return {region, index, screenRows, bufferRows}
      bufferRows += region.bufferRows
      screenRows += region.screenRows
    {index, screenRows, bufferRows}

  mergeAdjacentRectangularRegions: (startIndex, endIndex) ->
    for index in [endIndex..startIndex]
      if 0 < index < @regions.length
        leftRegion = @regions[index - 1]
        rightRegion = @regions[index]
        leftIsRectangular = leftRegion.bufferRows is leftRegion.screenRows
        rightIsRectangular = rightRegion.bufferRows is rightRegion.screenRows
        if leftIsRectangular and rightIsRectangular
          @regions.splice index - 1, 2,
            bufferRows: leftRegion.bufferRows + rightRegion.bufferRows
            screenRows: leftRegion.screenRows + rightRegion.screenRows

  # Public: Returns an array of strings describing the map's regions.
  inspect: ->
    for {bufferRows, screenRows} in @regions
      "#{bufferRows}:#{screenRows}"
