#' @title Plot a thematic map of so-ii
#'
#' @details
#' 
#' ## dataset specification
#' 
#' - `dataset` can be directly a sf (or sfc), in which case only its geometry is
#' used. All parameters passed to plot comes from `...`.
#' - if `dataset` is a list, each slot should be prepared so that to be call by
#' a `do.call(plot, slot)`. So it should have a slot named `x` and all slots useful
#' for plot.
#' 
#' ## dataset_legend specification
#' 
#' - `dataset_legend` can be directly a list of parameters that will be plot
#' through a `do.call(legend, dataset_legend)`. To be in this case, it is
#' checked if `dataset_legend` has a slot named `legend`.
#' - Otherwise, it is considered as a list of legends parameters and 
#' several legends are plotted.
#' 
#' By default, the dataset_legend is plotted in the "bottomright". It it has to
#' be changed a slot "x" is needed.
#' 
#' ## theme specification
#' 
#' For the specification of detail, it depends on the theme chosen.
#' 
#' - **none**: perimeter of so_ii is plotted.
#' - **catchment**: The area of catchments are plotted with a scope
#'      depending on detail. At least, a division between Lez and
#'      Bassin de l'Or is plotted.
#' - **catnat**: Informations on the number of "Arrêtés Cat
#'      Nat are provided at the scale of collectivities."
#' - **collectivty**: Boundaries of collectivities are plotted, more
#'      some administrative informations depending on detail.
#' - **hydro**: The hydrophic network is plotted. Depending on
#'      detail, only a part (rivers, canals, water bodies) or a degre of detail
#'      is plotted.
#' - **onrn**: Informations on the claims coming from Cat Nat system
#'      are plotted at the scale of the collectivities. With detail a selection
#'      of the data is made, with year a selection of the period.
#' - **osm**: A tile from OSM is plotted.
#' - **population**: Informations on the population coming from
#'      INSEE are plotted at the scale of the collectivities. With year a
#'      selection of the period is made, with detail a selection of how
#'      evolution between 2 years.
#' 
#' ## detail specification
#' 
#' For the specification of detail, it depends on the theme chosen.
#' 
#' - **catchment**: detail must be chosen in "none", "1", "2", "3"
#'      for levels of detail. If missing, "1" will be chosen.
#' - **catnat**: detail must be chosen in "inondation",
#'      "submersion", or "nappe". If missing all type will be chosen and
#'      aggregated before plotting.
#' - **clc**: detail must be chosen in "so.ii", "crop",
#'      "both". If missing, "so.ii" will be chosen, and only information inside
#'      so_ii_limit is plotted.
#' - **collectivity**: detail must be chosen in "none", "syble",
#'      "symbo", "epci" or "syndicate". If missing, "none" will be chosen,
#'      and only the boundaries of collectivities are plotted.
#' - **hydro**: detail must be chosen in "none", "1", "2", "3" for
#'      levels of detail or "canal", "river", "waterbody" for types of
#'      hydrographic elements. If missing, "none" will be chosen, and
#'      everything is plotted.
#' - **onrn**: detail must be chosen in "n_catnat", "freq_sin",
#'      "cost", "cost_hab", "cost_mean", "ratio", "balance", "ppri_year".
#' - **population**: detail must be chosen in "absolute",
#'      "relative". It used only when more than one year is provided to plot
#'      aither absolute or relative evolution.
#' 
#' ## year specification
#' 
#' For the specification of year, it depends on the theme chosen.
#' 
#' - **catnat**: year corresponds to the year of data. If 2 or more
#'      years are given, the sum of the period corresponding to the range of
#'      given years is plotted. If missing, the whole available period is
#'      plotted.
#' - **population**: year corresponds to the year of data. If
#'      missing, last available year is plotted. If 2 or more years are
#'      provided an analysis of the evolution between the range of given
#'      years is plotted.
#'
#' ## inset specification
#' 
#' If inset is not NULL, an inset will be plotted, depending on the value of
#' as.character(inset). Non-case sensitive partial matching is used, with "é"
#' interpreted as "e".
#' 
#' - **montpellier**: scope perimeter is located within Montpellier. Only
#'      useful when scope is less than Montpellier (for districts of
#'      Montpellier).
#' - **so-ii**: scope perimeter is located within so-ii. Only
#'      useful when scope is less than so-ii.
#' - **department**: scope perimeter is located within Hérault
#'      departement, if inset may be interpreted as "department", "département",
#'      "hérault", "34".
#' - **region**: scope perimeter is located within Occitanie region,
#'      if inset may be interpreted as "région", "Occitanie", "76" (INSEE code
#'      for Occitanie region).
#' - **nation**: scope perimeter is located within the metropolitean
#'      part of France, if inset may be interpreted as "France", "métropole",
#'      "nation".
#' 
#' If all other cases, nothing is added.
#'
#' ## scope specification
#'
#' The scope should be either:
#'
#' - **so.ii**: the whole perimiter of so.ii is chosen. This is
#'      default value.
#' - **vector of INSEE codes of collectivities in so.ii**: a
#'      perimeter is built with those collectivies. Only valid codes are taken
#'      into account. If none is chosen, a warning is dropped, and default
#'      value is used. See `so.ii::so_ii_collectivity` to have all the available
#'      collectivities 
#' - **vector of district for Montpellier**: a perimeter is built with those
#'      districts. There's 31 district, the selection is made with a character
#'      vector of the form "montpellier-xx", with "xx" being the number of the
#'      district ("01", ... "31"). See `so.ii::so_ii_montpellier` to have all
#'      the available districts.
#' 
#' It is not possible to mix collectivities and districts of Montpellier.
#'
#' ## path specification
#' 
#' Depending on the extension a device is chosen.
#' 
#' - **pdf**: grDevices::cairo_pdf
#' - **png**: grDevices::png
#' - **jpg**: grDevices::jpg
#' - **svg**: grDevices::svg
#' 
#' If path is NULL, standard plotting is used. If an extension is not managed,
#' an error is raised.
#' 
#' ## rendering specification
#' 
#' If 'density' is given, a call to `plot_polygon()` is made to render dataset.
#' If not, a call to the  `plot()` from sf is made.
#'
#' @param dataset sf object (or list of sf objects...), data to be plotted.
#'  See details.
#' @param dataset_legend list of parameters (or list of list...) to be passed to
#'  legend. See details.
#' @param theme character, choice for the theme (if any). See details.
#' @param theme_legend logical, should a legend be plotted for the theme.
#' @param detail character, detail for theme, depends on theme. See details.
#' @param year character, the year chosen for some themes. See details.
#' @param scope character, choice for the scope of the map. See details.
#' @param bar logical, should a bar be plotted for the dataset. See details.
#' @param limit logical, should the limit of so.ii be plotted for the dataset.
#' Default to TRUE.
#' @param inset character, managing if an inset is plotted.
#' @param path character, the name of the file to save the plot. Graphical
#'  device is chosen depending on extension. See details.
#' @param add logical, should the plot be added to an existing one. Default to
#' FALSE.
#' @param ...  some parameters that will be used by plot (from sf) or by
#' add_bar (so.ii).
#'
#' @return Nothing useful.
#' 
#' @export map_so_ii
#'
#' @encoding UTF-8
#' 
#' @examples
#' 
#' \dontrun{
#' # To be added (soon)
#' }

map_so_ii = function(
    dataset,
    dataset_legend = NULL,
    theme = c("none", "collectivity", "catchment", "catnat", "clc", "hydro", "onrn", "osm", "population"),
    theme_legend = FALSE,
    detail,
    year,
    scope = "so.ii",
    bar = TRUE,
    limit = TRUE,
    inset = NULL,
    path = NULL,
    add = FALSE,
    ...
) {
    requireNamespace("sf")
    osf = suppressMessages(sf::sf_use_s2(FALSE))
    on.exit(suppressMessages(sf::sf_use_s2(osf)))

    theme = match.arg(theme)

    if (!is.null(path)) {
        width = 18
        height = 18
        if (add != TRUE) {
            switch(
                EXPR = tolower(tools::file_ext(path)),
                "pdf" = grDevices::cairo_pdf(
                    filename = path,
                    width = width / 2.54,
                    height = height / 2.54
                ),
                "png" = grDevices::png(
                    filename = path,
                    width = width,
                    height = height,
                    units = "cm",
                    res = 144
                ),
                "jpg" = grDevices::jpeg(
                    filename = path,
                    width = width,
                    height = height,
                    units = "cm",
                    res = 144
                ),
                "svg" = grDevices::svg(
                    filename = path,
                    width = width / 2.54,
                    height = height / 2.54
                ),
                stop(sprintf("%s not recognized", tolower(tools::file_ext(path))))
            )
            graphics::par(bg = "white")
            on.exit(grDevices::dev.off(), add = TRUE, after = TRUE)
        } else {
            graphics::par(bg = "white")
            on.exit(
                switch(
                    EXPR = tolower(tools::file_ext(path)),
                    "pdf" = grDevices::dev.copy(
                        grDevices::cairo_pdf,
                        filename = path,
                        width = width / 2.54,
                        height = height / 2.54
                    ),
                    "png" = grDevices::dev.copy(
                        grDevices::png,
                        filename = path,
                        width = width,
                        height = height,
                        units = "cm",
                        res = 144
                    ),
                    "jpg" = grDevices::dev.copy(
                        grDevices::jpeg,
                        filename = path,
                        width = width,
                        height = height,
                        units = "cm",
                        res = 144
                    ),
                    "svg" = grDevices::dev.copy(
                        grDevices::svg,
                        filename = path,
                        width = width / 2.54,
                        height = height / 2.54
                    ),
                    stop(sprintf("%s not recognized", tolower(tools::file_ext(path))))
                ),
                add = TRUE, after = TRUE
            )
            on.exit(grDevices::dev.off(), add = TRUE, after = TRUE)
        }
    }

    ## Check scope
    scope_commune = so.ii::so_ii_collectivity[["commune"]]
    scope_montpellier = sprintf("montpellier-%s", so.ii::so_ii_montpellier[["district"]])
    if (identical(scope, "so.ii")) {
        scope = so.ii::so_ii_limit
    } else if (any(scope %in% scope_commune)) {
        scope = scope[scope %in% scope_commune]
        scope = suppressMessages(sf::st_union(so.ii::so_ii_collectivity[scope_commune %in% scope, ]))
    } else if (any(scope %in% scope_montpellier)) {
        scope = scope[scope %in% scope_montpellier]
        scope = suppressMessages(sf::st_union(so.ii::so_ii_montpellier[scope_montpellier %in% scope, ]))
    } else {
        warnings("Nothing pertinent found in scope, default value is used.")
        scope = so.ii::so_ii_limit
    }

    ## Init map
    if (add != TRUE) {
        cex.main = list(...)[["cex.main"]][1]
        if (is.null(cex.main) | !is.numeric(cex.main)) cex.main = 2.5
        graphics::par(mai = c(.65, .60, .50, .15))
        plot(scope, border = NA, axes = TRUE, main = list(...)[["main"]], cex.main = cex.main)
    } else {
        bar = FALSE
    }

    ## Plot theme if any, return theme_legend
    theme_legend = switch(
        EXPR = theme,
        "catchment" = map_theme_catchment(detail, theme_legend),
        "catnat" = map_theme_catnat(detail, year, theme_legend),
        "clc" = map_theme_clc(detail, theme_legend),
        "collectivity" = map_theme_collectivity(detail, theme_legend),
        "hydro" = map_theme_hydro(detail, theme_legend),
        "onrn" = map_theme_onrn(detail, theme_legend),
        "osm" = map_theme_osm(),
        "population" = map_theme_population(detail, year, theme_legend),
        NULL
    )

    ## Plot dataset if any
    if (!missing(dataset)) {
        plot_dataset(dataset, ...)
    }

    ## Make scope visible
    if (limit == TRUE) {
        plot(scope, lwd = 2, add = TRUE)
    }

    ## Plot bar
    if (bar == TRUE) {
        if (identical(scope, so.ii::so_ii_limit)) {
            # Parameters fitted for so_ii scope
            add_bar(
                bar_d = 10,
                bar_xy = c(3.55, 43.47),
                bar_adj = c(0.5, -1),
                bar_lon = 0.4788987
            )
        } else {
            add_bar(...)
        }
    }

    ## Plot inset
    if (!is.null(inset)) {
        inset = gsub("\u00e9", "e", tolower(as.character(inset)[1]))
        admissible = list(
            "montpellier" = c("34172", "montpellier"),
            "so-ii" = c("so-ii"),
            "department" = c("34", "departement", "department", "herault"),
            "region" = c("76", "occitanie", "region"),
            "nation" = c("france", "metropole", "nation")
        )
        inset = names(which(sapply(
            admissible,
            function(x, pattern){length(grep(sprintf("^%s", pattern), x)) > 0},
            inset
        )))
        if (length(inset) == 1 && inset %in% rownames(so.ii::so_ii_inset)) {
            scope_sf = sf::st_as_sf(scope)
            names(scope_sf) = "geometry"
            sf::st_geometry(scope_sf) = "geometry"
            inset =  rbind(
                so.ii::so_ii_inset[inset, "geometry"],
                sf::st_as_sf(scope_sf)

            )
            add_inset(
                inset,
                scope,
                col = c("gray85", "red"),
                border = c("gray", "red"),
                lwd = 1:2
            )
        }
    }

    ## Plot dataset_legend if any
    if (!is.null(dataset_legend)) {
        plot_dataset_legend(dataset_legend)
    }

    ## Plot theme_legend if any
    if (!is.null(theme_legend)) {
        if (!is.null(theme_legend[["text.width"]])) {
            text_legend = theme_legend[["legend"]]
            theme_legend[["legend"]] = rep("", length(text_legend))
        }
        temp = do.call(graphics::legend, theme_legend)
        if (!is.null(theme_legend[["text.width"]])) {
            graphics::text(
                x = temp[["rect"]][["left"]] + temp[["rect"]][["w"]],
                y = temp[["text"]][["y"]],
                labels = text_legend,
                pos = 2
            )
        }
    }

    return(invisible(NULL))
}

plot_dataset = function(dataset, internal = FALSE, ...) {
    if (isFALSE(internal) && methods::is(dataset, "list")) {
        lapply(dataset, plot_dataset, internal = TRUE)
        return(invisible())
    }

    if (isTRUE(internal) && methods::is(dataset, "list")) {
        parameter = dataset[! names(dataset) %in% "x" ]
        parameter[["add"]] = TRUE
        dataset = dataset[["x"]]
    }

    if (methods::is(dataset, "sf")) {
        dataset = dataset[["geometry"]]
    }
    if (! methods::is(dataset, "sfc")) {
        warning("'dataset' is not a sf or sfc object. It is not plotted.")
        return(invisible())
    }

    if (isTRUE(internal)) {
        if ("density" %in% names(parameter)) {
            parameter = parameter[intersect(names(parameter), c("density", "angle", "col", "border"))]
            do.call(plot_density, c(list(x = dataset), parameter))
        } else {
            do.call(plot, c(list(x = dataset), parameter))
        }
    } else {
        parameter = list(...)
        if ("density" %in% names(parameter)) {
            parameter = parameter[intersect(names(parameter), c("density", "angle", "col", "border"))]
            do.call(plot_density, c(list(x = dataset), parameter))
        } else {
            plot(dataset, add = TRUE, ...)
        }
    }
}

plot_dataset_legend = function(dataset_legend, internal = FALSE) {
    if (isFALSE(internal) && ! ("legend" %in% names(dataset_legend))) {
        lapply(dataset_legend, plot_dataset_legend, internal = TRUE)
        return(invisible())
    }

    if (! ("legend" %in% names(dataset_legend))) {
        warning("'dataset_legend' needs a 'legend' slot. It is not plotted.")
    } else {
        if (is.null(dataset_legend[["x"]])) dataset_legend[["x"]] = "bottomright"
        if (is.null(dataset_legend[["cex"]])) dataset_legend[["cex"]] = 0.8
        if (is.null(dataset_legend[["bg"]])) dataset_legend[["bg"]] = "white"
        if (is.null(dataset_legend[["inset"]])) dataset_legend[["inset"]] = 0.01
        do.call(graphics::legend, dataset_legend)
    }
}

map_theme_catchment = function(detail, add_legend) {
    if (missing(detail)) {
        detail = "1"
    }
    detail = match.arg(
        as.character(detail), 
        choices = levels(so.ii::so_ii_catchment[["degre"]])
    )

    selection  = so.ii::so_ii_catchment[["degre"]] == detail
    geometry = so.ii::so_ii_catchment[["geometry"]][selection]
    catchment = as.factor(so.ii::so_ii_catchment[["catchment_name"]][selection])
    color_legend = grDevices::hcl.colors(nlevels(catchment), "Pastel 1", alpha = .3)
    color = color_legend[catchment]
    border = "grey80"
    lwd = 2
    theme_legend = list(
        title = sprintf("Bassin versant"),
        legend = levels(catchment),
        x = "topright",
        cex = .8,
        bg = "white",
        inset = 0.01,
        fill = color_legend,
        border = border
    )
    plot(geometry, border = border, col = color, lwd = lwd, add = TRUE)

    if (add_legend == TRUE && detail != "3") {
        return(theme_legend)
    } else {
        return(NULL)
    }
}

map_theme_catnat = function(detail, year, add_legend) {
    if (missing(detail)) {
        detail = dimnames(so.ii::so_ii_catnat)[["hazard"]]
    }
    detail = match.arg(
        detail, 
        dimnames(so.ii::so_ii_catnat)[["hazard"]],
        several.ok = TRUE
    )

    if (missing(year)) {
        year = range(dimnames(so.ii::so_ii_catnat)[["period"]])
    }
    year = match.arg(
        as.character(year),
        dimnames(so.ii::so_ii_catnat)[["period"]],
        several.ok = TRUE
    )
    year = as.character(seq(min(year), max(year)))
    catnat = apply(
        so.ii::so_ii_catnat[, year, detail, drop = FALSE],
        1,
        sum
    )

    border = "grey80"
    catnat_palette = scales::colour_ramp(c("white", "grey50"), alpha = .5)
    color = scales::cscale(
        c(0, catnat),
        catnat_palette
    )[-1]
    plot(
        so.ii::so_ii_collectivity[["geometry"]],
        border = border,
        col = color,
        add = TRUE
    )

    legend_title = sprintf(
        "Cat Nat %s",
        if (length(detail) == 3) "" else paste(sort(detail), collapse = " & ")
    )
    legend_title = sprintf(
        "%s [%s]",
        legend_title,
        if (length(year) == 1) year else paste(range(year), collapse = "-")
    )

    value_legend = unique(sort(c(min(catnat), round(seq(0, max(catnat), length.out = 5)))))
    color_legend = scales::cscale(
        value_legend,
        catnat_palette
    )

    theme_legend = list(
        title = legend_title,
        legend = value_legend,
        x = "topright",
        cex = .8,
        bg = "white",
        inset = 0.01,
        fill = color_legend,
        border = border,
        text.width = max(graphics::strwidth(value_legend))
    )

    if (add_legend == TRUE) {
        return(theme_legend)
    } else {
        return(NULL)
    }
}

map_theme_clc = function(detail, add_legend) {
    if (missing(detail)) {
        detail = "so.ii"
    }
    detail = match.arg(
        detail, 
        c("so.ii", "crop", "both")
    )

    if (detail %in% c("crop", "both")) {
        plot(
            so.ii::so_ii_clc_crop[["geometry"]],
            border = NA,
            col = so.ii::so_ii_clc_crop[["color"]],
            add = TRUE
        )
    }

    if (detail %in% c("so.ii", "both")) {
        plot(
            so.ii::so_ii_clc[["geometry"]],
            border = NA,
            col = so.ii::so_ii_clc[["color"]],
            add = TRUE
        )
    }

    theme_legend = list(
        title = "CLC (2018)",
        legend = so.ii::clc_color[["label_fr"]],
        x = "topright",
        cex = .8,
        bg = "white",
        inset = 0.01,
        fill = so.ii::clc_color[["color"]]
    )

    if (add_legend == TRUE) {
        return(theme_legend)
    } else {
        return(NULL)
    }
}

map_theme_collectivity = function(detail, add_legend)  {
    if (missing(detail)) {
        detail = "none"
    }
    detail = match.arg(
        detail, 
        c("none", "syble", "symbo", "epci", "syndicate")
    )

    border = "grey80"
    color = NA

    theme_legend = list(
        title = "Caract\u00e9ristiques des communes",
        legend = "Commune",
        x = "topright",
        cex = .8,
        bg = "white",
        inset = 0.01,
        fill = color,
        border = border
    )
    geometry = so.ii::so_ii_collectivity[["geometry"]]
    plot(geometry, border = border, col = color, add = TRUE)
    
    if (detail %in% c("syble", "syndicate")) {
        color_legend = scales::alpha("orange", .3)
        color = ifelse(
            so.ii::so_ii_collectivity[["syble"]],
            color_legend,
            NA
        )
        plot(geometry, border = border, col = color, add = TRUE)
        theme_legend[["legend"]] = c(theme_legend[["legend"]], "SYBLE")
        theme_legend[["fill"]] = c(theme_legend[["fill"]], color_legend)  
    }
    if (detail %in% c("symbo", "syndicate")) {
        color_legend = scales::alpha("green", .3)
        color = ifelse(
            so.ii::so_ii_collectivity[["symbo"]],
            color_legend,
            NA
        )
        plot(geometry, border = border, col = color, add = TRUE)
        theme_legend[["legend"]] = c(theme_legend[["legend"]], "SYMBO")
        theme_legend[["fill"]] = c(theme_legend[["fill"]], color_legend) 
    }
    if (detail == "epci") {
        epci = as.factor(so.ii::so_ii_collectivity[["epci_name"]])
        color_legend = grDevices::hcl.colors(nlevels(epci), "Lisbon", alpha = .3)
        color = color_legend[epci]
        plot(geometry, border = border, col = color, add = TRUE)
        theme_legend[["legend"]] = levels(epci)
        theme_legend[["fill"]] = color_legend
    }

    if (add_legend == TRUE) {
        return(theme_legend)
    } else {
        return(NULL)
    }
}

map_theme_hydro = function(detail, add_legend) {
    if (missing(detail)) {
        detail = "none"
    }
    detail = match.arg(
        as.character(detail),
        choices = c(
            "none",
            levels(so.ii::so_ii_hydro[["degre"]]),
            levels(so.ii::so_ii_hydro[["type"]])
        )
    )
    color = scales::alpha("blue", .3)
    bg = scales::alpha("blue", .3)
    border = NA
    selection = seq(nrow(so.ii::so_ii_hydro))
    theme_legend = list(
        title = sprintf("R\u00e9seau hydrographique"),
        legend = "\u00e9l\u00e9ment du r\u00e9seau",
        x = "topright",
        cex = .8,
        bg = "white",
        inset = 0.01,
        col = color,
        lwd = 1
    )
    if (detail %in% levels(so.ii::so_ii_hydro[["type"]])) {
        selection  = as.character(so.ii::so_ii_hydro[["type"]]) == detail
        theme_legend[["legend"]] = detail
    }
    if (detail %in% levels(so.ii::so_ii_hydro[["degre"]])) {
        selection  = as.character(so.ii::so_ii_hydro[["degre"]]) <= detail
    }
    geometry = so.ii::so_ii_hydro[["geometry"]][selection]
    lwd = 4 - as.numeric(so.ii::so_ii_hydro[["degre"]][selection])

    plot(geometry, col = color, lwd = lwd, border = border, add = TRUE)

    if (add_legend == TRUE) {
        return(theme_legend)
    } else {
        return(NULL)
    }
}

map_theme_onrn = function(detail, add_legend) {
    if (missing(detail)) {
        detail = "cost"
    }
    detail = match.arg(
        as.character(detail),
        sort(colnames(so.ii::so_ii_onrn)[1:8])
    )

    onrn_palette = switch(
        EXPR = detail,
        "n_catnat"  = scales::colour_ramp(c("white", "red"), alpha = .5),
        "freq_sin"  = scales::colour_ramp(c("white", "red"), alpha = .5),
        "cost"      = scales::colour_ramp(c("white", "red"), alpha = .5),
        "cost_hab"  = scales::colour_ramp(c("white", "red"), alpha = .5),
        "cost_mean" = scales::colour_ramp(c("white", "red"), alpha = .5),
        "ratio"     = scales::colour_ramp(c("green", "white", "red"), alpha = .5),
        "balance"   = scales::colour_ramp(c("red", "white", "green"), alpha = .5),
        "ppri_year" = scales::colour_ramp(c("grey80", "grey50"), alpha = .5),
        NULL
    )
    onrn_trans = switch(
        EXPR = detail,
        "n_catnat"  = scales::identity_trans(),
        "freq_sin"  = scales::identity_trans(),
        "cost"      = scales::sqrt_trans(),
        "cost_hab"  = scales::sqrt_trans(),
        "cost_mean" = scales::sqrt_trans(),
        "ratio"     = scales::sqrt_trans(),
        "balance"   = scales::modulus_trans(.5),
        "ppri_year" = scales::identity_trans(),
        NULL
    )
    onrn_range = switch(
        EXPR = detail,
        "ratio"     = c(0, 4),
        "balance"   = max(abs(range(so.ii::so_ii_onrn[["balance"]]))) * c(-1, 1),
        NULL
    )

    color = scales::cscale(
        c(onrn_range, so.ii::so_ii_onrn[[detail]]),
        onrn_palette,
        trans = onrn_trans)
    if (length(onrn_range) > 0) {
        color = color[-seq(onrn_range)]
    }
    border = "grey80"
    plot(
        so.ii::so_ii_collectivity[["geometry"]],
        border = border,
        col = color,
        add = TRUE
    )

    if (sprintf("%s_min", detail) %in% names(so.ii::so_ii_onrn)) {
        selection = c(detail, sprintf("%s_min", detail), sprintf("%s_max", detail))
        temp = unique(so.ii::so_ii_onrn[selection])
        temp = temp[order(temp[[detail]]), ]
        text_legend = gsub("0 - 0", "0",
            sprintf(
                "%s - %s",
                temp[[sprintf("%s_min", detail)]],
                temp[[sprintf("%s_max", detail)]]
            )
        )
        value_legend = temp[[detail]]
    }
    if (detail %in% c("n_catnat", "ppri_year")) {
        value_legend = round(
            seq(
                min(so.ii::so_ii_onrn[[detail]], na.rm = TRUE),
                max(so.ii::so_ii_onrn[[detail]], na.rm = TRUE),
                length.out = 5
            )
        )
        text_legend = value_legend
    }
    if (detail %in% c("balance")) {
        value_legend = unique(
            c(
                seq(min(so.ii::so_ii_onrn[[detail]]), 0, length.out = 4),
                seq(0, max(so.ii::so_ii_onrn[[detail]]), length.out = 4)
            )
        )
        text_legend = formatC(
            as.integer(signif(round(value_legend), 2)),
            big.mark = " "
        )
        text.width = max(graphics::strwidth(text_legend))
    }
    color_legend = scales::cscale(
            c(onrn_range, value_legend),
            onrn_palette,
            trans = onrn_trans
        )
    if (length(onrn_range) > 0) {
        color_legend = color_legend[-seq(onrn_range)]
    }
    title_onrn = switch(
        EXPR = detail,
        "n_catnat"  = "Arr\u00eat\u00e9s Cat-Nat [1982-2021]",
        "freq_sin"  = "Sinistre / Risque [1995-2018]",
        "cost"      = "Co\u00fbt cumul\u00e9 (\u20AC) [1995-2018]",
        "cost_hab"  = "Co\u00fbt / hab (\u20ac) [1995-2018]",
        "cost_mean" = "Co\u00fbt / sinistre (\u20ac) [1995-2018]",
        "ratio"     = "Co\u00fbt / Prime [1995-2018]",
        "balance"   = "Co\u00fbt - Prime (\u20ac) [1995-2018]",
        "ppri_year" = "Ann\u00e9e des PPRI",
        NULL
    )

    theme_legend = list(
        title = title_onrn,
        legend = text_legend,
        x = "topright",
        cex = .8,
        bg = "white",
        inset = 0.01,
        fill = color_legend,
        border = border
    )
    if (detail %in% c("balance", "cost")) {
        theme_legend[["text.width"]] = max(graphics::strwidth(text_legend))
    }
    
    if (add_legend == TRUE) {
        return(theme_legend)
    } else {
        return(NULL)
    }
}

map_theme_osm = function() {
    so_ii_osm = terra::rast(
        system.file("extdata", "so_ii_osm.tif", package = "so.ii", mustWork = TRUE)
    )
    try(terra::plot(so_ii_osm, add = TRUE), silent = TRUE)
    graphics::mtext(
        text = "Fond de carte : \u00a9 Contributeurs OpenStreetMap", 
        side = 1, line = -1, adj = 1, cex = .6, font = 3
    )
    return(NULL)
}

map_theme_population = function(detail, year, add_legend) {
    if (missing(year)) {
        year = utils::tail(sort(colnames(so.ii::so_ii_population)), 1)
    }
    year = match.arg(
        as.character(year),
        sort(colnames(so.ii::so_ii_population)),
        several.ok = TRUE
    )

    border = "grey80"

    if (length(year) == 1) {
        pop_palette = scales::colour_ramp(c("white", "red"), alpha = .5)
        color = matrix(
            scales::cscale(
                so.ii::so_ii_population,
                pop_palette,
                trans = scales::log_trans()),
            nrow = nrow(so.ii::so_ii_population),
            dimnames = dimnames(so.ii::so_ii_population)
        )

        plot(
            so.ii::so_ii_collectivity[["geometry"]],
            border = border,
            col = color[ , year],
            add = TRUE
        )
        max_pop = max(so.ii::so_ii_population[ , year])
        min_pop = min(so.ii::so_ii_population[ , year])
        base = 10

        value_legend = unique(c(
            min_pop,
            base^(ceiling(log(min_pop)/log(base)):floor(log(max_pop)/log(base))),
            max_pop
        ))
        color_legend = scales::cscale(
                c(range(so.ii::so_ii_population), value_legend),
                pop_palette,
                trans = scales::log_trans()
            )[-(1:2)]
        text_legend = formatC(
            as.integer(value_legend),
            big.mark = " "
        )

        theme_legend = list(
            title = sprintf("Population %s", year),
            legend = text_legend,
            x = "topright",
            cex = .8,
            bg = "white",
            inset = 0.01,
            fill = color_legend,
            border = border,
            text.width = max(graphics::strwidth(text_legend))
        )
    }
    
    if (length(year) > 1) {
        if (missing(detail)) {
            detail = "absolute"
        }
        detail = match.arg(as.character(detail), c("absolute", "relative"))
        year = range(year)

        pop_palette = scales::colour_ramp(
            c("red", "white", "green"),
            alpha = .5
        )
        pop_data = switch(
            EXPR = detail,
            "absolute" = so.ii::so_ii_population[ , year[2]] -
                so.ii::so_ii_population[ , year[1]],
            "relative"  = (so.ii::so_ii_population[ , year[2]] -
                so.ii::so_ii_population[ , year[1]]) /
                so.ii::so_ii_population[ , year[1]]
        )
        range_data = max(abs(range(pop_data))) * c(-1, 1)
        pop_trans = switch(
            EXPR = detail,
            "absolute"  = scales::modulus_trans(0.2),
            "relative"  = scales::modulus_trans(0.1),
            NULL
        )
        color = scales::cscale(
            c(range_data, pop_data),
            pop_palette,
            trans = pop_trans
        )[-(1:2)]
        plot(
            so.ii::so_ii_collectivity[["geometry"]],
            border = border,
            col = color,
            add = TRUE
        )

        max_pop = max(pop_data)
        min_pop = min(pop_data)

        if (detail == "absolute") {
            range_pop = max(abs(c(max_pop, min_pop)))
            base = max(10, 10^floor(ceiling(log(range_pop)/log(10)) / 2))

            if (sign(min_pop) == -1) {
                value_legend = c(
                    -base^(floor(log(abs(min_pop))/log(base)):1),
                    base^(1:floor(log(max_pop)/log(base)))
                )
                value_legend = value_legend[
                    value_legend < max_pop &
                    value_legend > min_pop &
                    abs(value_legend) >= base
                ]
                value_legend = sort(c(0, range(pop_data), value_legend))
            } else {
                value_legend = unique(c(
                    min_pop,
                    base^(ceiling(log(min_pop)/log(base)):floor(log(max_pop)/log(base))),
                    max_pop
                ))
            }
            color_legend = scales::cscale(
                c(range_data, value_legend),
                pop_palette,
                trans = pop_trans
            )[-(1:2)]
            text_legend = formatC(
                as.integer(value_legend),
                big.mark = " "
            )
            title_legend = sprintf("Population \u00e9volution [%s-%s]", year[1], year[2])
        }

        if (detail == "relative") {
            max_pop = max(pop_data) * 100
            min_pop = min(pop_data) * 100
            range_pop = max(abs(c(max_pop, min_pop)))
            base = max(10, 10^floor(ceiling(log(range_pop)/log(10)) / 2))

            if (sign(min_pop) == -1) {
                value_legend = unique(c(
                    min_pop,
                    -base^(floor(log(abs(min_pop))/log(base)):0),
                    0,
                    base^(0:floor(log(max_pop)/log(base))),
                    max_pop
                ))
            } else {
                value_legend = unique(c(
                    min_pop,
                    base^(ceiling(log(min_pop)/log(base)):floor(log(max_pop)/log(base))),
                    max_pop
                ))
            }
            color_legend = scales::cscale(
                    c(range_data, value_legend / 100),
                    pop_palette,
                    trans = pop_trans
                )[-(1:2)]
            text_legend = sprintf(
                "%s %%",
                formatC(
                    signif(value_legend, 3),
                    digits = 2, format = "f", flag = "+",
                    big.mark = " "
                )
            )
            title_legend = sprintf("Population \u00e9volution [%s-%s]", year[1], year[2])
        }

        theme_legend = list(
            title = title_legend,
            legend = text_legend,
            x = "topright",
            cex = .8,
            bg = "white",
            inset = 0.01,
            fill = color_legend,
            border = border,
            text.width = max(graphics::strwidth(text_legend))
        )
    }

    if (add_legend == TRUE) {
        return(theme_legend)
    } else {
        return(NULL)
    }
}
