# frozen_string_literal: true

require 'image_optim/bin_resolver'
require 'image_optim/option_definition'

class ImageOptim
  class Worker
    # Class methods of ImageOptim::Worker
    module ClassMethods
      def self.extended(klass)
        klass.instance_variable_set(:@klasses, [])
      end

      # List of available workers
      def klasses
        @klasses.to_enum
      end

      # Remember all classes inheriting from this one
      def inherited(base)
        super
        @klasses << base
      end

      # Underscored class name symbol
      def bin_sym
        @bin_sym ||=
          name.
          split('::').last. # get last part
          gsub(/([a-z])([A-Z])/, '\1_\2').downcase. # convert AbcDef to abc_def
          to_sym
      end

      def option_definitions
        @option_definitions ||= []
      end

      def option(name, default, type, description = nil, &proc)
        attr_reader name

        OptionDefinition.new(name, default, type, description, &proc).
          tap{ |option_definition| option_definitions << option_definition }
      end

      # Create hash with format mapped to list of workers sorted by run order
      def create_all_by_format(image_optim, &options_proc)
        by_format = {}
        create_all(image_optim, &options_proc).each do |worker|
          worker.image_formats.each do |format|
            by_format[format] ||= []
            by_format[format] << worker
          end
        end
        by_format
      end

      # Create list of workers sorted by run order
      # Workers are initialized with options provided through options_proc
      # Resolve all bins of all workers, if there are errors and
      # skip_missing_workers of image_optim is true - show warnings, otherwise
      # fail with one joint exception
      def create_all(image_optim, &options_proc)
        workers = init_all(image_optim, &options_proc)

        resolved = []
        errors = BinResolver.collect_errors(workers) do |worker|
          worker.resolve_used_bins!
          resolved << worker
        end

        unless errors.empty?
          messages = errors.map(&:to_s).uniq
          if image_optim.skip_missing_workers
            messages.each{ |message| warn message }
          else
            joint_message = ['Bin resolving errors:', *messages].join("\n")
            fail BinResolver::Error, joint_message
          end
        end

        resolved.sort_by.with_index{ |worker, i| [worker.run_order, i] }
      end

    private

      def init_all(image_optim, &options_proc)
        klasses.map do |klass|
          options = options_proc[klass]
          next if options[:disable]

          if !options.key?(:allow_lossy) && klass.method_defined?(:allow_lossy)
            options[:allow_lossy] = image_optim.allow_lossy
          end
          klass.init(image_optim, options)
        end.compact.flatten
      end
    end
  end
end
