# frozen_string_literal: true

module Recog
  # A collection of {Fingerprint fingerprints} for matching against a particular
  # kind of fingerprintable data, e.g. an HTTP `Server` header
  class DB
    require 'nokogiri'
    require 'recog/fingerprint'

    # @return [String]
    attr_reader :path

    # @return [Array<Fingerprint>] {Fingerprint} objects that can be matched
    #   against strings that make sense for the {#match_key}
    attr_reader :fingerprints

    # @return [String] Taken from the `fingerprints/matches` attribute, or
    #   defaults to the basename of {#path} without the `.xml` extension.
    attr_reader :match_key

    # @return [String] Taken from the `fingerprints/protocol` attribute, or
    #   defaults to an empty string
    attr_reader :protocol

    # @return [String] Taken from the `fingerprints/database_type` attribute
    #   defaults to an empty string
    attr_reader :database_type

    # @return [Float] Taken from the `fingerprints/preference` attribute,
    #   defaults to 0.10.  Used when ordering databases, highest numbers
    #   are given priority and are processed first.
    attr_reader :preference

    # Default Fingerprint database preference when it isn't specified in file
    # Do not use a value below 0.10 so as to allow users to specify lower
    # values in their own custom XML that will always run last.
    DEFAULT_FP_PREFERENCE = 0.10

    # @param path [String]
    def initialize(path)
      @match_key = nil
      @protocol = ''
      @database_type = ''
      @preference = DEFAULT_FP_PREFERENCE.to_f
      @path = path
      @fingerprints = []

      parse_fingerprints
    end

    # @return [void]
    def parse_fingerprints
      xml = nil

      File.open(path, 'rb') do |fd|
        xml = Nokogiri::XML(fd.read(fd.stat.size))
      end

      raise "#{path} is invalid XML: #{xml.errors.join(',')}" unless xml.errors.empty?

      xml.xpath('/fingerprints').each do |fbase|
        @match_key = fbase['matches'].to_s if fbase['matches']
        @protocol = fbase['protocol'].to_s if fbase['protocol']
        @database_type = fbase['database_type'].to_s if fbase['database_type']
        @preference = fbase['preference'].to_f if fbase['preference']
      end

      filepath = path.sub(/\.xml$/, '')
      @match_key ||= File.basename(path).sub(/\.xml$/, '')

      xml.xpath('/fingerprints/fingerprint').each do |fprint|
        @fingerprints << Fingerprint.new(fprint, @match_key, @protocol, filepath)
      end

      xml = nil
    end
  end
end
