# frozen_string_literal: true
# Copyright:: Copyright 2017, Noah Kantrowitz
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module ChefUtils
  # String-like object for version strings.
  #
  # @since 13.2
  # @api internal
  class VersionString < String
    # Parsed version object for the string.
    # @return [Gem::Version]
    attr_reader :parsed_version

    # Create a new VersionString from an input String.
    #
    # @param val [String] Version string to parse.
    def initialize(val)
      val ||= ""
      super(val)
      begin
        @parsed_version = ::Gem::Version.create(self)
      rescue ArgumentError
        @parsed_version = nil
      end
    end

    # @!group Compat wrappers for String

    # Compat wrapper for + to behave like a normal String.
    #
    # @param other [String]
    # @return [String]
    def +(other)
      to_s + other
    end

    # Compat wrapper for * to behave like a normal String.
    #
    # @param other [Integer]
    # @return [String]
    def *(other)
      to_s * other
    end

    # @!group Comparison operators

    # Compare a VersionString to an object. If compared to another VersionString
    # then sort like `Gem::Version`, otherwise try to treat the other object as
    # a version but fall back to normal string comparison.
    #
    # @param other [Object]
    # @return [Integer]
    def <=>(other)
      other_ver = case other
                  when VersionString
                    other.parsed_version
                  else
                    begin
                      Gem::Version.create(other.to_s)
                    rescue ArgumentError
                      # Comparing to a string that isn't a version.
                      return super
                    end
                  end
      parsed_version <=> other_ver
    end

    # Compat wrapper for == based on <=>.
    #
    # @param other [Object]
    # @return [Boolean]
    def ==(other)
      (self <=> other) == 0
    end

    # Compat wrapper for != based on <=>.
    #
    # @param other [Object]
    # @return [Boolean]
    def !=(other)
      (self <=> other) != 0
    end

    # Compat wrapper for < based on <=>.
    #
    # @param other [Object]
    # @return [Boolean]
    def <(other)
      (self <=> other) < 0
    end

    # Compat wrapper for <= based on <=>.
    #
    # @param other [Object]
    # @return [Boolean]
    def <=(other)
      (self <=> other) < 1
    end

    # Compat wrapper for > based on <=>.
    #
    # @param other [Object]
    # @return [Boolean]
    def >(other)
      (self <=> other) > 0
    end

    # Compat wrapper for >= based on <=>.
    #
    # @param other [Object]
    # @return [Boolean]
    def >=(other)
      (self <=> other) > -1
    end

    # @!group Matching operators

    # Matching operator to support checking against a requirement string.
    #
    # @param other [Regexp, String]
    # @return [Boolean]
    # @example Match against a Regexp
    #   ChefUtils::VersionString.new('1.0.0') =~ /^1/
    # @example Match against a requirement
    #   ChefUtils::VersionString.new('1.0.0') =~ '~> 1.0'
    def =~(other)
      case other
      when Regexp
        super
      else
        begin
          Gem::Requirement.create(other) =~ parsed_version
        rescue ArgumentError
          # one side of the comparison wasn't parsable
          super
        end
      end
    end

    # Back-compat API for chef-sugar.  The other APIs are preferable.
    #
    # @api private
    def satisfies?(*constraints)
      Gem::Requirement.new(*constraints).satisfied_by?(@parsed_version)
    end
  end
end
