#include <iostream>
#include "uCalc.h"

using namespace std;
using namespace uCalcSoftware;

void ucalc_call ConvertUnits(uCalcBase::Callback cb) {
   auto  value = cb.Arg(1);
   auto fromUnit = cb.ArgStr(2);
   auto toUnit = cb.ArgStr(3);

   // Construct the variable names for direct and inverse factors
   auto factorName = fromUnit + "_to_" + toUnit;
   auto inverseFactorName = toUnit + "_to_" + fromUnit;

   auto uc_instance = cb.uCalc();

   // Try to find the direct conversion factor
   auto factorItem = uc_instance.ItemOf(factorName);
   if (factorItem.NotEmpty()) {
      cb.Return(value * factorItem.Value());
      return;
   }

   // If not found, try to find the inverse factor and use its reciprocal
   auto inverseFactorItem = uc_instance.ItemOf(inverseFactorName);
   if (inverseFactorItem.NotEmpty()) {
      cb.Return(value / inverseFactorItem.Value());
      return;
   }

   // If no factor is found, raise an error
   cb.Error().Raise("Conversion factor not found for " + fromUnit + " to " + toUnit);
}
int main() {
   uCalc uc;
   // Define the conversion factors as variables
   uc.DefineVariable("in_to_cm = 2.54");
   uc.DefineVariable("km_to_miles = 0.621371");

   // Define a custom function for temperature, since it's not a simple multiplication
   uc.DefineFunction("ConvertTempFToC(val) = (val - 32) * 5.0/9.0");

   // Register our generic conversion callback
   uc.DefineFunction("Convert(val, fromUnit As String, toUnit As String)", ConvertUnits);

   // Create generic rules in the expression transformer
   auto t = uc.ExpressionTransformer();
   t.FromTo("{@Number:val} {@Alpha:from} to {@Alpha:to}", "Convert({val}, '{from}', '{to}')");
   // A specific rule for Fahrenheit to Celsius since it's more complex (higher precedence because it's defined last)
   t.FromTo("{@Number:val} F to C", "ConvertTempFToC({val})");

   cout << "10 in to cm = " << uc.Eval("10 in to cm") << endl;
   cout << "100 km to miles = " << uc.Eval("100 km to miles") << endl;

   // Test the inverse conversion, which the callback handles automatically
   cout << "254 cm to in = " << uc.Eval("254 cm to in") << endl;
   cout << "98.6 F to C = " << uc.Eval("98.6 F to C") << endl;
}