# 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
    # ASN.1 ANY: accepts any types
    #
    # ANY is often used with an {ObjectId} to detect real type.
    #
    # On encoding, ANY is replaced by its values (which must be an RASN1 type).
    # If `any#value` is `nil` and Any object is not {#optional?}, `any` will be encoded as a {Null} object.
    #
    # @example Parsing with any model
    #   class AnyModel < RASN1::Model
    #     sequence :seq,
    #              content: [objectid(:id), any(:data)]
    #   end
    #   model = AnyModel.new
    #   model.parse!("\x30\x09\x06\x03\x2a\x03\x04\x02\x02\x01\xff")
    #   model[:id].value    #=> "1.2.3.4"
    #   model[:data].value  #=> "\x02\x02\x01\xff"
    #   # As we knwon object with id 1.2.3.4 is an integer, with then parse data as such
    #   data = RASN1::Types::Integer.new
    #   data.parse!(model[:data].value)
    #   data.value          #=> 511
    # @author Sylvain Daubert
    class Any < Base
      # @return [String] DER-formated string
      def to_der
        if value?
          case @value
          when Base, Model
            @value.to_der
          else
            @value.to_s
          end
        else
          optional? ? '' : Null.new.to_der
        end
      end

      # @return [Boolean]
      def can_build?
        value? || !optional?
      end

      # Parse a DER string. This method updates object: {#value} will be a DER
      # string.
      # @param der [String] DER string
      # @param ber [Boolean] if +true+, accept BER encoding
      # @return [Integer] total number of parsed bytes
      def parse!(der, ber: false)
        total_length, _data = do_parse(der, ber: ber)
        total_length
      end

      # @param level [::Integer]
      # @return [String]
      def inspect(level=0)
        str = common_inspect(level)
        str << if !value?
                 'NULL'
               elsif @value.is_a?(OctetString) || @value.is_a?(BitString)
                 "#{@value.type}: #{value.value.inspect}"
               elsif @value.class < Base
                 "#{@value.type}: #{value.value}"
               else
                 value.to_s.inspect
               end
      end

      # @private Tracer private API
      # @return [String]
      def trace
        return trace_any if value?

        msg_type(no_id: true) << ' NONE'
      end

      private

      # @param level [Integer]
      def common_inspect(level)
        lvl = [0, level].max
        str = '  ' * lvl
        str << "#{@name} " unless @name.nil?
        str << asn1_class.to_s.upcase << ' ' unless asn1_class == :universal
        str << "[#{id}] EXPLICIT " if explicit?
        str << "[#{id}] IMPLICIT " if implicit?
        str << '(ANY) '
      end

      # @private
      # @!macro do_parse
      # @see Types::Base#do_parse
      def do_parse(der, ber: false)
        if der.empty?
          return [0, ''] if optional?

          raise ASN1Error, 'Expected ANY but get nothing'
        end

        id_size = Types.decode_identifier_octets(der).last
        total_length, = get_data(der[id_size..], ber)
        total_length += id_size

        @no_value = false
        real_value = der[0, total_length].to_s
        @value = real_value

        [total_length, real_value]
      end

      # @return [String]
      def trace_any
        msg_type(no_id: true) << trace_data(value)
      end
    end
  end
end
