# -*- encoding: utf-8 -*-
# -*- frozen_string_literal: true -*-
# -*- warn_indent: true -*-

module UnitMeasurements
  class Measurement
    include Arithmetic
    include Comparison
    include Conversion
    include Formatter
    include Math

    CONVERSION_STRING_REGEXP = /(.+?)\s?(?:\s+(?:in|to|as)\s+(.+)|\z)/i.freeze

    attr_reader :quantity, :unit

    def initialize(quantity, unit)
      raise BaseError, "Quantity cannot be blank." if quantity.blank?
      raise BaseError, "Unit cannot be blank." if unit.blank?

      @quantity = convert_quantity(quantity)
      @unit = unit_from_unit_or_name!(unit)
    end

    def convert_to(target_unit)
      target_unit = unit_from_unit_or_name!(target_unit)

      return self if target_unit == unit

      conversion_factor = (unit.conversion_factor / target_unit.conversion_factor)

      self.class.new((quantity * conversion_factor), target_unit)
    end
    [:to, :in, :as].each { |method_alias| alias_method method_alias, :convert_to }

    def convert_to!(target_unit)
      measurement = convert_to(target_unit)
      @quantity, @unit = measurement.quantity, measurement.unit

      self
    end
    [:to!, :in!, :as!].each { |method_alias| alias_method method_alias, :convert_to! }

    def inspect(dump: false)
      dump ? super() : to_s
    end

    def to_s
      "#{quantity} #{unit}"
    end

    def quantity
      case @quantity
      when Rational
        @quantity.denominator == 1 ? @quantity.numerator : @quantity
      else
        @quantity
      end
    end

    class << self
      extend Forwardable

      def unit_group
        raise BaseError, "`Measurement` does not have a `unit_group` object. You cannot directly use `Measurement`. Instead, build a new unit group by calling `UnitMeasurements.build`."
      end

      def_delegators :unit_group, :primitive, :units, :unit_names, :unit_with_name_and_aliases,
                     :unit_names_with_aliases, :unit_for, :unit_for!, :defined?,
                     :unit_or_alias?, :[]

      def parse(input)
        input = Normalizer.normalize(input)
        source, target = input.match(CONVERSION_STRING_REGEXP)&.captures

        target ? _parse(source).convert_to(target) : _parse(source)
      end

      private

      def _parse(string)
        quantity, unit = Parser.parse(string)

        new(quantity, unit)
      end
    end

    private

    def convert_quantity(quantity)
      case quantity
      when Float
        BigDecimal(quantity, Float::DIG)
      when Integer
        Rational(quantity)
      when String
        quantity = Normalizer.normalize(quantity)
        quantity, _ = Parser.parse(quantity)

        quantity
      else
        quantity
      end
    end

    def unit_from_unit_or_name!(value)
      value.is_a?(Unit) ? value : self.class.unit_group.unit_for!(value)
    end
  end
end