using AIXM::Refinements

module AIXM
  module Component

    # Vertical limit defines a 3D airspace vertically. It is often noted in
    # AIP as follows:
    #
    #   upper_z
    #   (max_z)   whichever is higher
    #   -------
    #   lower_z
    #   (min_z)   whichever is lower
    #
    # ===Cheat Sheet in Pseudo Code:
    #   vertical_limit = AIXM.vertical_limit(
    #     upper_z: AIXM.z
    #     max_z: AIXM.z or nil
    #     lower_z: AIXM.z
    #     min_z: AIXM.z or nil
    #   )
    #
    # ===Shortcuts:
    # * +AIXM::GROUND+ - surface expressed as "0 ft QFE"
    # * +AIXM::UNLIMITED+ - no upper limit expressed as "FL 999"
    #
    # @see https://gitlab.com/openflightmaps/ofmx/wikis/Airspace#ase-airspace
    class VerticalLimit
      include AIXM::Association

      # @api private
      TAGS = { upper_z: :Upper, lower_z: :Lower, max_z: :Max, min_z: :Mnm }.freeze

      # @api private
      CODES = { qfe: :HEI, qnh: :ALT, qne: :STD }.freeze

      # @!method layer
      #   @return [AIXM::Component::Layer] layer to which this vertical limit applies
      belongs_to :layer

      # @return [AIXM::Z] upper limit
      attr_reader :upper_z

      # @return [AIXM::Z] lower limit
      attr_reader :lower_z

      # @return [AIXM::Z] alternative upper limit ("whichever is higher")
      attr_reader :max_z

      # @return [AIXM::Z] alternative lower limit ("whichever is lower")
      attr_reader :min_z

      def initialize(upper_z:, max_z: nil, lower_z:, min_z: nil)
        self.upper_z, self.max_z, self.lower_z, self.min_z = upper_z, max_z, lower_z, min_z
      end

      # @return [String]
      def inspect
        payload = %i(upper_z max_z lower_z min_z).map { %Q(#{_1}="#{send(_1)}") if send(_1) }.compact
        %Q(#<#{self.class} #{payload.join(' ')}>)
      end

      def upper_z=(value)
        fail(ArgumentError, "invalid upper_z") unless value.is_a? AIXM::Z
        @upper_z = value
      end

      def max_z=(value)
        fail(ArgumentError, "invalid max_z") unless value.nil? || value.is_a?(AIXM::Z)
        @max_z = value
      end

      def lower_z=(value)
        fail(ArgumentError, "invalid lower_z") unless value.is_a? AIXM::Z
        @lower_z = value
      end

      def min_z=(value)
        fail(ArgumentError, "invalid min_z") unless value.nil? || value.is_a?(AIXM::Z)
        @min_z = value
      end

      # @return [String] AIXM or OFMX markup
      def to_xml
        TAGS.keys.each_with_object(Builder::XmlMarkup.new(indent: 2)) do |limit, builder|
          if z = send(limit)
            builder.tag!(:"codeDistVer#{TAGS[limit]}", CODES[z.code].to_s)
            builder.tag!(:"valDistVer#{TAGS[limit]}", z.alt.to_s)
            builder.tag!(:"uomDistVer#{TAGS[limit]}", z.unit.upcase.to_s)
          end
        end.target!
      end
    end

  end
end
