require 'open-uri'
require 'zlib'
require 'json'
require 'forwardable'
require 'arduino/library'
require 'arduino/library/model'

module Arduino
  module Library

    # This class represents a single entry into the library-index.json file,
    # in other words — a `library.properties` file.
    class Database
      extend Forwardable
      include Utilities

      def_delegators :@db_list, *(Array.new.methods - Object.methods)

      class << self
        alias from new

        def default
          @default ||= new
        end
      end

      attr_accessor :local_file,
                    :db_list

      def initialize(file_or_url = Arduino::Library::DEFAULT_ARDUINO_LIBRARY_INDEX_URL)
        self.local_file = read_file_or_url(file_or_url)
        load_json
      end

      # Usage: find(attr1: value, attr2: /regexp/, ... )
      def find(**opts)
        limit = opts[:limit]
        opts.delete(:limit)
        match_list = []

        db_list.find do |entry|
          matches = entry_matches?(entry, opts)
          match_list << entry if matches
          break if limit && match_list.size >= limit
        end

        match_list.each { |entry| yield(entry) } if block_given?
        match_list
      end

      private

      def entry_matches?(entry, opts)
        matches = true
        opts.each_pair do |attr, check|
          value   = entry.send(attr)
          matches &= case check
                      when String
                        value == check
                      when Regexp
                        value =~ check
                      when Array
                        value = value.split(',') unless value.is_a?(Array)
                        value.eql?(check) || value.include?(check) || value.first == '*'
                      when Proc
                        check.call(value)
                      else
                        raise InvalidArgument, "Class #{check.class.name} is unsupported for value checks"
                    end
          break unless matches
        end
      end

      private

      def load_json
        hash = JSON.load(local_file.read)
        self.db_list = hash['libraries'].map { |lib| Model.from_hash(lib) }
      end
    end
  end
end