module GemBench
  # Each gem either needs to be required at boot time or not.
  # Player helps determine which gems can use `require: false` in the Gemfile to cut down load times.
  class Player
    # MAJOR.MINOR split on point length == 2
    # MAJOR.MINOR.PATCH split on point length == 3
    # Semver 2.0 Standard is to accept minor and patch updates
    SEMVER_SPLIT_ON_POINT_LENGTH = 2
    attr_accessor :name, :version, :state, :stats
    attr_reader :file_path_glob, :gemfile_regex, :checked, :exclude_file_pattern

    def initialize(options = {})
      @name = options[:name]
      @version = options[:version]
      @exclude_file_pattern = options[:exclude_file_pattern]
      @state = nil
      @stats = []
      @file_path_glob = GemBench::PATH_GLOB.call(@name)
      # Used to find the line of the Gemfile which creates the primary dependency on this gem
      @gemfile_regex = GemBench::DEPENDENCY_REGEX_PROC.call(@name)
      @checked = false
    end

    def set_starter(file_path, line_match: nil)
      return false if file_path =~ exclude_file_pattern

      # Some gems may have zero files to check, as they may be using gem as a
      #   delivery system for shell scripts!  As such we need to check which
      #   gems got checked, and which had nothing to check
      @checked = true
      line_match ||= GemBench::RAILTIE_REGEX
      scan = !GemBench::DO_NOT_SCAN.include?(name) && check_line(file_path, line_match)
      stats << [file_path, scan] if scan
      self.state = if !!scan
        GemBench::PLAYER_STATES[:starter]
      else
        GemBench::PLAYER_STATES[:bench]
      end
    end

    def check_line(file_path, line_match)
      File.read(file_path).encode(
        "utf-8",
        invalid: :replace,
        undef: :replace,
        replace: "_",
      ) =~ line_match
    rescue ArgumentError => e
      if e.message =~ /invalid byte sequence/
        puts "[GemBench] checking #{file_path} failed due to unparseable file content"
        false # Assume the likelihood of files with encoding issues that also contain railtie to be low, so: false.
      else
        puts "[GemBench] checking #{file_path} failed. Please report a bug to https://github.com/pboling/gembench/issues"
        raise e
      end
    end

    def starter?
      state == GemBench::PLAYER_STATES[:starter]
    end

    def to_s(format = :name)
      case format
      when :name
        name
      when :v
        "#{name} v#{version}"
      when :semver
        "gem '#{name}', '~> #{semver}'"
      when :locked
        "gem '#{name}', '#{version}'"
      when :legacy # when depending on legacy gems, you specifically want to not upgrade, except patches.
        "gem '#{name}', '~> #{version}'"
      when :upgrade # when upgrading, and testing gem compatibility you want to try anything newer
        "gem '#{name}', '>= #{version}'"
      else
        raise ArgumentError, "Unknown format for #{self.class.name}#to_s"
      end
    end

    def inspect
      to_s(:name)
    end

    def semver
      ver = version
      ver = ver[0..(ver.rindex(".") - 1)] until ver.split(".").length <= SEMVER_SPLIT_ON_POINT_LENGTH
      ver
    end

    def how
      case state
      when GemBench::PLAYER_STATES[:starter]
        to_s(:semver)
      when GemBench::PLAYER_STATES[:bench]
        "#{to_s(:semver)}, require: false"
      else
        if checked
          "#{self} is feeling very lost right now."
        else
          "#{self} had no files to evaluate."
        end
      end
    end

    def suggest(num)
      "\t[SUGGESTION] #{num}) #{how}"
    end

    def info(num)
      "\t[INFO] #{num}) #{how}"
    end

    def careful(num)
      "\t[BE CAREFUL] #{num}) #{how}"
    end
  end
end
