#  File src/library/utils/R/roman.R
#  Part of the R package, https://www.R-project.org
#
#  Copyright (C) 1995-2025 The R Core Team
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  A copy of the GNU General Public License is available at
#  https://www.R-project.org/Licenses/



.max.roman <- 4999L # maximally allowed value for (default) check.range=TRUE
## FIXME (?):  Users "cannot" set  check.range = FALSE  in as.roman(), print.roman(), ...

.as.roman <- function(x, check.range=TRUE)
{
    if(is.integer(x)) { }
    else if(is.double(x) || is.logical(x)) # <- as.roman(NA)
        x <- as.integer(x)
    else if(is.character(x)) {
	x <- if(all(dig.x <- !nzchar(x) | is.na(x) | grepl("^[[:digit:]]+$", x)))
		 as.integer(x)
	     else if(any(dig.x)) {
		 r <- suppressWarnings(as.integer(x))# NAs for all non-dig
		 r[!dig.x] <- .roman2numeric(x[!dig.x])
		 r
	     }
	     else ## no digits -- assume all roman characters
		 .roman2numeric(x)
    }
    else
        stop("cannot coerce 'x' to roman")
    if(check.range) x[x <= 0L | x > .max.roman] <- NA
    class(x) <- "roman"
    x
}
as.roman <- function(x) .as.roman(x, check.range=TRUE)

as.character.roman <- function(x, ...) .numeric2roman(x)

format.roman <- function(x, ...) format(as.character.roman(x), ...)

print.roman <- function(x, quote = FALSE, ...)
{
    if(length(x)) print(as.character.roman(x), quote=quote, ...)
    else cat("<0-length roman>\n")
    invisible(x)
}

`[.roman` <-
function(x, i)
{
    cl <- oldClass(x)
    y <- NextMethod("[")
    oldClass(y) <- cl
    y
}

Ops.roman <- function(e1, e2) {
    if(.Generic %in% c("+", "-", "*", "^", "%%", "%/%", "/")) { # "Arith" in S4 parlance:
	e1 <- .as.roman(e1, check.range=FALSE)
	e2 <- .as.roman(e2, check.range=FALSE)
	as.roman(NextMethod(.Generic))
    }
    else # "Compare" and "Logic" in S4 parlance; just work with integer:
	NextMethod(.Generic)
}

Summary.roman <- function(x, ..., na.rm=TRUE) {
    if(.Generic %in% c("any", "all"))
        NextMethod(.Generic)
    else # max, min, .. sum
        as.roman(NextMethod(.Generic))
}


## for recycling etc
rep.roman <- function(x, ...) structure(rep(unclass(x), ...), class = class(x))

## romans: used in both utility functions, and not unuseful in general:
.romans <-
    c(1000L, 900L, 500L, 400L, 100L, 90L, 50L, 40L, 10L, 9L, 5L,  4L, 1L)
names(.romans) <-
    c("M", "CM",  "D",  "CD", "C",  "XC", "L", "XL","X","IX","V","IV","I")
## Can *not* use stats {dependency cycle at build time} -- hence need our own:
## .setNames <- function (object = nm, nm) {
##     names(object) <- nm
##     object
## }
## .romans <- .setNames(
##     c(1000L, 900L, 500L, 400L, 100L, 90L, 50L, 40L, 10L, 9L, 5L,  4L, 1L),
##     c("M", "CM",  "D",  "CD", "C",  "XC", "L", "XL","X","IX","V","IV","I"))

.numeric2roman <-
function(x) {
    romaNs <- names(.romans)
    n2r <- function(z) {
        y <- character()
	for(i in seq_along(.romans)) {
	    d <- .romans[[i]]
            while(z >= d) {
                z <- z - d
                y <- c(y, romaNs[i])
            }
        }
        paste(y, collapse = "")
    }

    x <- as.integer(x)
    ind <- is.na(x) | (x <= 0L) | (x > .max.roman)
    out <- character(length(x))
    out[ind] <- NA
    out[!ind] <- vapply(x[!ind], n2r, "")
    out
}

.roman2numeric <- function(x)
{
    out <- integer(length(x))
    out[ina <- is.na(x) | !nzchar(x)] <- NA
    if(any(ind <- !ina)) {
        y <- toupper(x[ind])
        y <- gsub("CM", "DCCCC", y)
        y <- gsub("CD", "CCCC", y)
        y <- gsub("XC", "LXXXX", y)
        y <- gsub("XL", "XXXX", y)
        y <- gsub("IX", "VIIII", y)
        y <- gsub("IV", "IIII", y)
        ok <- grepl("^M{0,9}D?C{0,6}L?X{0,6}V?I{0,6}$", y) # allow "...IIIIII"
        if(any(!ok)) {
            warning(sprintf(ngettext(sum(!ok),
                                     "invalid roman numeral: %s",
                                     "invalid roman numerals: %s"),
                            paste(x[ind][!ok], collapse = " ")),
                    domain = NA)
            out[ind][!ok] <- NA
        }
	out[ind][ok] <-
	    vapply(strsplit(y[ok], ""),
		   function(z)
		       as.integer(sum(.romans[match(z, names(.romans))])),
		   integer(1L))
    }
    out
}
