module UnitConversion

  class NumberFormatter

    def self.for(parsed_value, parsed_unit)
      case
        when parsed_unit && parsed_unit.metric?
          DecimalFormatter
        when parsed_value.rational?
          RationalFormatter
        else
          DecimalFormatter
      end.new

    end

    def initialize
    end

    # Converts a Numeric into a cooking-friendly Rational
    def rationalize(value)

      # NOTE: this algorithm seems stupid.

      if value.floor == value
        return value
      end

      useful_denominators = [2, 3, 4, 8, 16]

      if value.is_a?(Rational) && useful_denominators.include?(value.denominator)
        return value
      end

      whole = value.floor
      fraction = value - whole

      approximations = useful_denominators.map do |d|
        best_n = (1...d).each.map { |n| {delta: (fraction - Rational(n, d)).abs, numerator: n} }.sort { |a, b| a[:delta] <=> b[:delta] }.first
        {denominator: d, numerator: best_n[:numerator], delta: best_n[:delta]}
      end

      # Add 0 and 1
      approximations << { denominator: 1, numerator: 0, delta: (fraction - Rational(0,1)).abs }
      approximations << { denominator: 1, numerator: 1, delta: (fraction - Rational(1,1)).abs }

      best = approximations.sort { |a, b| [a[:delta], a[:denominator]] <=> [b[:delta], b[:denominator]] }.first

      Rational((whole * best[:denominator]) + best[:numerator], best[:denominator])
    end

  end

  class RationalFormatter < NumberFormatter
    def format(value)
      rational_val = rationalize(value)
      if rational_val.denominator == 1
        rational_val.to_i.to_s
      elsif rational_val.denominator < rational_val.numerator.abs
        whole = rational_val.floor
        fraction = rational_val - whole
        "#{whole} #{fraction}"
      else
        rational_val.to_s
      end
    end
  end

  class DecimalFormatter < NumberFormatter
    def format(value)
      "%g" % ("%.3f" % value)
    end
  end

end