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

using namespace std;
using namespace uCalcSoftware;

void ucalc_call GetCurrentDate(uCalcBase::Callback cb) {
   // In a real application, this would return the system's current date.
   // For this example, we'll use a fixed date for consistent output.
   // Let's pretend today is January 15, 2026 (a Thursday).
   cb.Return(46036); // Using Excel-style date serial number for simplicity
}

void ucalc_call AddDuration(uCalcBase::Callback cb) {
   auto startDate = cb.Arg(1);
   auto number = cb.Arg(2);
   auto unit = cb.ArgStr(3);
   auto result = startDate;

   if (unit == "day" || unit == "days") {
      result = startDate + number;
   } else if (unit == "week" || unit == "weeks") {
      result = startDate + (number * 7);
   } else if (unit == "month" || unit == "months") {
      result = startDate + (number * 30); // Approximation for example
   }
   cb.Return(result);
}

void ucalc_call GetNextDayOfWeek(uCalcBase::Callback cb) {
   auto dayName = cb.ArgStr(1);
   auto today = 46036; // Thursday, Jan 15, 2026
   auto todayDayOfWeek = 5; // 1=Sun, 2=Mon, ..., 5=Thu

   auto targetDay = 0;
   if (dayName == "Sunday") targetDay = 1;
   if (dayName == "Monday") targetDay = 2;
   if (dayName == "Tuesday") targetDay = 3;
   if (dayName == "Wednesday") targetDay = 4;
   if (dayName == "Thursday") targetDay = 5;
   if (dayName == "Friday") targetDay = 6;
   if (dayName == "Saturday") targetDay = 7;

   auto daysToAdd = (targetDay - todayDayOfWeek + 7) % 7;
   // Always get the *next* week's day
   if (daysToAdd == 0) daysToAdd = 7;

   cb.Return(today + daysToAdd);
}

void ucalc_call FormatDate(uCalcBase::Callback cb) {
   // This is a simplified formatter for the example.
   // A real implementation would be more robust.
   auto dateSerial = cb.Arg(1);
   if (dateSerial == 46036) cb.ReturnStr("2026-01-15");
   if (dateSerial == 46037) cb.ReturnStr("2026-01-16");
   if (dateSerial == 46039) cb.ReturnStr("2026-01-18");
   if (dateSerial == 46043) cb.ReturnStr("2026-01-22");
   if (dateSerial == 46050) cb.ReturnStr("2026-01-29");
   if (dateSerial == 46096) cb.ReturnStr("2026-03-16");
}

int main() {
   uCalc uc;
   // 1. Define the helper functions in the uCalc engine
   uc.DefineFunction("GetCurrentDate()", GetCurrentDate);
   uc.DefineFunction("AddDuration(date, num, unit As String)", AddDuration);
   uc.DefineFunction("GetNextDayOfWeek(dayName As String)", GetNextDayOfWeek);
   uc.DefineFunction("FormatDate(date) As String", FormatDate);

   // 2. Create the transformer and define the DSL rules
   {
      uCalc::Transformer t(uc);
      t.Owned(); // Causes t to be released when it goes out of scope
      // Set case-insensitivity for all rules
      t.DefaultRuleSet().CaseSensitive(false);

      // Define the rules
      t.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}");
      t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}");
      t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek(day))}");
      t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), Double(num), unit))}");

      // 3. Process the input strings
      cout << "Input: 'today' -> Output: " << t.Transform("today") << endl;
      cout << "Input: 'tomorrow' -> Output: " << t.Transform("tomorrow") << endl;
      cout << "Input: 'next Sunday' -> Output: " << t.Transform("next Sunday") << endl;
      cout << "Input: 'in 2 weeks' -> Output: " << t.Transform("in 2 weeks") << endl;
      cout << "Input: 'in 60 days' -> Output: " << t.Transform("in 60 days") << endl;
   }
}