--- Mayan and Aztec 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-mayan
-- @release 0.1 2026-07-19

local M = {}

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


-- === Mayan Long Count ===

-- Fixed date of start of Mayan calendar (Goodman-Martinez-Thompson).
local MAYAN_EPOCH = basic.fixed_from_jd(584283)

-- Mayan long count date constructor.
local function mayan_long_count_date(baktun, katun, tun, uinal, kin) -- luacheck: ignore
  return {baktun, katun, tun, uinal, kin}
end

-- Mayan long count component accessors.
local function mayan_baktun(date) return date[1] end -- luacheck: ignore
local function mayan_katun(date)  return date[2] end -- luacheck: ignore
local function mayan_tun(date)    return date[3] end -- luacheck: ignore
local function mayan_uinal(date)  return date[4] end -- luacheck: ignore
local function mayan_kin(date)    return date[5] end -- luacheck: ignore

--- Fixed date corresponding to Mayan long count `count`.
-- @tparam table count Mayan long count date {baktun,katun,tun,uinal,kin}.
-- @treturn number Fixed date.
function M.fixed_from_mayan_long_count(count)
  return MAYAN_EPOCH + basic.from_radix(count, {20, 20, 18, 20})
end

--- Mayan long count date {baktun,katun,tun,uinal,kin} of fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {baktun, katun, tun, uinal, kin}
function M.mayan_long_count_from_fixed(date)
  return basic.to_radix(date - MAYAN_EPOCH, {20, 20, 18, 20})
end


-- === Mayan Haab ===

-- Mayan haab date constructor.
local function mayan_haab_date(month, day)
  return {month, day}
end

-- Mayan haab accessors.
local function mayan_haab_month(date) return date[1] end
local function mayan_haab_day(date)   return date[2] end

-- Number of days into haab cycle of h_date.
local function mayan_haab_ordinal(h_date)
  return (mayan_haab_month(h_date) - 1) * 20
    + mayan_haab_day(h_date)
end

-- Fixed date of start of haab cycle.
local MAYAN_HAAB_EPOCH = MAYAN_EPOCH
  - mayan_haab_ordinal(mayan_haab_date(18, 8))

--- Mayan haab date {month, day} of fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {month, day}
function M.mayan_haab_from_fixed(date)
  local count = (date - MAYAN_HAAB_EPOCH) % 365
  local day   = count % 20
  local month = 1 + basic.quotient(count, 20)
  return mayan_haab_date(month, day)
end
local mayan_haab_from_fixed = M.mayan_haab_from_fixed

-- Latest haab date on or before fixed date.
local function mayan_haab_on_or_before(haab, date)
  return basic.mod3(
    mayan_haab_ordinal(haab) + MAYAN_HAAB_EPOCH,
    date, date - 365
  )
end


-- === Mayan Tzolkin ===

-- Mayan tzolkin date constructor.
local function mayan_tzolkin_date(number, name)
  return {number, name}
end

-- Mayan tzolkin accessors.
local function mayan_tzolkin_number(date) return date[1] end
local function mayan_tzolkin_name(date)   return date[2] end

-- Number of days into tzolkin cycle of t_date.
local function mayan_tzolkin_ordinal(t_date)
  local number = mayan_tzolkin_number(t_date)
  local name   = mayan_tzolkin_name(t_date)
  return (number - 1 + 39 * (number - name)) % 260
end

-- Start of tzolkin date cycle.
local MAYAN_TZOLKIN_EPOCH = MAYAN_EPOCH
  - mayan_tzolkin_ordinal(mayan_tzolkin_date(4, 20))

--- Mayan tzolkin date {number, name} of fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {number, name}
function M.mayan_tzolkin_from_fixed(date)
  local count  = date - MAYAN_TZOLKIN_EPOCH + 1
  local number = basic.amod(count, 13)
  local name   = basic.amod(count, 20)
  return mayan_tzolkin_date(number, name)
end
local mayan_tzolkin_from_fixed = M.mayan_tzolkin_from_fixed

-- Latest tzolkin date on or before fixed date.
local function mayan_tzolkin_on_or_before(tzolkin, date)
  return basic.mod3(
    mayan_tzolkin_ordinal(tzolkin) + MAYAN_TZOLKIN_EPOCH,
    date, date - 260
  )
end

-- Year bearer of year containing fixed date; bogus for uayeb.
local function mayan_year_bearer_from_fixed(date)
  local x = mayan_haab_on_or_before(mayan_haab_date(1, 0), date)
  if mayan_haab_month(mayan_haab_from_fixed(date)) == 19 then
    return basic.BOGUS
  else
    return mayan_tzolkin_name(mayan_tzolkin_from_fixed(x))
  end
end

-- Latest haab-tzolkin combination on or before date; bogus if impossible.
local function mayan_calendar_round_on_or_before(haab, tzolkin, date)
  local haab_count    = mayan_haab_ordinal(haab) + MAYAN_HAAB_EPOCH
  local tzolkin_count = mayan_tzolkin_ordinal(tzolkin) + MAYAN_TZOLKIN_EPOCH
  local diff = tzolkin_count - haab_count
  if diff % 5 == 0 then
    return basic.mod3(haab_count + 365 * diff, date, date - 18980)
  else
    return basic.BOGUS
  end
end


-- === Aztec Xihuitl ===

-- Known date of Aztec cycles (Caso's correlation).
local AZTEC_CORRELATION = julian.fixed_from_julian(
  julian.julian_date(1521, julian.AUGUST, 13)
)

-- Aztec xihuitl date constructor.
local function aztec_xihuitl_date(month, day)
  return {month, day}
end

-- Aztec xihuitl accessors.
local function aztec_xihuitl_month(date) return date[1] end
local function aztec_xihuitl_day(date)   return date[2] end

-- Number of elapsed days into xihuitl cycle of x_date.
local function aztec_xihuitl_ordinal(x_date)
  return (aztec_xihuitl_month(x_date) - 1) * 20
    + (aztec_xihuitl_day(x_date) - 1)
end

-- Start of a xihuitl cycle.
local AZTEC_XIHUITL_CORRELATION = AZTEC_CORRELATION
  - aztec_xihuitl_ordinal(aztec_xihuitl_date(11, 2))

--- Aztec xihuitl date {month, day} of fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {month, day}
function M.aztec_xihuitl_from_fixed(date)
  local count = (date - AZTEC_XIHUITL_CORRELATION) % 365
  local day   = 1 + (count % 20)
  local month = 1 + basic.quotient(count, 20)
  return aztec_xihuitl_date(month, day)
end
local aztec_xihuitl_from_fixed = M.aztec_xihuitl_from_fixed

-- Latest xihuitl date on or before fixed date.
local function aztec_xihuitl_on_or_before(xihuitl, date)
  return basic.mod3(
    AZTEC_XIHUITL_CORRELATION + aztec_xihuitl_ordinal(xihuitl),
    date, date - 365
  )
end


-- === Aztec Tonalpohualli ===

-- Aztec tonalpohualli date constructor.
local function aztec_tonalpohualli_date(number, name)
  return {number, name}
end

-- Aztec tonalpohualli accessors.
local function aztec_tonalpohualli_number(date) return date[1] end
local function aztec_tonalpohualli_name(date)   return date[2] end

-- Number of days into tonalpohualli cycle of t_date.
local function aztec_tonalpohualli_ordinal(t_date)
  local number = aztec_tonalpohualli_number(t_date)
  local name   = aztec_tonalpohualli_name(t_date)
  return (number - 1 + 39 * (number - name)) % 260
end

-- Start of a tonalpohualli date cycle.
local AZTEC_TONALPOHUALLI_CORRELATION = AZTEC_CORRELATION
  - aztec_tonalpohualli_ordinal(aztec_tonalpohualli_date(1, 5))

--- Aztec tonalpohualli date {number, name} of fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {number, name}
function M.aztec_tonalpohualli_from_fixed(date)
  local count  = date - AZTEC_TONALPOHUALLI_CORRELATION + 1
  local number = basic.amod(count, 13)
  local name   = basic.amod(count, 20)
  return aztec_tonalpohualli_date(number, name)
end
local aztec_tonalpohualli_from_fixed = M.aztec_tonalpohualli_from_fixed

-- Latest tonalpohualli date on or before fixed date.
local function aztec_tonalpohualli_on_or_before(tonalpohualli, date)
  return basic.mod3(
    AZTEC_TONALPOHUALLI_CORRELATION
      + aztec_tonalpohualli_ordinal(tonalpohualli),
    date, date - 260
  )
end


-- === Aztec Xiuhmolpilli ===

-- Aztec xiuhmolpilli designation constructor.
local function aztec_xiuhmolpilli_designation(number, name) -- luacheck: ignore
  return {number, name}
end

-- Aztec xiuhmolpilli accessors.
local function aztec_xiuhmolpilli_number(date) return date[1] end -- luacheck: ignore
local function aztec_xiuhmolpilli_name(date)   return date[2] end -- luacheck: ignore

-- Latest xihuitl-tonalpohualli combination on or before date; bogus if impossible.
local function aztec_xihuitl_tonalpohualli_on_or_before(xihuitl, tonalpohualli, date) -- luacheck: ignore
  local xihuitl_count = aztec_xihuitl_ordinal(xihuitl)
    + AZTEC_XIHUITL_CORRELATION
  local tonalpohualli_count = aztec_tonalpohualli_ordinal(tonalpohualli)
    + AZTEC_TONALPOHUALLI_CORRELATION
  local diff = tonalpohualli_count - xihuitl_count
  if diff % 5 == 0 then
    return basic.mod3(xihuitl_count + 365 * diff, date, date - 18980)
  else
    return basic.BOGUS
  end
end

-- Designation of year containing fixed date; bogus for nemontemi.
local function aztec_xiuhmolpilli_from_fixed(date)
  local x = aztec_xihuitl_on_or_before(
    aztec_xihuitl_date(18, 20), date + 364
  )
  local month = aztec_xihuitl_month(aztec_xihuitl_from_fixed(date))
  if month == 19 then
    return basic.BOGUS  -- nemontemi
  else
    return aztec_tonalpohualli_from_fixed(x)
  end
end


-- === Exports ===

--- Latest fixed date on or before `date` with Mayan Haab date `haab`.
-- @function mayan_haab_on_or_before
-- @tparam table  haab Mayan Haab date {month, day}.
-- @tparam number date Fixed date.
-- @treturn number Fixed date.
M.mayan_haab_on_or_before = mayan_haab_on_or_before

--- Latest fixed date on or before `date` with Mayan Tzolkin date `tzolkin`.
-- @function mayan_tzolkin_on_or_before
-- @tparam table  tzolkin Mayan Tzolkin date {number, name}.
-- @tparam number date    Fixed date.
-- @treturn number Fixed date.
M.mayan_tzolkin_on_or_before = mayan_tzolkin_on_or_before

--- Latest fixed date on or before `date` with Mayan Calendar Round `haab`+`tzolkin`.
-- Returns BOGUS if the combination is impossible.
-- @function mayan_calendar_round_on_or_before
-- @tparam table  haab    Mayan Haab date {month, day}.
-- @tparam table  tzolkin Mayan Tzolkin date {number, name}.
-- @tparam number date    Fixed date.
-- @treturn number Fixed date or BOGUS.
M.mayan_calendar_round_on_or_before = mayan_calendar_round_on_or_before

--- Year bearer of fixed `date` in the Mayan calendar.
-- @function mayan_year_bearer_from_fixed
-- @tparam number date Fixed date.
-- @treturn number Year bearer.
M.mayan_year_bearer_from_fixed = mayan_year_bearer_from_fixed

--- Latest fixed date on or before `date` with Aztec Xihuitl date `xihuitl`.
-- @function aztec_xihuitl_on_or_before
-- @tparam table  xihuitl Aztec Xihuitl date {month, day}.
-- @tparam number date    Fixed date.
-- @treturn number Fixed date.
M.aztec_xihuitl_on_or_before = aztec_xihuitl_on_or_before

--- Latest fixed date on or before `date` with Aztec Tonalpohualli date `tonalpohualli`.
-- @function aztec_tonalpohualli_on_or_before
-- @tparam table  tonalpohualli Aztec Tonalpohualli date {number, name}.
-- @tparam number date          Fixed date.
-- @treturn number Fixed date.
M.aztec_tonalpohualli_on_or_before = aztec_tonalpohualli_on_or_before

--- Aztec Xiuhmolpilli (52-year cycle) designation for fixed `date`.
-- Returns BOGUS during Nemontemi (the 5 unlucky days).
-- @function aztec_xiuhmolpilli_from_fixed
-- @tparam number date Fixed date.
-- @treturn table|number Tonalpohualli date {number, name} or BOGUS.
M.aztec_xiuhmolpilli_from_fixed = aztec_xiuhmolpilli_from_fixed


return M
