# frozen_string_literal: true

# Algorithms and Containers project is Copyright (c) 2009 Kanwei Li
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the 'Software'), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# A RbTreeMap is a map that is stored in sorted order based on the order of its keys. This ordering is
# determined by applying the function <=> to compare the keys. No duplicate values for keys are allowed,
# so duplicate values are overwritten.
#
# A major advantage of RBTreeMap over a Hash is the fact that keys are stored in order and can thus be
# iterated over in order. This is useful for many datasets.
#
# The implementation is adapted from Robert Sedgewick's Left Leaning Red-Black Tree implementation,
# which can be found at https://www.cs.princeton.edu/~rs/talks/LLRB/Java/RedBlackBST.java
#
# Most methods have O(log n) complexity.

class Puppet::Graph::RbTreeMap
  include Enumerable

  attr_reader :size

  alias_method :length, :size

  # Create and initialize a new empty TreeMap.
  def initialize
    @root = nil
    @size = 0
  end

  # Insert an item with an associated key into the TreeMap, and returns the item inserted
  #
  # Complexity: O(log n)
  #
  # map = Containers::TreeMap.new
  # map.push("MA", "Massachusetts") #=> "Massachusetts"
  # map.get("MA") #=> "Massachusetts"
  def push(key, value)
    @root = insert(@root, key, value)
    @root.color = :black
    value
  end
  alias_method :[]=, :push

  # Return true if key is found in the TreeMap, false otherwise
  #
  # Complexity: O(log n)
  #
  #   map = Containers::TreeMap.new
  #   map.push("MA", "Massachusetts")
  #   map.push("GA", "Georgia")
  #   map.has_key?("GA") #=> true
  #   map.has_key?("DE") #=> false
  def has_key?(key)
    !get_recursive(@root, key).nil?
  end

  # Return the item associated with the key, or nil if none found.
  #
  # Complexity: O(log n)
  #
  #   map = Containers::TreeMap.new
  #   map.push("MA", "Massachusetts")
  #   map.push("GA", "Georgia")
  #   map.get("GA") #=> "Georgia"
  def get(key)
    node = get_recursive(@root, key)
    node ? node.value : nil
    node.value if node
  end
  alias_method :[], :get

  # Return the smallest key in the map.
  #
  # Complexity: O(log n)
  #
  #   map = Containers::TreeMap.new
  #   map.push("MA", "Massachusetts")
  #   map.push("GA", "Georgia")
  #   map.min_key #=> "GA"
  def min_key
    @root.nil? ? nil : min_recursive(@root).key
  end

  # Return the largest key in the map.
  #
  # Complexity: O(log n)
  #
  #   map = Containers::TreeMap.new
  #   map.push("MA", "Massachusetts")
  #   map.push("GA", "Georgia")
  #   map.max_key #=> "MA"
  def max_key
    @root.nil? ? nil : max_recursive(@root).key
  end

  # Deletes the item and key if it's found, and returns the item. Returns nil
  # if key is not present.
  #
  # Complexity: O(log n)
  #
  #   map = Containers::TreeMap.new
  #   map.push("MA", "Massachusetts")
  #   map.push("GA", "Georgia")
  #   map.delete("MA") #=> "Massachusetts"
  def delete(key)
    result = nil
    if @root
      return unless has_key? key

      @root, result = delete_recursive(@root, key)
      @root.color = :black if @root
      @size -= 1
    end
    result
  end

  # Returns true if the tree is empty, false otherwise
  def empty?
    @root.nil?
  end

  # Deletes the item with the smallest key and returns the item. Returns nil
  # if key is not present.
  #
  # Complexity: O(log n)
  #
  #   map = Containers::TreeMap.new
  #   map.push("MA", "Massachusetts")
  #   map.push("GA", "Georgia")
  #   map.delete_min #=> "Massachusetts"
  #   map.size #=> 1
  def delete_min
    result = nil
    if @root
      @root, result = delete_min_recursive(@root)
      @root.color = :black if @root
      @size -= 1
    end
    result
  end

  # Deletes the item with the largest key and returns the item. Returns nil
  # if key is not present.
  #
  # Complexity: O(log n)
  #
  #   map = Containers::TreeMap.new
  #   map.push("MA", "Massachusetts")
  #   map.push("GA", "Georgia")
  #   map.delete_max #=> "Georgia"
  #   map.size #=> 1
  def delete_max
    result = nil
    if @root
      @root, result = delete_max_recursive(@root)
      @root.color = :black if @root
      @size -= 1
    end
    result
  end

  # Yields [key, value] pairs in order by key.
  def each(&blk)
    recursive_yield(@root, &blk)
  end

  def first
    return nil unless @root

    node = min_recursive(@root)
    [node.key, node.value]
  end

  def last
    return nil unless @root

    node = max_recursive(@root)
    [node.key, node.value]
  end

  def to_hash
    @root ? @root.to_hash : {}
  end

  class Node # :nodoc: all
    attr_accessor :color, :key, :value, :left, :right

    def initialize(key, value)
      @key = key
      @value = value
      @color = :red
      @left = nil
      @right = nil
    end

    def to_hash
      h = {
        :node => {
          :key => @key,
          :value => @value,
          :color => @color,
        }
      }
      h[:left] = left.to_hash if @left
      h[:right] = right.to_hash if @right
      h
    end

    def red?
      @color == :red
    end

    def colorflip
      @color       = @color == :red       ? :black : :red
      @left.color  = @left.color == :red  ? :black : :red
      @right.color = @right.color == :red ? :black : :red
    end

    def rotate_left
      r = @right
      r_key, r_value = r.key, r.value
      b = r.left
      r.left = @left
      @left = r
      @right = r.right
      r.right = b
      r.color, r.key, r.value = :red, @key, @value
      @key, @value = r_key, r_value
      self
    end

    def rotate_right
      l = @left
      l_key, l_value = l.key, l.value
      b = l.right
      l.right = @right
      @right = l
      @left = l.left
      l.left = b
      l.color, l.key, l.value = :red, @key, @value
      @key, @value = l_key, l_value
      self
    end

    def move_red_left
      colorflip
      if (@right.left && @right.left.red?)
        @right.rotate_right
        rotate_left
        colorflip
      end
      self
    end

    def move_red_right
      colorflip
      if (@left.left && @left.left.red?)
        rotate_right
        colorflip
      end
      self
    end

    def fixup
      rotate_left if @right && @right.red?
      rotate_right if (@left && @left.red?) && (@left.left && @left.left.red?)
      colorflip if (@left && @left.red?) && (@right && @right.red?)

      self
    end
  end

  private

  def recursive_yield(node, &blk)
    return unless node

    recursive_yield(node.left, &blk)
    yield node.key, node.value
    recursive_yield(node.right, &blk)
  end

  def delete_recursive(node, key)
    if (key <=> node.key) == -1
      node.move_red_left if (!isred(node.left) && !isred(node.left.left))
      node.left, result = delete_recursive(node.left, key)
    else
      node.rotate_right if isred(node.left)
      if (((key <=> node.key) == 0) && node.right.nil?)
        return nil, node.value
      end

      if (!isred(node.right) && !isred(node.right.left))
        node.move_red_right
      end
      if (key <=> node.key) == 0
        result = node.value
        min_child = min_recursive(node.right)
        node.value = min_child.value
        node.key = min_child.key
        node.right = delete_min_recursive(node.right).first
      else
        node.right, result = delete_recursive(node.right, key)
      end
    end
    return node.fixup, result
  end

  def delete_min_recursive(node)
    if node.left.nil?
      return nil, node.value
    end

    if (!isred(node.left) && !isred(node.left.left))
      node.move_red_left
    end
    node.left, result = delete_min_recursive(node.left)

    return node.fixup, result
  end

  def delete_max_recursive(node)
    if (isred(node.left))
      node = node.rotate_right
    end
    return nil, node.value if node.right.nil?

    if (!isred(node.right) && !isred(node.right.left))
      node.move_red_right
    end
    node.right, result = delete_max_recursive(node.right)

    return node.fixup, result
  end

  def get_recursive(node, key)
    return nil if node.nil?

    case key <=> node.key
    when 0 then return node
    when -1 then return get_recursive(node.left, key)
    when  1 then return get_recursive(node.right, key)
    end
  end

  def min_recursive(node)
    return node if node.left.nil?

    min_recursive(node.left)
  end

  def max_recursive(node)
    return node if node.right.nil?

    max_recursive(node.right)
  end

  def insert(node, key, value)
    unless node
      @size += 1
      return Node.new(key, value)
    end

    case key <=> node.key
    when 0 then node.value = value
    when -1 then node.left = insert(node.left, key, value)
    when  1 then node.right = insert(node.right, key, value)
    end

    node.rotate_left if (node.right && node.right.red?)
    node.rotate_right if (node.left && node.left.red? && node.left.left && node.left.left.red?)
    node.colorflip if (node.left && node.left.red? && node.right && node.right.red?)
    node
  end

  def isred(node)
    return false if node.nil?

    node.color == :red
  end
end
