# frozen_string_literal: true

module Vedeu

  module DSL

    # There are two ways to construct views with Vedeu. You would like
    # to draw the view to the screen immediately (immediate render) or
    # you want to save a view to be drawn when you trigger a refresh
    # event later (deferred view).
    #
    # Both of these approaches require that you have defined an
    # interface (or 'visible area') first. You can find out how to
    # define an interface with Vedeu below or in
    # {Vedeu::Interfaces::DSL}. The examples in 'Immediate Render' and
    # 'Deferred View' use these interface definitions: (Note: should
    # you use these examples, ensure your terminal is at least 70
    # characters in width and 5 lines in height.)
    #
    #   Vedeu.interface :main do
    #     geometry do
    #       height 4
    #       width  50
    #     end
    #   end
    #
    #   Vedeu.interface :title do
    #     geometry do
    #       height 1
    #       width  50
    #       x      use('main').left
    #       y      use('main').north
    #     end
    #   end
    #
    # Both of these approaches use a concept of Buffers in Vedeu.
    # There are three buffers for any defined interface. These are
    # imaginatively called: 'back', 'front' and 'previous'.
    #
    # The 'back' buffer is the content for an interface which will be
    # shown next time a refresh event is fired globally or for that
    # interface. So, 'back' becomes 'front'.
    #
    # The 'front' buffer is the content for an interface which is
    # currently showing. When a refresh event is fired, again,
    # globally or for that interface specifically, the content of this
    # 'front' buffer is first copied to the 'previous' buffer, and
    # then the current 'back' buffer overwrites this 'front' buffer.
    #
    # The 'previous' buffer contains what was shown on the 'front'
    # before the current 'front'.
    #
    # You can only write to either the 'front' (you want the content
    # to be drawn immediately (immediate render)) or the 'back' (you
    # would like the content to be drawn on the next refresh
    # (deferred view)).
    #
    # The basic view DSL methods look like this:
    #
    #   renders/views
    #     |- view
    #         |- lines
    #         |   |- line
    #         |       |- streams
    #         |       |   |- stream
    #         |       |       |- char
    #         |       |
    #         |       |- stream
    #         |           |- char
    #         |
    #         |- line
    #             |- streams
    #             |   |- stream
    #             |       |- char
    #             |
    #             |- stream
    #                 |- char
    #
    #   renders/views
    #     |- view
    #         |- lines/line
    #             |- streams/stream
    #                 |- char
    #
    # @api public
    #
    class Views

      include Vedeu::DSL
      include Vedeu::DSL::Cursors
      include Vedeu::DSL::Border
      include Vedeu::DSL::Geometry
      include Vedeu::DSL::Use
      include Vedeu::DSL::Elements

      class << self

        # {include:file:docs/dsl/by_method/renders.md}
        # @macro param_block
        # @macro raise_requires_block
        # @return [Vedeu::Views::Composition]
        def renders(&block)
          raise Vedeu::Error::RequiresBlock unless block_given?

          composition(true, &block)
        end
        alias render renders

        # {include:file:docs/dsl/by_method/views.md}
        # @macro param_block
        # @macro raise_requires_block
        # @return [Vedeu::Views::Composition]
        def views(&block)
          raise Vedeu::Error::RequiresBlock unless block_given?

          composition(false, &block)
        end

        private

        # Handles the directives you wish to send to render. Typically
        # includes `view` with associated sub-directives. The binding
        # of the block is also accessed so that we can ascertain the
        # calling client application class, this is so that if there
        # methods being called inside your views, Vedeu will redirect
        # that call to the client class instance instead. (This
        # actually occurs when Vedeu rescues from method_missing. See
        # {Vedeu::DSL}.)
        #
        # @macro param_block
        # @return [void]
        def client_binding(&block)
          eval('self', block.binding).tap do |client|
            Vedeu.log(type:    :debug,
                      message: "Client binding: #{client.class.name}")
          end
        end

        # Creates a new Vedeu::Views::Composition which may contain
        # one or more view {Vedeu::Views::View} objects.
        #
        # @param refresh [Boolean]
        # @macro param_block
        # @return [Vedeu::Views::Composition]
        def composition(refresh = false, &block)
          attrs = {
            client: client_binding(&block),
            colour: Vedeu.config.colour,
          }

          Vedeu::Views::Composition.build(attrs, &block).update_buffers(refresh)
        end

      end # Eigenclass

    end # Views

  end # DSL

  # @api public
  # @!method render
  #   @see Vedeu::DSL::Views.render
  # @api public
  # @!method renders
  #   @see Vedeu::DSL::Views.renders
  # @api public
  # @!method views
  #   @see Vedeu::DSL::Views.views
  def_delegators Vedeu::DSL::Views,
                 :renders,
                 :render,
                 :views

end # Vedeu
