# 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 Bit String
    #
    # A BIT STRING is a primitive ASN.1 type, able to embbed data whose length is not multiple of a byte.
    #
    # To specify a non byte multiple length, +:bit_length+ option must be used.
    #
    # @example Simple bit string
    #   # A 4-bit length bit string, with value 1000b
    #   bs = RASN1::Types::BitString.new(value: "\x80", bit_length: 4)
    #   # As DER/BER encode data on bytes, such encodings specify unused part in binary string
    #   bs.to_der   #=> "\x03\x02\x04\x80"
    # @author Sylvain Daubert
    # @author LemonTree55
    class BitString < Primitive
      # BitString id value
      ID = 3

      # @return [Integer]
      attr_writer :bit_length

      # @!macro type_initialize
      # @option options [Object] :bit_length default bit_length value. Should be
      #   present if +:default+ is set
      def initialize(options={})
        super
        if @default
          raise ASN1Error, "#{@name}: default bit length is not defined" if @options[:bit_length].nil?

          @default_bit_length = @options[:bit_length]
        end
        @bit_length = @options[:bit_length]
      end

      # Get bit length
      def bit_length
        if value?
          @bit_length
        else
          @default_bit_length
        end
      end

      # @param level [Integer]
      # @return [String]
      def inspect(level=0)
        str = common_inspect(level)
        str << " #{value.inspect} (bit length: #{bit_length})"
      end

      # Same as {Base#can_build?} but also check bit_length
      # @see Base#can_build?
      # @return [Boolean]
      def can_build?
        super || (!@default.nil? && (@bit_length != @default_bit_length))
      end

      # Make value from DER/BER string. Also set {#bit_length}.
      # @param der [String]
      # @param ber [::Boolean]
      # @return [void]
      # @see Types::Base#der_to_value
      def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
        unused = der.unpack1('C').to_i
        value = der[1..].to_s
        @bit_length = value.length * 8 - unused
        @value = value
      end

      private

      # @author Sylvain Daubert
      # @author adfoster-r7
      # @return [String]
      def value_to_der
        raise ASN1Error, "#{@name}: bit length is not set" if bit_length.nil?

        value = generate_value_with_correct_length

        unused = value.length * 8 - @bit_length.to_i
        der = [unused, value].pack('CA*')

        if unused.positive?
          last_byte = value[-1].to_s.unpack1('C').to_i
          last_byte &= (0xff >> unused) << unused
          der[-1] = [last_byte].pack('C')
        end

        @value = value

        der
      end

      # @return [String]
      def generate_value_with_correct_length
        value = (@value || '').dup.force_encoding('BINARY')
        value << "\x00".b while value.length * 8 < @bit_length.to_i
        return value unless value.length * 8 > @bit_length.to_i

        max_len = @bit_length.to_i / 8 + ((@bit_length.to_i % 8).positive? ? 1 : 0)
        value[0, max_len].to_s
      end

      # @return [BitString]
      def explicit_type
        self.class.new(name: name, value: @value, bit_length: @bit_length)
      end
    end
  end
end
