module Oppen
    class PrintStack
        class PrintStackEntry
                  attr_reader :break_type
                  attr_reader :offset
      def initialize(offset, break_type)
        @offset = offset
        @break_type = break_type
      end
    end
        attr_reader :buffer
        attr_reader :config
        attr_reader :genspace
        attr_reader :items
        attr_reader :new_line
        attr_reader :space
        attr_reader :width
    def initialize(width, new_line, config, space, out)
      @buffer = out
      @config = config
      @genspace =
        if space.respond_to? :call
          raise ArgumentError, 'space argument must be a Proc of arity 1' \
            if space.to_proc.arity != 1
          space
        else
          ->(n) { space * n }
        end
      @indent = 0       @items = []
      @new_line = new_line
      @width = width
      @space = width
    end
                    def output
      buffer.truncate buffer.pos
      buffer.string
    end
                                                    def print(token, token_width, trim_on_break: 0)
      case token
      in Token::Begin
        handle_begin token, token_width
      in Token::End
        handle_end
      in Token::Break
        handle_break token, token_width, trim_on_break:
      in Token::String
        handle_string token, token_width
      end
    end
                            def handle_begin(token, token_width)
      if token_width > space
        type =
          if token.break_type == :consistent
            :consistent
          else
            :inconsistent
          end
        if config&.indent_anchor == :current_offset
          indent = token.offset
          if !items.empty?
            indent += top.offset
          end
        else
          indent = space - token.offset
        end
        push PrintStackEntry.new indent, type
      else
        push PrintStackEntry.new 0, :fits
      end
    end
                def handle_end
      pop
    end
                                        def handle_break(token, token_width, trim_on_break: 0)
      block = top
      case block.break_type
      in :fits
                @space -= token.width
        write token
      in :consistent
        @space = block.offset - token.offset
        indent =
          if config&.indent_anchor == :current_offset
            token.offset
          else
            width - space
          end
        erase trim_on_break
        write token.line_continuation
        print_new_line indent
      in :inconsistent
        if token_width > space
          @space = block.offset - token.offset
          indent =
            if config&.indent_anchor == :current_offset
              token.offset
            else
              width - space
            end
          erase trim_on_break
          write token.line_continuation
          print_new_line indent
        else
          @space -= token.width
          write token
        end
      end
    end
                            def handle_string(token, token_width)
      return if token.value.empty?
      @space = [0, space - token_width].max
      if @indent.positive?
        indent @indent
        @indent = 0
      end
      write token
    end
                        def push(print_stack_entry)
      items.append print_stack_entry
    end
                def pop
      if items.empty?
        raise 'Popping empty stack'
      end
      items.pop
    end
                def top
      if items.empty?
        raise 'Accessing empty stack'
      end
      items.last
    end
                                    def print_new_line(amount)
      write new_line
      if config&.indent_anchor == :current_offset
        @space = width - top.offset - amount
        @indent = width - space
      else
        @indent = amount
      end
    end
                        def write(obj)
      buffer.write obj.to_s
    end
                        def erase(count = 0)
      raise ArgumentError, "count = #{count} must be non-negative" if count.negative?
      buffer.seek(-count, IO::SEEK_CUR)
      @space += count
    end
                                def indent(amount)
      raise ArgumentError 'Indenting using negative amount' if amount.negative?
      write genspace.(amount) if amount.positive?
    end
  end
end