--- Coptic and Ethiopic 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-coptic-ethiopic
-- @release 0.1 2026-07-19

local M = {}

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


-- === Epochs ===

-- Coptic epoch: August 29, 284 CE (Julian).
local COPTIC_EPOCH = julian.fixed_from_julian(
  julian.julian_date(julian.ce(284), julian.AUGUST, 29)
)

-- Ethiopic epoch: August 29, 8 CE (Julian).
local ETHIOPIC_EPOCH = julian.fixed_from_julian(
  julian.julian_date(julian.ce(8), julian.AUGUST, 29)
)

local function coptic_date(year, month, day) return {year, month, day} end

--- True if `c_year` is a Coptic leap year.
-- @tparam number c_year Coptic year.
-- @treturn boolean
function M.coptic_leap_year(c_year)
  return c_year % 4 == 3
end


-- === Coptic conversion ===

--- Fixed date of Coptic date `c_date`.
-- @tparam table c_date Coptic date {year, month, day}.
-- @treturn number Fixed date.
function M.fixed_from_coptic(c_date)
  local month = basic.standard_month(c_date)
  local day   = basic.standard_day(c_date)
  local year  = basic.standard_year(c_date)
  return COPTIC_EPOCH - 1            -- Days before start of calendar.
    + 365 * (year - 1)               -- Ordinary days in prior years.
    + basic.quotient(year, 4)        -- Leap days in prior years.
    + 30 * (month - 1)               -- Days in prior months this year.
    + day                            -- Days so far this month.
end
local fixed_from_coptic = M.fixed_from_coptic

--- Coptic date {year, month, day} equivalent to fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, month, day}
function M.coptic_from_fixed(date)
  local year  = basic.quotient(
    4 * (date - COPTIC_EPOCH) + 1463, 1461
  )
  local month = 1 + basic.quotient(
    date - fixed_from_coptic(coptic_date(year, 1, 1)),
    30
  )
  local day = date + 1
    - fixed_from_coptic(coptic_date(year, month, 1))
  return coptic_date(year, month, day)
end
local coptic_from_fixed = M.coptic_from_fixed


-- === Ethiopic date constructor ===

-- Ethiopic date constructor.
local function ethiopic_date(year, month, day) -- luacheck: ignore
  return {year, month, day}
end


-- === Ethiopic conversion ===

--- Fixed date of Ethiopic date `e_date`.
-- Same structure as the Coptic calendar with a different epoch.
-- @tparam table e_date Ethiopic date {year, month, day}.
-- @treturn number Fixed date.
function M.fixed_from_ethiopic(e_date)
  local month = basic.standard_month(e_date)
  local day   = basic.standard_day(e_date)
  local year  = basic.standard_year(e_date)
  return ETHIOPIC_EPOCH
    + fixed_from_coptic(coptic_date(year, month, day))
    - COPTIC_EPOCH
end

--- Ethiopic date {year, month, day} equivalent to fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, month, day}
function M.ethiopic_from_fixed(date)
  return coptic_from_fixed(date + COPTIC_EPOCH - ETHIOPIC_EPOCH)
end


-- === Coptic date in Gregorian year ===

--- Fixed dates of Coptic month `c_month`, day `c_day` that fall in Gregorian year `g_year`.
-- @tparam number c_month Coptic month.
-- @tparam number c_day Coptic day.
-- @tparam number g_year Gregorian year.
-- @treturn table List of fixed dates (0 or 1 entries).
function M.coptic_in_gregorian(c_month, c_day, g_year)
  local jan1  = gregorian.gregorian_new_year(g_year)
  local y     = basic.standard_year(coptic_from_fixed(jan1))
  local date0 = fixed_from_coptic(coptic_date(y,     c_month, c_day))
  local date1 = fixed_from_coptic(coptic_date(y + 1, c_month, c_day))
  return basic.list_range(
    {date0, date1},
    gregorian.gregorian_year_range(g_year)
  )
end
local coptic_in_gregorian = M.coptic_in_gregorian


-- === Coptic Christmas ===

--- Fixed dates of Coptic Christmas (Coptic month 4, day 29) in Gregorian year `g_year`.
-- @tparam number g_year Gregorian year.
-- @treturn {number,...} Fixed dates of Coptic Christmas in the year.
function M.coptic_christmas(g_year)
  return coptic_in_gregorian(4, 29, g_year)
end


return M
