# frozen_string_literal: true

require 'license_finder/package_utils/licensing'
require 'license_finder/package_utils/license_files'
require 'license_finder/package_utils/notice_files'

module LicenseFinder
  # Super-class that adapts data from different package management
  # systems (gems, npm, pip, etc.) to a common interface.
  #
  # Guidance on adding a new system
  #
  # - subclass Package, and initialize based on the data you receive from the
  #   package manager
  # - if the package specs will report license names, pass :spec_licenses in the
  #   constructor options
  # - if the package's files can be searched for licenses pass :install_path in
  #   the constructor options
  # - otherwise, override #licenses_from_spec or #license_files
  class Package
    attr_reader :logger, :name, :version, :authors, :summary, :description, :children, :parents, :groups, :manual_approval, :license_names_from_spec, :install_path

    def self.license_names_from_standard_spec(spec)
      licenses = spec['licenses'] || [spec['license']].compact
      licenses = [licenses] unless licenses.is_a?(Array)
      licenses = licenses.flatten
      licenses.map do |license|
        if license.is_a? Hash
          license['type']
        else
          license
        end
      end
    end

    def initialize(name, version = nil, options = {})
      @logger = options[:logger] || Core.default_logger

      ## DESCRIPTION
      @name = name
      @version = version || ''
      @authors = options[:authors] || ''
      @summary = options[:summary] || ''
      @description = options[:description] || ''
      @homepage = options[:homepage] || ''
      @package_url = options[:package_url].to_s
      @children = options[:children] || []
      @parents = Set.new # will be figured out later by package manager
      @groups = options[:groups] || []

      ## APPROVAL
      @permitted = false
      @restricted = false
      @manual_approval = nil

      ## LICENSING
      @license_names_from_spec = options[:spec_licenses] || []
      @install_path = options[:install_path]
      @missing = options[:missing] || false
      @decided_licenses = Set.new
    end

    ## DESCRIPTION

    attr_accessor :homepage, :package_url

    ## APPROVAL

    def approved_manually!(approval)
      @manual_approval = approval
    end

    def approved_manually?
      !@manual_approval.nil?
    end

    def approved?
      # Question: is `!restricted?` redundant?
      # DecisionApplier does not call `permitted!` or `approved_manually!`
      # if a Package has been restricted.
      (approved_manually? || permitted?) && !restricted?
    end

    def permitted!
      @permitted = true
    end

    def permitted?
      @permitted
    end

    def restricted!
      @restricted = true
    end

    def restricted?
      @restricted
    end

    ## EQUALITY

    def <=>(other)
      eq_name = name <=> other.name
      return eq_name unless eq_name.zero?

      version <=> other.version
    end

    def eql?(other)
      name == other.name && version == other.version
    end

    def hash
      [name, version].hash
    end

    ## LICENSING # stubbed in tests, otherwise private # checked in tests, otherwise private

    def licenses
      @licenses ||= activations.map(&:license).sort_by(&:name).to_set
    end

    def activations
      licensing.activations.tap do |activations|
        activations.each { |activation| log_activation activation }
      end
    end

    def licensing
      Licensing.new(self, @decided_licenses, licenses_from_spec, license_files)
    end

    def decide_on_license(license)
      @decided_licenses << license
    end

    def licenses_from_spec
      license_names_from_spec
        .map { |name| License.find_by_name(name) }
        .to_set
    end

    def license_files
      LicenseFiles.find(install_path, logger: logger)
    end

    def notice_files
      NoticeFiles.find(install_path, logger: logger)
    end

    def package_manager
      'unknown'
    end

    def missing?
      @missing
    end

    def log_activation(activation)
      preamble = format('package %s:', activation.package.name)
      if activation.sources.empty?
        logger.debug activation.package.class, format('%s no licenses found', preamble)
      else
        activation.sources.each do |source|
          logger.debug activation.package.class, format("%s found license '%s' %s", preamble, activation.license.name, source)
        end
      end
    end
  end
end

require 'license_finder/packages/manual_package'
require 'license_finder/packages/bower_package'
require 'license_finder/packages/go_package'
require 'license_finder/packages/bundler_package'
require 'license_finder/packages/pip_package'
require 'license_finder/packages/npm_package'
require 'license_finder/packages/maven_package'
require 'license_finder/packages/gradle_package'
require 'license_finder/packages/cocoa_pods_package'
require 'license_finder/packages/carthage_package'
require 'license_finder/packages/spm_package'
require 'license_finder/packages/rebar_package'
require 'license_finder/packages/erlangmk_package'
require 'license_finder/packages/mix_package'
require 'license_finder/packages/merged_package'
require 'license_finder/packages/nuget_package'
require 'license_finder/packages/conan_package'
require 'license_finder/packages/yarn_package'
require 'license_finder/packages/pnpm_package'
require 'license_finder/packages/sbt_package'
require 'license_finder/packages/cargo_package'
require 'license_finder/packages/composer_package'
require 'license_finder/packages/conda_package'
require 'license_finder/packages/pubspec_package'
