require 'rails_helper'

RSpec.describe UnitConversion do

  describe '.with_custom_units' do
    it 'can convert' do
      UnitConversion.with_custom_units({
        'recipe': UnitConversion.parse('4 cups')
      }) do
        vu = UnitConversion.parse('2 1/2 recipes')
        expect(vu.volume?).to be_truthy
        lu = vu.convert("liters")
        expect(lu.raw_value).to be_between(2.3, 2.4)
        expect(lu.unit.to_s).to eq 'liter'
      end
    end

    it 'returns the value of the block' do
      val = UnitConversion.with_custom_units({
        'recipe': UnitConversion.parse('4 cups')
      }) do
        42
      end

      expect(val).to eq 42
    end
  end

  describe '.auto_unit' do
    it 'leaves units alone if reasonable' do
      expect(UnitConversion.auto_unit('1/2', 'tbsp')).to eq ['1/2', 'tablespoon']
      expect(UnitConversion.auto_unit('2', 'cups')).to eq ['2', 'cup']
      expect(UnitConversion.auto_unit('1', 'c')).to eq ['1', 'cup']
    end

    it 'leaves units alone if unknown' do
      expect(UnitConversion.auto_unit('1/16', 'splat')).to eq ['1/16', 'splat']
      expect(UnitConversion.auto_unit('4.5', 'dogs')).to eq ['4.5', 'dogs']
      expect(UnitConversion.auto_unit('0', '')).to eq ['0', '']
    end

    it 'converts standard volume units correctly' do
      expect(UnitConversion.auto_unit('6', 'tsp')).to eq ['2', 'tablespoon']
      expect(UnitConversion.auto_unit('24', 'tsp')).to eq ['1/2', 'cup']
      expect(UnitConversion.auto_unit('1/16', 'cup')).to eq ['1', 'tablespoon']
      expect(UnitConversion.auto_unit('1/48', 'cup')).to eq ['1', 'teaspoon']
      expect(UnitConversion.auto_unit('768', 'tsp')).to eq ['1', 'gallon']
      expect(UnitConversion.auto_unit('6', '[tsp_us]')).to eq ['2', 'tablespoon']
    end

    it 'converts standard mass units correctly' do
      expect(UnitConversion.auto_unit('32', 'oz')).to eq ['2', 'pound']
      expect(UnitConversion.auto_unit('40', 'oz')).to eq ['2 1/2', 'pound']
      expect(UnitConversion.auto_unit('3/4', 'lb')).to eq ['12', 'ounce']
      expect(UnitConversion.auto_unit('0.0625', 'lb')).to eq ['1', 'ounce']
    end
  end

  describe '.convert' do
    it 'scales decimal numbers' do
      expect(UnitConversion.convert('1', '2', 'cup', 'cup')).to eq '2'
      expect(UnitConversion.convert('1.5', '2', 'cup', 'cup')).to eq '3'
      expect(UnitConversion.convert('4', '.5', 'cup', 'cup')).to eq '2'
      expect(UnitConversion.convert('4', '1/2', 'cup', 'cup')).to eq '2'
    end

    it 'scales rational numbers' do
      expect(UnitConversion.convert('1/2', '2', 'cup', 'cup')).to eq '1'
      expect(UnitConversion.convert('1 1/2', '2', 'cup', 'cup')).to eq '3'
      expect(UnitConversion.convert('4', '.5', 'cup', 'cup')).to eq '2'
      expect(UnitConversion.convert('4', '1/2', 'cup', 'cup')).to eq '2'
      expect(UnitConversion.convert('2', '1/3', 'cup', 'cup')).to eq '2/3'
    end

    it 'converts units' do
      expect(UnitConversion.convert('1/2', '1', 'cup', 'tbsp')).to eq '8'
      expect(UnitConversion.convert('8', '1', 'tablespoon', 'cup')).to eq '1/2'
      expect(UnitConversion.convert('1', '1', 'tablespoon', 'cup')).to eq '1/16'
      expect(UnitConversion.convert('2.0', '1', 'tablespoon', 'cup')).to eq '0.125'
      expect(UnitConversion.convert('2/3', '1', 'tablespoon', 'teaspoons')).to eq '2'
      expect(UnitConversion.convert('2', '4', 'teaspoons', 'tablespoons')).to eq '2 2/3'
    end

    it 'scales odd units without conversion' do
      expect(UnitConversion.convert('1/2', '2', 'slices', 'slices')).to eq '1'
      expect(UnitConversion.convert('4', '1/8', nil, nil)).to eq '1/2'
      expect(UnitConversion.convert('4', '1/8', 'slices', nil)).to eq '1/2'
      expect(UnitConversion.convert('4', '1/8', 'slices', '')).to eq '1/2'
    end

    it 'converts and scales' do
      expect(UnitConversion.convert('1/2', '2', 'cup', 'tbsp')).to eq '16'
      expect(UnitConversion.convert('2.0', '1 1/2', 'tablespoon', 'cup')).to eq '0.188'
      expect(UnitConversion.convert('2', '1 1/2', 'tablespoon', 'cup')).to eq '3/16'
    end

    it 'converts from volume to mass' do
      expect(UnitConversion.convert('5', '1', 'cup', 'ounce', '5 oz/c')).to eq '25'
    end

    it 'converts from mass to volume' do
      expect(UnitConversion.convert('25', '1', 'ounce', 'cup', '5 oz/c')).to eq '5'
    end
  end

  describe '.parse' do
    it 'correctly parses strings to Units' do
      data = {
          '4 c' => Unitwise(4, 'cup'),
          '5.5 oz' => Unitwise(5.5, 'ounce'),
          '-4 tbsp' => Unitwise(-4, 'tablespoon'),
          '223 g/c' => Unitwise(223, 'gram/cup'),
          '1/3 c' => Unitwise("1/3".to_r, 'cup'),
          '-5/16 g' => Unitwise("-5/16".to_r, 'gram'),
          '2 1/2 c' => Unitwise("5/2".to_r, 'cup'),
          '1.03 gram/ml' => Unitwise("1.03", 'g/ml')
      }

      data.each do |input, output|
        expect(UnitConversion.parse(input).unitwise).to eq output
      end
    end

    it 'raises UnknownUnitError with bad units' do
      data = [
          '5 cats',
          '33 gulps/thinking',
          '9.2 meeters/s2',
          '5/8 floor'
      ]

      data.each do |input|
        expect { UnitConversion.parse(input).unitwise }.to raise_error(UnitConversion::UnknownUnitError), "'#{input}' didn't raise"
      end
    end

    it 'raises UnparseableUnitError on malformed string' do
      data = [
          '5.5/2 cups',
          '2/3.0 cups',
          'ounce',
          '-.4 cups',
          'gallons 6',
          '5,5 tsp'
      ]

      data.each do |input|
        expect { UnitConversion.parse(input) }.to raise_error(UnitConversion::UnparseableUnitError), "'#{input}' didn't raise"
      end
    end
  end



end
