module Bitcoin

  # A class for recovering partial from merkleblock message.
  # For a complete Merkle tree implementation, migrate to the merkle gem.
  class PartialTree

    attr_accessor :root

    def initialize(root = nil)
      @root = root
    end

    def merkle_root
      root.value
    end

    # https://bitcoin.org/en/developer-reference#creating-a-merkleblock-message
    def self.build(tx_count, hashes, flags)
      flags = flags.each_char.map(&:to_i)
      root = build_initial_tree( Array.new(tx_count) { Node.new })
      current_node = root
      hash_index = 0
      flags.each do |f|
        current_node.flag = f
        if f.zero? || current_node.leaf?
          current_node.value = hashes[hash_index]
          hash_index += 1
        end
        current_node = current_node.next_partial
        if hash_index == hashes.size
          if current_node&.leaf?
            current_node.value = hashes.last
          end
          break
        end
      end
      new(root)
    end

    def self.build_initial_tree(nodes)
      while nodes.size != 1
        nodes = nodes.each_slice(2).map { |m|
          parent = Node.new
          parent.left = m[0]
          parent.right = m[1] ? m[1] : m[0].dup
          parent
        }
      end
      nodes.first
    end

    def find_node(value)
      root.find_node(value)
    end

    # node of merkle tree
    class Node

      attr_accessor :flag
      attr_accessor :value
      attr_accessor :parent
      attr_accessor :left
      attr_accessor :right

      def initialize(value = nil)
        @value = value
      end

      def left=(node)
        node.parent = self
        @left = node
      end

      def right=(node)
        node.parent = self
        @right = node
      end

      def value
        return @value if @value
        self.right = left.dup unless right
        Bitcoin.double_sha256([left.value + right.value].pack('H*')).bth
      end

      def root?
        parent.nil?
      end

      def leaf?
        right.nil? && left.nil?
      end

      def partial?
        !flag.nil?
      end

      def next_partial
        return nil if root? && (flag.zero? || (left.nil? && right.nil?) || (left.partial? && right.partial?))
        return parent.next_partial if flag.zero? || leaf?
        return left unless left.partial?
        self.right = left.dup unless right
        right.partial? ? parent.next_partial : right
      end

      # calculate the depth of this node in the tree.
      def depth
        d = 0
        current_node = self
        until current_node.root? do
          current_node = current_node.parent
          d += 1
        end
        d
      end

      # @param target value to be found
      # @return node which has same value as target. nil if this node and any children don't have same value.
      def find_node(target)
        return self if value == target
        return nil if flag && flag.zero?
        return left&.find_node(target) || right&.find_node(target)
      end

      def index
        i = 0
        d = 1
        current_node = self
        until current_node.root? do
          i += d if current_node.parent.right == current_node
          current_node = current_node.parent
          d *= 2
        end
        i
      end
    end
  end
end
