--- Old Hindu solar and lunar calendar conversions.
-- Ported from "Calendrical Calculations" (4th edition)
-- by Nachum Dershowitz and Edward M. Reingold.
-- Original Lisp code (CALENDRICA 4.0) is Apache 2.0 licensed.
-- @module calendrica-old-hindu
-- @release 0.1 2026-07-19

local M = {}

local basic  = require("calendrica-basic")
local julian = require("calendrica-julian")
local astro  = require("calendrica-astro")


-- === Epoch and day count ===

local HINDU_EPOCH = julian.fixed_from_julian(
  julian.julian_date(julian.bce(3102), julian.FEBRUARY, 18)
)

-- Elapsed days (Ahargana) to date since Hindu epoch.
local function hindu_day_count(date)
  return date - HINDU_EPOCH
end


-- === Old Hindu solar constants ===

-- Length of Old Hindu solar year in days.
local ARYA_SOLAR_YEAR = 1577917500 / 4320000

-- Length of Old Hindu solar month in days.
local ARYA_SOLAR_MONTH = ARYA_SOLAR_YEAR / 12


-- === Hindu solar date constructor ===

--- Construct a Hindu solar date {year, month, day}.
-- @tparam number year  Hindu solar year.
-- @tparam number month Hindu solar month.
-- @tparam number day   Hindu solar day.
-- @treturn table {year, month, day}
function M.hindu_solar_date(year, month, day)
  return {year, month, day}
end
local hindu_solar_date = M.hindu_solar_date


-- === Old Hindu solar conversion ===

--- Old Hindu solar date {year, month, day} equivalent to fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, month, day}
function M.old_hindu_solar_from_fixed(date)
  local sun   = hindu_day_count(date) + astro.hr(6)
  local year  = basic.quotient(sun, ARYA_SOLAR_YEAR)
  local month = 1 + (basic.quotient(sun, ARYA_SOLAR_MONTH) % 12)
  local day   = 1 + math.floor(sun % ARYA_SOLAR_MONTH)
  return hindu_solar_date(year, month, day)
end

--- Fixed date corresponding to Old Hindu solar date `s_date`.
-- @tparam table s_date Hindu solar date {year, month, day}.
-- @treturn number Fixed date.
function M.fixed_from_old_hindu_solar(s_date)
  local month = basic.standard_month(s_date)
  local day   = basic.standard_day(s_date)
  local year  = basic.standard_year(s_date)
  return math.ceil(
    HINDU_EPOCH
    + year * ARYA_SOLAR_YEAR
    + (month - 1) * ARYA_SOLAR_MONTH
    + day + astro.hr(-30)
  )
end


-- === Old Hindu lunar constants ===

-- Length of Old Hindu lunar month in days.
local ARYA_LUNAR_MONTH = 1577917500 / 53433336

-- Length of Old Hindu lunar day (tithi) in days.
local ARYA_LUNAR_DAY = ARYA_LUNAR_MONTH / 30


-- === Old Hindu lunar date constructor and accessors ===

-- Old Hindu lunar date constructor.
local function old_hindu_lunar_date(year, month, leap, day)
  return {year, month, leap, day}
end

-- Old Hindu lunar date accessors.
local function old_hindu_lunar_year(date)  return date[1] end
local function old_hindu_lunar_month(date) return date[2] end
local function old_hindu_lunar_leap(date)  return date[3] end
local function old_hindu_lunar_day(date)   return date[4] end


-- === Old Hindu lunar conversion ===

-- True if l_year is an Old Hindu lunar leap year.
local function old_hindu_lunar_leap_year(l_year)
  return (l_year * ARYA_SOLAR_YEAR - ARYA_SOLAR_MONTH)
    % ARYA_LUNAR_MONTH
    >= 23902504679 / 1282400064
end

--- Old Hindu lunar date {year, month, leap, day} equivalent to fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, month, leap, day}
function M.old_hindu_lunar_from_fixed(date)
  local sun      = hindu_day_count(date) + astro.hr(6)
  local new_moon = sun - (sun % ARYA_LUNAR_MONTH)
  local leap = ARYA_SOLAR_MONTH - ARYA_LUNAR_MONTH
      >= (new_moon % ARYA_SOLAR_MONTH)
    and (new_moon % ARYA_SOLAR_MONTH) > 0
  local month = 1 + (math.ceil(new_moon / ARYA_SOLAR_MONTH) % 12)
  local day = 1 + (basic.quotient(sun, ARYA_LUNAR_DAY) % 30)
  local year = math.ceil((new_moon + ARYA_SOLAR_MONTH) / ARYA_SOLAR_YEAR) - 1
  return old_hindu_lunar_date(year, month, leap, day)
end

--- Fixed date corresponding to Old Hindu lunar date `l_date`.
-- @tparam table l_date Old Hindu lunar date {year, month, leap, day}.
-- @treturn number Fixed date.
function M.fixed_from_old_hindu_lunar(l_date)
  local year  = old_hindu_lunar_year(l_date)
  local month = old_hindu_lunar_month(l_date)
  local leap  = old_hindu_lunar_leap(l_date)
  local day   = old_hindu_lunar_day(l_date)
  local mina = (12 * year - 1) * ARYA_SOLAR_MONTH
  local lunar_new_year = ARYA_LUNAR_MONTH
    * (1 + basic.quotient(mina, ARYA_LUNAR_MONTH))
  local month_offset
  if not leap
    and math.ceil(
          (lunar_new_year - mina) / (ARYA_SOLAR_MONTH - ARYA_LUNAR_MONTH)
        ) <= month
  then
    month_offset = month
  else
    month_offset = month - 1
  end
  return math.ceil(
    HINDU_EPOCH
    + lunar_new_year
    + ARYA_LUNAR_MONTH * month_offset
    + (day - 1) * ARYA_LUNAR_DAY
    + astro.hr(-6)
  )
end


-- === Jovian cycle ===

-- Number of days in one revolution of Jupiter around the Sun.
local ARYA_JOVIAN_PERIOD = 1577917500 / 364224

-- Year of Jupiter cycle at fixed date.
local function jovian_year(date)
  return basic.amod(
    27 + basic.quotient(hindu_day_count(date),
                        ARYA_JOVIAN_PERIOD / 12),
    60
  )
end


-- === Exports ===

--- True if Old Hindu lunar year `l_year` is a leap year.
-- @function old_hindu_lunar_leap_year
-- @tparam number l_year Old Hindu lunar year.
-- @treturn boolean
M.old_hindu_lunar_leap_year = old_hindu_lunar_leap_year

--- Year in Jupiter's 60-year cycle for fixed `date`.
-- @function jovian_year
-- @tparam number date Fixed date.
-- @treturn number Year (1..60).
M.jovian_year = jovian_year

-- Exposed for use by calendrica-modern-hindu; not part of the public API.
M.HINDU_EPOCH = HINDU_EPOCH

return M
