module VER
  class TilingLayout < Tk::Tile::LabelFrame
    require_relative 'tiling/common'
    require_relative 'tiling/horizontal'
    require_relative 'tiling/vertical'

    attr_reader :strategy, :stack, :options

    def initialize(parent, options = {})
      super

      pack(fill: :both, expand: true)

      # @stack contains the buffers in the order they are displayed
      @stack = []
      @options = {}
      self.strategy = VerticalTiling
    end

    def strategy=(mod)
      @strategy = mod
      apply if stack.any?
    end

    def create_buffer(options = {})
      buffer = Buffer.new(self, options)
      yield buffer
      stack.unshift(buffer)

      apply
      buffer.focus
    end

    def close_buffer(buffer)
      stack.delete(buffer)
      buffer.destroy

      if previous = stack.first
        apply
        previous.focus
      else
        VER.exit
      end
    end

    def focus_next(current)
      visible = head_tail_hidden.first(2).flatten
      return unless index = visible.index(current)

      found = visible[index + 1] || visible[0]
      found.focus
    end

    def focus_prev(current)
      visible = head_tail_hidden.first(2).flatten

      return unless index = visible.index(current)

      found = visible[index - 1]
      found.focus
    end

    # called on #3
    # +-----+-----+ > +-----+-----+ > +-----+-----+ > +-----+-----+
    # |     |  2  | > |     |  3  | > |     |  1  | > |     |  1  |
    # |  1  +-----+ > |  1  +-----+ > |  3  +-----+ > |  3  +-----+
    # |     |  3  | > |     |  2  | > |     |  2  | > |     |  2  |
    # +-----+-----+ > +-----+-----+ > +-----+-----+ > +-----+-----+
    def push_up(current)
      return unless index = stack.index(current)
      previous = stack[index - 1]
      current.raise(previous)
      stack[index - 1] = current
      stack[index] = previous

      apply
      previous.focus unless visible?(current)
    end

    # called on #3
    # +-----+-----+ > +-----+-----+ > +-----+-----+ > +-----+-----+
    # |     |  2  | > |     |  1  | > |     |  3  | > |     |  2  |
    # |  1  +-----+ > |  3  +-----+ > |  1  +-----+ > |  1  +-----+
    # |     |  3  | > |     |  2  | > |     |  2  | > |     |  3  |
    # +-----+-----+ > +-----+-----+ > +-----+-----+ > +-----+-----+
    def push_down(current)
      return unless index = stack.index(current)
      following = stack[index + 1] || stack[0]
      current.raise(following)
      stack[stack.index(following)] = current
      stack[index] = following

      apply
      following.focus unless visible?(current)
    end

    def push_top(current)
      stack.unshift(stack.delete(current))
      apply
    end

    def push_bottom(buffer)
      stack.push(stack.delete(buffer))
      apply
      stack.first.focus unless visible?(buffer)
    end

    def cycle_next(current)
      return unless index = stack.index(current)
      stack.push(stack.shift)
      apply
      stack[index].focus
    end

    def cycle_prev(current)
      return unless index = stack.index(current)
      stack.unshift(stack.pop)
      apply
      stack[index].focus
    end

    def apply(options = {})
      @options.merge!(options)
      strategy.apply(self, @options)
    end

    def head_tail_hidden(options = {})
      @options.merge!(options)
      strategy.prepare(self, @options).first(3)
    end

    def visible?(buffer)
      visible = head_tail_hidden.first(2).flatten
      visible.include?(buffer)
    end
  end
end
