module Vedeu

  module Geometries

    # Geometry allows the configuration of the position and size of an
    # interface. Within Vedeu, as the same for ANSI terminals, has the
    # origin at top-left, y = 1, x = 1. The 'y' coordinate is
    # deliberately first.
    #
    # The Geometry DSL can be used within the Interface DSL or
    # standalone. Here are example of declarations for a `geometry`
    # block:
    #
    # A standalone geometry definition:
    #
    #   Vedeu.geometry :some_interface do
    #     height 5 # Sets the height of the view to 5
    #     width 20 # Sets the width of the view to 20
    #     x 3      # Start drawing 3 spaces from left
    #     y 10     # Start drawing 10 spaces from top
    #     xn 30    # Stop drawing 30 spaces from the left
    #     yn 20    # Stop drawing 20 spaces from top
    #   end
    #
    # An interface including a geometry definition:
    #
    #   Vedeu.interface :some_interface do
    #     geometry do
    #       height 5
    #       width 20
    #       x 3
    #       y 10
    #       xn 30
    #       yn 20
    #     end
    #     # ... some code here
    #   end
    #
    # If a declaration is omitted for `height` or `width` the full
    # remaining space available in the terminal will be used. `x` and
    # `y` both default to 1.
    #
    # You can also make a geometry declaration dependent on another
    # view:
    #
    #   Vedeu.interface :other_panel do
    #     # ... some code here
    #   end
    #
    #   Vedeu.interface :main do
    #     geometry do
    #       height 10
    #       y { use(:other_panel).south }
    #     end
    #     # ... some code here
    #   end
    #
    #
    # This view will begin just below "other\_panel".
    #
    # This crude ASCII diagram represents a Geometry within Vedeu,
    # each of the labels is a value you can access or define.
    #
    #        x    north    xn           # north:  y - 1
    #      y +--------------+           # top:    y
    #        |     top      |           # west:   x - 1
    #        |              |           # left:   x
    #   west | left   right | east      # right:  xn
    #        |              |           # east:   xn + 1
    #        |    bottom    |           # bottom: yn
    #     yn +--------------+           # south:  yn + 1
    #             south
    #
    class DSL

      include Vedeu::DSL
      include Vedeu::DSL::Geometry
      include Vedeu::DSL::Use
      include Vedeu::Geometries::Validator

      # Align the interface/view horizontally or vertically within
      # the terminal.
      #
      # @param vertical [Symbol] One of :bottom, :middle, :none, :top.
      # @param horizontal [Symbol] One of :center, :centre, :left,
      #   :none, :right.
      # @param width [Fixnum] The number of characters/columns wide;
      #   this is required when the given value for horizontal is any
      #   value other than :none.
      # @param height [Fixnum] The number of lines/rows tall; this is
      #   required when the given value for vertical is any value
      #   other than :none.
      # @raise [Vedeu::Error::InvalidSyntax]
      #   - When the vertical is not given.
      #   - When the horizontal is not given.
      #   - When the horizontal is given (and not :none) and the width
      #     is not given.
      #   - When the vertical is given (and not :none) and the height
      #     is not given.
      # @return [Vedeu::Geometries::Geometry]
      def align(vertical, horizontal, width, height)
        validate_vertical_alignment!(vertical)
        validate_horizontal_alignment!(horizontal)
        validate_height!(height) unless vertical == :none
        validate_width!(width)   unless horizontal == :none

        horizontal_alignment(horizontal, width)
        vertical_alignment(vertical, height)
      end

      # @param value [Symbol] One of :center, :centre, :left, :none,
      #   :right.
      # @param width [Fixnum] The number of characters/columns.
      # @return [Vedeu::Geometries::Geometry]
      def horizontal_alignment(value, width)
        validate_horizontal_alignment!(value)
        validate_width!(width)

        model.horizontal_alignment = Vedeu::Coercers::HorizontalAlignment
          .coerce(value)
        model.width = width
        model
      end

      # @param value [Symbol] One of :bottom, :middle, :none, :top.
      # @param height [Fixnum] The number of lines/rows.
      # @return [Vedeu::Geometries::Geometry]
      def vertical_alignment(value, height)
        validate_vertical_alignment!(value)
        validate_height!(height)

        model.vertical_alignment = Vedeu::Coercers::VerticalAlignment
          .coerce(value)
        model.height = height
        model
      end

      # Vertically align the interface/view to the bottom of the
      # terminal.
      #
      #   Vedeu.geometry :some_interface do
      #     # `height` is a positive integer, e.g. 30
      #     align_bottom 30
      #
      #     # this is the same as:
      #     # vertical_alignment(:bottom, 30)
      #
      #     # or you can use: (see notes)
      #     # align(:bottom, :none, Vedeu.width, 30)
      #
      #     # ... some code
      #   end
      #
      # @note
      #   Vedeu.width in the example above will set the width to the
      #   default width of the terminal, this can be substituted for
      #   your own positive integer.
      # @param height [Fixnum] The number of lines/rows.
      # @raise [Vedeu::Error::InvalidSyntax] When the height is not
      #   given.
      # @return [Vedeu::Geometries::Geometry]
      def align_bottom(height)
        vertical_alignment(:bottom, height)
      end

      # Horizontally align the interface/view centrally.
      #
      #   Vedeu.geometry :some_interface do
      #     # `width` is a positive integer, e.g. 30
      #     align_centre 30
      #
      #     # this is the same as:
      #     # horizontal_alignment(:centre, 30)
      #
      #     # or you can use: (see notes)
      #     # align(:none, :centre, 30, Vedeu.height)
      #
      #     # ... some code
      #   end
      #
      #   # Also allows `align_center` if preferred.
      #
      # @note
      #   Vedeu.height in the example above will set the height to the
      #   default height of the terminal, this can be substituted for
      #   your own positive integer.
      # @param width [Fixnum] The number of characters/columns.
      # @raise [Vedeu::Error::InvalidSyntax] When the width is not
      #   given.
      # @return [Vedeu::Geometries::Geometry]
      def align_centre(width)
        horizontal_alignment(:centre, width)
      end
      alias_method :align_center, :align_centre

      # Horizontally align the interface/view to the left.
      #
      #   Vedeu.geometry :some_interface do
      #     # `width` is a positive integer, e.g. 30
      #     align_left 30
      #
      #     # this is the same as:
      #     # horizontal_alignment(:left, 30)
      #
      #     # or you can use: (see notes)
      #     # align(:none, :left, 30, Vedeu.height)
      #
      #     # ... some code
      #   end
      #
      # @note
      #   Vedeu.height in the example above will set the height to the
      #   default height of the terminal, this can be substituted for
      #   your own positive integer.
      # @param width [Fixnum] The number of characters/columns.
      # @raise [Vedeu::Error::InvalidSyntax] When the width is not
      #   given.
      # @return [Vedeu::Geometries::Geometry]
      def align_left(width)
        horizontal_alignment(:left, width)
      end

      # Vertically align the interface/view to the middle of the
      # terminal.
      #
      #   Vedeu.geometry :some_interface do
      #     # `height` is a positive integer, e.g. 30
      #     align_middle 30
      #
      #     # this is the same as:
      #     # vertical_alignment(:middle, 30)
      #
      #     # or you can use: (see notes)
      #     # align(:middle, :none, Vedeu.width, 30)
      #
      #     # ... some code
      #   end
      #
      # @note
      #   Vedeu.width in the example above will set the width to the
      #   default width of the terminal, this can be substituted for
      #   your own positive integer.
      # @param height [Fixnum] The number of lines/rows.
      # @raise [Vedeu::Error::InvalidSyntax] When the height is not
      #   given.
      # @return [Vedeu::Geometries::Geometry]
      def align_middle(height)
        vertical_alignment(:middle, height)
      end

      # Align the interface/view to the right.
      #
      #   Vedeu.geometry :some_interface do
      #     # `width` is a positive integer, e.g. 30
      #     align_right 30
      #
      #     # this is the same as:
      #     # horizontal_alignment(:right, 30)
      #
      #     # or you can use: (see notes)
      #     # align(:none, :right, 30, Vedeu.height)
      #
      #     # ... some code
      #   end
      #
      # @note
      #   Vedeu.height in the example above will set the height to the
      #   default height of the terminal, this can be substituted for
      #   your own positive integer.
      # @param width [Fixnum] The number of characters/columns.
      # @raise [Vedeu::Error::InvalidSyntax] When the width is not
      #   given.
      # @return [Vedeu::Geometries::Geometry]
      def align_right(width)
        horizontal_alignment(:right, width)
      end

      # Vertically align the interface/view to the top of the
      # terminal.
      #
      #   Vedeu.geometry :some_interface do
      #     # `height` is a positive integer, e.g. 30
      #     align_top 30
      #
      #     # this is the same as:
      #     # vertical_alignment(:top, 30)
      #
      #     # or you can use: (see notes)
      #     # align(:top, :none, Vedeu.width, 30)
      #
      #     # ... some code
      #   end
      #
      # @note
      #   Vedeu.width in the example above will set the width to the
      #   default width of the terminal, this can be substituted for
      #   your own positive integer.
      # @param height [Fixnum] The number of lines/rows.
      # @raise [Vedeu::Error::InvalidSyntax] When the height is not
      #   given.
      # @return [Vedeu::Geometries::Geometry]
      def align_top(height)
        vertical_alignment(:top, height)
      end

      # Returns the width in characters for the number of columns
      # specified.
      #
      #   Vedeu.geometry :main_interface do
      #     # ... some code
      #     width columns(9) # Vedeu.width # => 92 (for example)
      #                      # 92 / 12 = 7
      #                      # 7 * 9 = 63
      #                      # Therefore, width is 63 characters.
      #   end
      #
      # @param value [Fixnum]
      # @raise [Vedeu::Error::OutOfRange] When the value parameter is
      #   not between 1 and 12 inclusive.
      # @return [Fixnum|Vedeu::Error::OutOfRange]
      def columns(value)
        Vedeu::Geometries::Grid.columns(value)
      end

      # Specify the number of characters/rows/lines tall the interface
      # will be. This value will be ignored when `y` and `yn` are set.
      #
      #   Vedeu.geometry :some_interface do
      #     height 8
      #     # ... some code
      #   end
      #
      # @param value [Fixnum]
      # @return [Fixnum]
      def height(value)
        model.height = proc { value }
      end
      alias_method :height=, :height

      # Returns the height in characters for the number of rows
      # specified.
      #
      #   Vedeu.geometry :main_interface do
      #     # ... some code
      #     height rows(3)  # Vedeu.height # => 38 (for example)
      #                     # 38 / 12 = 3
      #                     # 3 * 3 = 9
      #                     # Therefore, height is 9 characters.
      #   end
      #
      # @param value [Fixnum]
      # @raise [Vedeu::Error::OutOfRange] When the value parameter is
      #   not between 1 and 12 inclusive.
      # @return [Fixnum]
      def rows(value)
        Vedeu::Geometries::Grid.rows(value)
      end

      # Specify the number of characters/columns wide the interface
      # will be. This value will be ignored when `x` and `xn` are set.
      #
      #   Vedeu.geometry :some_interface do
      #     width 25
      #     # ... some code
      #   end
      #
      # @param value [Fixnum] The number of characters/columns.
      # @return [Fixnum]
      def width(value)
        model.width = proc { value }
      end
      alias_method :width=, :width

      # Specify the starting x position (column) of the interface.
      #
      #   Vedeu.geometry :some_interface do
      #     x 7 # start on column 7.
      #
      #     # start on column 8, if :other_interface changes position
      #     # then :some_interface will too.
      #     x { use(:other_interface).east }
      #     # ... some code
      #   end
      #
      # @param value [Fixnum]
      # @param block [Proc]
      # @return [Fixnum]
      def x(value = 1, &block)
        return model.x = block if block_given?

        model.x = value
      end
      alias_method :x=, :x

      # Specify the ending x position (column) of the interface.
      # This value will override `width`.
      #
      #   Vedeu.geometry :some_interface do
      #     xn 37 # end at column 37.
      #
      #     # when :other_interface changes position,
      #     # :some_interface will too.
      #     xn  { use(:other_interface).right }
      #     # ... some code
      #   end
      #
      # @param value [Fixnum]
      # @param block [Proc]
      # @return [Fixnum]
      def xn(value = 1, &block)
        return model.xn = block if block_given?

        model.xn = value
      end
      alias_method :xn=, :xn

      # Specify the starting y position (row/line) of the interface.
      #
      #   Vedeu.geometry :some_interface do
      #     y  4 # start at row 4
      #
      #     # start on row/line 3, when :other_interface changes
      #     # position, :some_interface will too.
      #     y  { use(:other_interface).north }
      #     # ... some code
      #   end
      #
      # @param value [Fixnum]
      # @param block [Proc]
      # @return [Fixnum]
      def y(value = 1, &block)
        return model.y = block if block_given?

        model.y = value
      end
      alias_method :y=, :y

      # Specify the ending y position (row/line) of the interface.
      # This value will override `height`.
      #
      #   Vedeu.geometry :some_interface do
      #     yn 24 # end at row 24.
      #
      #     # if :other_interface changes position, :some_interface
      #     # will too.
      #     yn { use(:other_interface).bottom }
      #     # ... some code
      #   end
      #
      # @param value [Fixnum]
      # @param block [Proc]
      # @return [Fixnum]
      def yn(value = 1, &block)
        return model.yn = block if block_given?

        model.yn = value
      end
      alias_method :yn=, :yn

    end # DSL

  end # Geometries

  # @!method geometry
  #   @see Vedeu::Geometries::DSL.geometry
  def_delegators Vedeu::Geometries::DSL,
                 :geometry

end # Vedeu