All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.arturopala.gitignore.Zoom.scala Maven / Gradle / Ivy

/*
 * Copyright 2021 Artur Opala
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.arturopala.gitignore

import scala.annotation.tailrec

/** A Mutable wrapper of the character sequence.
  * Helps track different lookup operations.
  */
final class Zoom(input: CharSequence) {

  private var from: Int = 0
  private var to: Int = input.length()
  private var min: Int = Int.MaxValue
  private var max: Int = Int.MinValue

  def start(): Int = min
  def end(): Int = max
  def frame: (Int, Int) = (from, to)
  def left: Int = from
  def right: Int = to
  def isEmpty: Boolean = from >= to
  def nonEmpty: Boolean = from < to
  def frameWidth: Int = Math.max(0, to - from)

  def contour: (Int, Int) = (min, max)
  def hasContour: Boolean = max >= 0 && min <= input.length() && min <= max
  def contourLength: Int = if (hasContour) max - min else 0

  def minElseBottom: Int = if (min != Int.MaxValue) min else 0
  def maxElseTop: Int = if (max != Int.MinValue) max else input.length
  def minElseTop: Int = if (min != Int.MaxValue) min else input.length
  def maxElseBottom: Int = if (max != Int.MinValue) max else 0

  def setFrame(from: Int, to: Int): Zoom = {
    this.from = Math.max(Math.min(from, input.length() - 1), 0)
    this.to = Math.min(input.length(), Math.max(from, to))
    this
  }

  def resetContour: Zoom = {
    min = Int.MaxValue
    max = Int.MinValue
    this
  }

  /** Similar to [[String.indexOf]] */
  def indexOf(string: String): Int = {
    @tailrec
    def lookup(
      stringPos: Int,
      inputPos: Int,
      mark: Int,
      first: Boolean
    ): Int =
      if (stringPos == string.length()) mark
      else if (inputPos == input.length()) -1
      else {
        if (input.charAt(inputPos) == string.charAt(stringPos))
          lookup(stringPos + 1, inputPos + 1, if (first) inputPos else mark, false)
        else if (first)
          lookup(0, inputPos + 1, -1, true)
        else
          lookup(0, mark + 1, -1, true)
      }

    if (string.isEmpty) 0
    else lookup(0, from, -1, true)
  }

  /** Similar to [[String.lastIndexOf]] */
  def lastIndexOf(string: String): Int = {
    @tailrec
    def lookup(
      stringPos: Int,
      inputPos: Int,
      mark: Int,
      first: Boolean
    ): Int =
      if (stringPos < 0) mark - string.length() + 1
      else if (inputPos < 0) -1
      else {
        if (input.charAt(inputPos) == string.charAt(stringPos))
          lookup(stringPos - 1, inputPos - 1, if (first) inputPos else mark, false)
        else if (first)
          lookup(string.length() - 1, inputPos - 1, -1, true)
        else
          lookup(string.length() - 1, mark - 1, -1, true)
      }

    if (string.isEmpty) 0
    else lookup(string.length() - 1, to - 1, -1, true)
  }

  /** Lookup for the given string from the left side. */
  def lookupRightFor(string: String, maxDistance: Int = Int.MaxValue): Boolean =
    nonEmpty && {
      Debug.debug(s"lookupRightFor${if (maxDistance == Int.MaxValue) "" else "(adjacent)"}: $string\n   $this")
      val i = indexOf(string)
      (i - from <= maxDistance && i >= from && (i + string.length) <= to) && {
        from = i + string.length
        min = Math.min(min, i)
        max = Math.max(max, i + string.length)
        Debug.debug(s"=> $this")
        true
      }
    }

  /** Lookup for the given string from the right side. */
  def lookupLeftFor(string: String, maxDistance: Int = Int.MaxValue): Boolean =
    nonEmpty && {
      Debug.debug(s"lookupLeftFor${if (maxDistance == Int.MaxValue) "" else "(adjacent)"}: $string\n   $this")
      val i = lastIndexOf(string)
      ((to - i - string.length) <= maxDistance && i >= from && (i + string.length) <= to) && {
        to = i
        min = Math.min(min, i)
        max = Math.max(max, i + string.length)
        Debug.debug(s"=> $this")
        true
      }
    }

  /** Lookup for the given string from both sides. */
  def lookupFor(string: String): Boolean =
    nonEmpty && {
      Debug.debug(s"lookupFor: $string\n   $this")
      val i1 = indexOf(string)
      val i2 = lastIndexOf(string)
      min =
        if (i1 >= 0) i1
        else Int.MaxValue
      max =
        if (i2 >= 0 && i2 + string.length <= to) i2 + string.length
        else Int.MinValue
      Debug.debug(hasContour, s"=> $this")
      hasContour
    }

  /** Lookup from the left side while check succeeds. */
  def lookupRightWhile(check: Char => Boolean, maxSteps: Int = Int.MaxValue): Boolean =
    nonEmpty && {
      Debug.debug(s"lookupRightWhile:\n   $this")
      var c = maxSteps
      val mark = from
      while (c > 0 && from < to && check(input.charAt(from))) {
        from = from + 1
        c = c - 1
      }

      from > mark && {
        min = Math.min(min, mark)
        max = Math.max(max, from)
        Debug.debug(s"=> $this")
        true
      }
    }

  /** Lookup from the right side while check succeeds. */
  def lookupLeftWhile(check: Char => Boolean, maxSteps: Int = Int.MaxValue): Boolean =
    nonEmpty && {
      Debug.debug(s"lookupLeftWhile:\n   $this")
      var c = maxSteps
      val mark = to
      while (c > 0 && to > from && check(input.charAt(to - 1))) {
        to = to - 1
        c = c - 1
      }

      to < mark && {
        min = Math.min(min, to)
        max = Math.max(max, mark)
        Debug.debug(s"=> $this")
        true
      }
    }

  def lookupWhile(check: Char => Boolean, maxSteps: Int = 0): Boolean = {
    Debug.debug(s"lookupWhile:\n   $this")
    var i1 = from
    var stop1 = i1 >= to || check(input.charAt(i1))
    while (!stop1)
      stop1 = { i1 = i1 + 1; i1 >= to } || check(input.charAt(i1))
    var i2 = to
    var stop2 = i2 <= from || check(input.charAt(i2 - 1))
    while (!stop2)
      stop2 = { i2 = i2 - 1; i2 <= from } || check(input.charAt(i2 - 1))

    min = Math.min(min, i1)
    max = Math.max(max, i2)
    val r = hasContour && contourLength >= maxSteps
    Debug.debug(r, s"=> $this")
    r
  }

  /** Lookup from the left side until check succeeds. */
  def lookupRightUntil(check: Char => Boolean, minSteps: Int = 0, maxSteps: Int = Int.MaxValue): Boolean =
    (isEmpty && {
      Debug.debug(s"lookupRightUntil (empty):\n   $this")
      min = Math.min(from, min)
      max = Math.max(to, max)
      Debug.debug(s"=> $this")
      true
    }) || {
      Debug.debug(s"lookupRightUntil:\n   $this")
      var c = maxSteps
      val mark = from
      while (c > 0 && from < to && !check(input.charAt(from))) {
        from = from + 1
        c = c - 1
      }

      (from - mark >= minSteps) && {
        min = Math.min(min, mark)
        max = Math.max(max, from)
        Debug.debug(s"=> $this")
        true
      }
    }

  /** Lookup from right until check succeeds. */
  def lookupLeftUntil(check: Char => Boolean, minSteps: Int = 0, maxSteps: Int = Int.MaxValue): Boolean =
    (isEmpty && {
      Debug.debug(s"lookupLeftUntil (empty):\n   $this")
      min = Math.min(from, min)
      max = Math.max(to, max)
      Debug.debug(s"=> $this")
      true
    }) || {
      Debug.debug(s"lookupLeftUntil:\n   $this")
      var c = maxSteps
      val mark = to
      while (c > 0 && to > from && !check(input.charAt(to - 1))) {
        to = to - 1
        c = c - 1
      }

      (mark - to >= minSteps) && {
        min = Math.min(min, to)
        max = Math.max(max, mark)
        Debug.debug(s"=> $this")
        true
      }
    }

  def lookupWhileNot(check: Char => Boolean, minSteps: Int = Int.MaxValue, maxSteps: Int = 0): Boolean = {
    Debug.debug(s"lookupUntil:\n   $this")
    var i1 = from
    var stop1 = i1 >= to || !check(input.charAt(i1))
    while (!stop1)
      stop1 = { i1 = i1 + 1; i1 >= to } || !check(input.charAt(i1))
    var i2 = to
    var stop2 = i2 <= from || !check(input.charAt(i2 - 1))
    while (!stop2)
      stop2 = { i2 = i2 - 1; i2 <= from } || !check(input.charAt(i2 - 1))

    min = Math.min(min, i1)
    max = Math.max(max, i2)
    val r = hasContour && {
      val l = contourLength
      l >= maxSteps && l <= minSteps
    }
    Debug.debug(r, s"=> $this")
    r
  }

  /** Expand contour from the left side. */
  def takeAllFromLeft(): Boolean = {
    Debug.debug(s"takeAllFromLeft:\n   $this")
    val mark = from
    from = to
    min = Math.min(min, mark)
    max = Math.max(max, from)
    Debug.debug(s"=> $this")
    true
  }

  /** Expand contour from the right side. */
  def takeAllFromRight(): Boolean = {
    Debug.debug(s"takeAllFromRight:\n   $this")
    val mark = to
    to = from
    min = Math.min(min, to)
    max = Math.max(max, mark)
    Debug.debug(s"=> $this")
    true
  }

  def takeAll(): Boolean = {
    Debug.debug(s"takeAll:\n   $this")
    min = Math.min(min, from)
    max = Math.max(max, to)
    Debug.debug(hasContour, s"=> $this")
    hasContour
  }

  /** Change frame boundary on the left or the right side. */
  def resizeFrame(distance: Int, leftSide: Boolean): Unit =
    if (leftSide) {
      from = Math.min(from + distance, to)
    } else {
      to = Math.max(to - distance, from)
    }

  def noOverlapBetweenContours(other: Zoom): Boolean =
    this.maxElseBottom <= other.minElseTop || other.maxElseBottom <= this.minElseTop

  /** Move this frame on the left or the right side of the other Zoom's contour. */
  def flipFrame(other: Zoom, leftSide: Boolean): Boolean = {
    val r = noOverlapBetweenContours(other)
    Debug.debug(r, s"flipFrame: ${if (leftSide) "left" else "right"}\n   $this\n&  $other")
    r && {
      if (leftSide) {
        from = other.from
        to = Math.max(from, Math.min(this.to, this.min))
      } else {
        to = other.to
        from = Math.min(to, Math.max(this.from, this.max))
      }
      Debug.debug(s"=> $this")
      true
    }
  }

  def noGapBetweenContours(other: Zoom): Boolean =
    this.minElseBottom <= other.maxElseTop && this.maxElseTop >= other.minElseBottom

  /** Merge contours and frames. */
  def merge(other: Zoom): Boolean = {
    val r = noGapBetweenContours(other)
    Debug.debug(r, s"merge:\n   $this\n&  $other")
    from = Math.max(this.from, other.from)
    to = Math.min(this.to, other.to)
    min = Math.min(this.min, other.min)
    max = Math.max(this.max, other.max)
    Debug.debug(r, s"=> $this")
    r
  }

  /** Intersect contours. */
  def intersectContour(other: Zoom): Boolean = {
    if (hasContour && other.hasContour) {
      min = Math.max(this.min, other.min)
      max = Math.min(this.max, other.max)
    } else {
      resetContour
    }
    hasContour
  }

  /** Union contours. */
  def unionContour(other: Zoom): Boolean = {
    if (hasContour && other.hasContour) {
      min = Math.min(this.min, other.min)
      max = Math.max(this.max, other.max)
    } else {
      resetContour
    }
    hasContour
  }

  def closeUpFrameAndResetContour: Boolean =
    (hasContour && {
      from = min
      to = max
      true
    }) & {
      resetContour
      true
    }

  def squeezeRightOrLeft(offset: Int, minWidth: Int, maxTo: Int): Boolean = {
    Debug.debug(s"squeezeRightOrLeft $offset $minWidth $maxTo\n   $this")
    to = to - 1
    if ((isEmpty || frameWidth < minWidth) && to < maxTo) {
      from = from + 1
      to = maxTo
    }
    min = Int.MaxValue
    max = Int.MinValue
    val r = nonEmpty && frameWidth >= minWidth
    Debug.debug(r, s"=> $this")
    r
  }

  /** Copy frame and reset contour. */
  def copyFrameAndResetContour: Zoom = {
    val z = Zoom(input)
    z.from = this.from
    z.to = this.to
    Debug.debug(s"copyFrameAndResetContour:\n   $z")
    z
  }

  /** Exact copy of this Zoom. */
  def copy: Zoom = {
    val z = Zoom(input)
    z.from = this.from
    z.to = this.to
    z.min = this.min
    z.max = this.max
    z
  }

  final def prettyPrint: String = {
    import scala.io.AnsiColor._
    val sb = new StringBuilder()
    var i = 0
    while (i < input.length()) {
      if (i >= from && i < to) sb.append(BLUE) else sb.append(CYAN)
      if (i >= min && i < max) sb.append(WHITE_B + BOLD)
      sb.append(input.charAt(i))
      sb.append(RESET)
      i = i + 1
    }
    sb.toString()
  }

  override def toString(): String =
    s"$prettyPrint from=$from to=$to min=${if (min != Int.MaxValue) min.toString else "_"} max=${if (max != Int.MinValue) max.toString
    else "_"}"

}

object Zoom {

  def apply(value: CharSequence): Zoom =
    new Zoom(value)

  def apply(value: CharSequence, from: Int): Zoom = {
    val z = Zoom(value)
    z.from = from
    z
  }

  def apply(value: CharSequence, from: Int, to: Int): Zoom = {
    val z = Zoom(value)
    z.from = from
    z.to = to
    z
  }

  def apply(value: CharSequence, from: Int, to: Int, min: Int, max: Int): Zoom = {
    val z = Zoom(value)
    z.from = from
    z.to = to
    z.min = min
    z.max = max
    z
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy