--- Icelandic 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-icelandic
-- @release 0.1 2026-07-19

local M = {}

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


-- === Epoch ===

-- Fixed date of start of the Icelandic calendar.
local ICELANDIC_EPOCH = gregorian.fixed_from_gregorian(
  gregorian.gregorian_date(1, gregorian.APRIL, 19)
)


-- === Date constructor and accessors ===

-- Icelandic date constructor.
local function icelandic_date(year, season, week, weekday)
  return {year, season, week, weekday}
end

-- Icelandic year accessor.
local function icelandic_year(i_date)    return i_date[1] end

-- Icelandic season accessor.
local function icelandic_season(i_date)  return i_date[2] end

-- Icelandic week accessor.
local function icelandic_week(i_date)    return i_date[3] end

-- Icelandic weekday accessor.
local function icelandic_weekday(i_date) return i_date[4] end


-- === Season boundaries ===

-- Fixed date of start of summer season in Icelandic year i_year.
local function icelandic_summer(i_year)
  local apr19 = ICELANDIC_EPOCH
    + 365 * (i_year - 1)
    + basic.sigma(
    {
        basic.to_radix(i_year, {4,25,4}),
        {97,24,1,0}
    },
    function(y,a)
        return y*a
    end)
  return gregorian.kday_on_or_after(basic.THURSDAY, apr19)
end

-- Fixed date of start of winter season in Icelandic year i_year.
local function icelandic_winter(i_year)
  return icelandic_summer(i_year + 1) - 180
end


-- === Conversion ===

--- Fixed date equivalent to Icelandic date `i_date`.
-- @tparam table i_date Icelandic date {year, season, week, weekday}.
-- @treturn number Fixed date.
function M.fixed_from_icelandic(i_date)
  local year    = icelandic_year(i_date)
  local season  = icelandic_season(i_date)
  local week    = icelandic_week(i_date)
  local weekday = icelandic_weekday(i_date)
  local start   -- Start of season.
  local shift   -- First day of week in prior season.
  if season == astro.SUMMER then
    start = icelandic_summer(year)
    shift = basic.THURSDAY
  else
    start = icelandic_winter(year)
    shift = basic.SATURDAY
  end
  return start
    + 7 * (week - 1)              -- Elapsed weeks.
    + (weekday - shift) % 7
end
local fixed_from_icelandic = M.fixed_from_icelandic

--- Icelandic date {year, season, week, weekday} corresponding to fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, season, week, weekday}
function M.icelandic_from_fixed(date)
  local approx = basic.quotient(
    date - ICELANDIC_EPOCH + 369,
    146097 / 400
  )
  local year
  if date >= icelandic_summer(approx) then
    year = approx
  else
    year = approx - 1
  end
  local season
  if date < icelandic_winter(year) then
    season = astro.SUMMER
  else
    season = astro.WINTER
  end
  local start  -- Start of current season.
  if season == astro.SUMMER then
    start = icelandic_summer(year)
  else
    start = icelandic_winter(year)
  end
  local week    = 1 + basic.quotient(date - start, 7)
  local weekday = basic.day_of_week_from_fixed(date)
  return icelandic_date(year, season, week, weekday)
end


-- === Predicates and helpers ===

-- True if Icelandic i_year is a leap year (53 weeks).
local function icelandic_leap_year(i_year)
  return (icelandic_summer(i_year + 1) - icelandic_summer(i_year)) ~= 364
end

-- Month of i_date on the Icelandic calendar.
local function icelandic_month(i_date)
  local date      = fixed_from_icelandic(i_date)
  local year      = icelandic_year(i_date)
  local season    = icelandic_season(i_date)
  local midsummer = icelandic_winter(year) - 90
  local start
  if season == astro.WINTER then
    start = icelandic_winter(year)
  elseif date >= midsummer then
    start = midsummer - 90
  elseif date < icelandic_summer(year) + 90 then
    start = icelandic_summer(year)
  else
    start = midsummer  -- Epagomenae.
  end
  return 1 + basic.quotient(date - start, 30)
end


-- === Exports ===

--- Fixed date of start of summer season in Icelandic year `i_year`.
-- @function icelandic_summer
-- @tparam number i_year Icelandic year.
-- @treturn number Fixed date.
M.icelandic_summer = icelandic_summer

--- Fixed date of start of winter season in Icelandic year `i_year`.
-- @function icelandic_winter
-- @tparam number i_year Icelandic year.
-- @treturn number Fixed date.
M.icelandic_winter = icelandic_winter

--- True if Icelandic year `i_year` is a leap year (53 weeks).
-- @function icelandic_leap_year
-- @tparam number i_year Icelandic year.
-- @treturn boolean
M.icelandic_leap_year = icelandic_leap_year

--- Month number of Icelandic date `i_date`.
-- @function icelandic_month
-- @tparam table i_date Icelandic date {year, season, week, weekday}.
-- @treturn number Month number.
M.icelandic_month = icelandic_month


return M
