# frozen_string_literal: true

# This file is part of RASN1
# See https://codeberg.org/lemontree55/rasn1 for more informations
# Copyright (C) 2016-2024 Sylvain Daubert <sylvain.daubert@laposte.net>
# Copyright (C) 2024 LemonTree55 <lenontree@proton.me>
# This program is published under MIT license.

module RASN1
  module Types
    # A ASN.1 CHOICE is a choice between different types.
    #
    # @example Create a CHOICE
    #   choice = Choice.new
    #   choice.value = [Integer.new(implicit: 0, class: :context),
    #                   Integer.new(implicit: 1, class: :context),
    #                   OctetString.new(implicit: 2, class: :context)]
    #   # Choose an element
    #   choice.chosen = 0   # choose int1
    #   # Set chosen value
    #   choise.value[choice.chosen].value = 1
    #   # Also set chosen value
    #   choise.set_chosen_value(1)
    #   # Got chosen value
    #   choise.value[choice.chosen].value # => 1
    #   choice.chosen_value               # => 1
    #
    # @example Encode a CHOICE
    #   choice = Choice.new
    #   choice.value = [Integer.new(implicit: 0, class: :context, value: 1),
    #                   Integer.new(implicit: 1, class: :context),
    #                   OctetString.new(implicit: 2, class: :context, value: "a")]
    #   # to_der only encodes the chosen value
    #   choice.chosen = 0   # choose int1
    #   choice.to_der   # => "\x80\x01\x01"
    #   # another choice, another encoding string
    #   choice.chosen_value = 2
    #   choice.to_der   # => "\x82\x01a"
    #
    # @example Parse a CHOICE
    #   choice = Choice.new
    #   choice.value = [Integer.new(implicit: 0, class: :context, value: 1),
    #                   Integer.new(implicit: 1, class: :context),
    #                   OctetString.new(implicit: 2, class: :context, value: "a")]
    #   # Parse an octet string
    #   choice.parse!("\x82\x03abc")
    #   choice.chosen        # => 2
    #   choice.chosen_value  # => "abc"
    #   # Parse an unexpected value
    #   choice.parse!("\x04\x03abc") # => RASN1::ASN1Error
    # @author Sylvain Daubert
    class Choice < Base
      # Chosen type
      # @return [Integer] index of type in choice value
      attr_accessor :chosen

      # Set chosen value.
      # @note {#chosen} MUST be set before calling this method
      # @param value [Object]
      # @return [Object] value
      # @raise [ChoiceError] {#chosen} not set
      def set_chosen_value(value) # rubocop:disable Naming/AccessorMethodName
        check_chosen
        @value[@chosen].value = value
      end

      # Get chosen value
      # @note {#chosen} MUST be set before calling this method
      # @return [Object] value
      # @raise [ChoiceError] {#chosen} not set
      def chosen_value
        check_chosen
        case @value[@chosen]
        when Base
          @value[@chosen].value
        when Model
          @value[@chosen]
        when Wrapper
          @value[@chosen].element
        end
      end

      # Encode Choice into DER encoding
      # @return [String] DER-formated string
      # @raise [ChoiceError] {#chosen} not set
      # @note {#chosen} MUST be set before calling this method
      def to_der
        check_chosen
        @value[@chosen].to_der
      end

      # Parse a DER string. This method updates object by setting {#chosen} and
      # chosen value.
      # @param der [String] DER string
      # @param ber [Boolean] if +true+, accept BER encoding
      # @return [Integer] total number of parsed bytes
      # @raise [ASN1Error] error on parsing
      def parse!(der, ber: false)
        len, data = do_parse(der, ber: ber)
        return 0 if len.zero?

        element = @value[@chosen]
        if element.explicit?
          element.do_parse_explicit(data)
        else
          element.der_to_value(data, ber: ber)
        end
        len
      end

      # @private
      # @!macro do_parse
      # @see Types::Base#do_parse
      # @since 0.15.0 Specific +#do_parse+ to handle recursivity
      def do_parse(der, ber: false)
        @value.each_with_index do |element, i|
          @chosen = i
          return element.do_parse(der, ber: ber)
        rescue ASN1Error
          @chosen = nil
          next
        end

        @no_value = true
        @value = void_value
        raise ASN1Error, "CHOICE #{@name}: no type matching #{der.inspect}" unless optional?

        [0, ''.b]
      end

      # Make choice value from DER/BER string.
      # @param der [String]
      # @param ber [::Boolean]
      # @return [void]
      # @since 0.15.0 Specific +#der_to_value+ to handle recursivity
      def der_to_value(der, ber: false)
        @value.each_with_index do |element, i|
          @chosen = i
          element.parse!(der, ber: ber)
          break
        rescue ASN1Error
          @chosen = nil
          next
        end
      end

      # @param level [::Integer]
      # @return [String]
      def inspect(level=0)
        str = common_inspect(level)
        str << if defined?(@chosen) && value?
                 "\n#{@value[@chosen].inspect(level + 1)}"
               else
                 ' not chosen!'
               end
      end

      # @private Tracer private API
      # @return [String]
      def trace
        msg_type(no_id: true)
      end

      # Return empty array
      # @return [Array]
      # @since 0.15.0
      def void_value
        []
      end

      private

      # @return [void]
      # @raise [ChoiceError] {#chosen} is not set
      def check_chosen
        raise ChoiceError.new(self) if !defined?(@chosen) || @chosen.nil?
      end
    end
  end
end
