require 'rails_helper'

RSpec.describe UnitConversion::Conversion do

  def get_value_unit(value, units)
    UnitConversion.parse(value, units)
  end

  def get_number(number)
    UnitConversion::ParsedNumber.new(number)
  end

  def get_unit(unit)
    UnitConversion::ParsedUnit.new(unit)
  end

  describe UnitConversion::ScaleConversion do
    it 'scales' do
      expect(UnitConversion::ScaleConversion.new(get_number(2)).convert(get_value_unit(4, 'cups')).raw_value).to eq 8
      expect(UnitConversion::ScaleConversion.new(get_number('1/2')).convert(get_value_unit(4, 'cups')).raw_value).to eq 2
    end

    it 'leaves the unit alone' do
      expect(UnitConversion::ScaleConversion.new(get_number(2)).convert(get_value_unit(4, 'cups')).unit.original_unit).to eq 'cups'
      expect(UnitConversion::ScaleConversion.new(get_number(2)).convert(get_value_unit(4, 'cats')).unit.original_unit).to eq 'cats'
    end
  end

  describe UnitConversion::MetricUnitConversion do
    it 'converts standard units' do
      expect(UnitConversion::MetricUnitConversion.new.convert(get_value_unit(1, 'cup')).unit.to_s).to eq 'milliliter'
      expect(UnitConversion::MetricUnitConversion.new.convert(get_value_unit(1, 'cup')).pretty_value).to eq '236.588'

      expect(UnitConversion::MetricUnitConversion.new.convert(get_value_unit(1, 'gallon')).unit.to_s).to eq 'liter'
      expect(UnitConversion::MetricUnitConversion.new.convert(get_value_unit(1, 'gallon')).pretty_value).to eq '3.785'
    end
  end

  describe UnitConversion::StandardUnitConversion do
    it 'converts standard units' do
      expect(UnitConversion::StandardUnitConversion.new.convert(get_value_unit(200, 'ml')).unit.to_s).to eq 'cup'
      expect(UnitConversion::StandardUnitConversion.new.convert(get_value_unit(200, 'ml')).pretty_value).to eq '7/8'

      expect(UnitConversion::StandardUnitConversion.new.convert(get_value_unit(5, 'liter')).unit.to_s).to eq 'gallon'
      expect(UnitConversion::StandardUnitConversion.new.convert(get_value_unit(5, 'liter')).pretty_value).to eq '1 5/16'
    end
  end

  describe UnitConversion::ConvertConversion do
    it 'converts standard units' do
      expect(UnitConversion::ConvertConversion.new(get_unit('tbsp')).convert(get_value_unit(1, 'cups')).raw_value).to eq 16
      expect(UnitConversion::ConvertConversion.new(get_unit('cups')).convert(get_value_unit(8, 'tbsp')).raw_value).to eq 0.5
    end

    it 'takes on the new unit' do
      expect(UnitConversion::ConvertConversion.new(get_unit('tbsp')).convert(get_value_unit(1, 'cups')).unit.original_unit).to eq 'tbsp'
      expect(UnitConversion::ConvertConversion.new(get_unit('gallons')).convert(get_value_unit(1, 'cups')).unit.original_unit).to eq 'gallons'
    end

    it 'converts from mass to volume' do
      expect(UnitConversion::ConvertConversion.new(get_unit('oz'), get_value_unit(5, 'oz/c')).convert(get_value_unit(2, 'cups')).raw_value).to be_within(0.01).of(10)
    end

    it 'converts from volume to mass' do
      expect(UnitConversion::ConvertConversion.new(get_unit('c'), get_value_unit(5, 'oz/c')).convert(get_value_unit(10, 'oz')).raw_value).to be_within(0.01).of(2)
    end

    it 'raises an error when attempting a mass/volume conversion without density' do
      expect { UnitConversion::ConvertConversion.new(get_unit('c')).convert(get_value_unit(10, 'oz')) }.to raise_error(UnitConversion::MissingDensityError)
      expect { UnitConversion::ConvertConversion.new(get_unit('oz')).convert(get_value_unit(10, 'c')) }.to raise_error(UnitConversion::MissingDensityError)
    end

    it 'raises an error when density param is not a density' do
      expect { UnitConversion::ConvertConversion.new(get_unit('oz'), get_value_unit(2, 'g/oz')) }.to raise_error(UnitConversion::UnknownUnitError)
      expect { UnitConversion::ConvertConversion.new(get_unit('oz'), get_value_unit(2, 'cats')) }.to raise_error(UnitConversion::UnknownUnitError)
      expect { UnitConversion::ConvertConversion.new(get_unit('oz'), get_value_unit(2, 'g')) }.to raise_error(UnitConversion::UnknownUnitError)
    end

    it 'preserves the formatter for standard units' do
      original = get_value_unit(10, 'tbsp')
      converted = UnitConversion::ConvertConversion.new(get_unit('c')).convert(original)
      expect(original.formatter).to be converted.formatter

      original = get_value_unit("10.0", 'tbsp')
      converted = UnitConversion::ConvertConversion.new(get_unit('c')).convert(original)
      expect(original.formatter).to be converted.formatter
    end

    it 'always sets the formatter to a DecimalFormatter for metric units' do
      original = get_value_unit(10, 'tbsp')
      expect(original.formatter).to be_a UnitConversion::RationalFormatter

      converted = UnitConversion::ConvertConversion.new(get_unit('ml')).convert(original)
      expect(converted.formatter).to be_a UnitConversion::DecimalFormatter
    end
  end

end
