#' @title Plot a single map legend
#' @description Plot different types of legend. The "type" argument defines the
#' legend type.
#' Please note that some arguments are available for all types of legend and
#' some others are only relevant for specific legend types (see Details).
#'
#' @md
#' @param type type of legend:
#' * **prop** for proportional symbols,
#' * **choro** for choropleth maps,
#' * **cont** for continuous maps (e.g. raster),
#' * **typo** for typology maps,
#' * **symb** for symbols maps,
#' * **prop_line** for proportional lines maps,
#' * **grad_line** for graduated lines maps,
#' * **histo** for histograms,
#' * **choro_point** for choropleth points maps,
#' * **choro_line** for choropleth lines maps,
#' * **choro_symb** for choropleth on symbols maps
#' * **typo_line** for typology lines maps.
#' @param val
#' vector of value(s) (for "prop" and "prop_line", at least c(min, max)
#' for "cont"),
#' vector of categories (for "symb", "typo", "typo_line"),
#' break labels (for "choro", "choro_point", "choro_line", "choro_symb",
#' and "grad_line"),
#' histogram parameters (for "histo").
#' @param pos position of the legend. It can be one of 'topleft', 'top',
#' 'topright', 'right', 'bottomright', 'bottom','bottomleft',
#' 'left', 'interactive' or a vector of two coordinates
#' in map units (c(x, y)).
#' @param pal a color palette name or a vector of colors
#' @param alpha opacity, in the range \[0,1\]
#' @param inches size of the largest symbol (radius for circles, half width
#' for squares) in inches
#' @param val_max maximum value corresponding to the largest symbol
#' @param border symbol border color(s)
#' @param symbol type of symbols, 'circle' or 'square'
#' @param self_adjust if TRUE values are self-adjusted to keep min, max and
#' intermediate rounded values
#' @param title title of the legend
#' @param title_cex size of the legend title
#' @param val_cex size of the values in the legend
#' @param val_rnd number of decimal places of the values in
#' the legend
#' @param val_dec decimal separator
#' @param val_big thousands separator
#' @param frame if TRUE the legend is plotted within a frame
#' @param no_data if TRUE a "missing value" box is plotted
#' @param no_data_txt label for missing values
#' @param bg background color of the legend
#' @param fg foreground color of the legend
#' @param box_border border color of legend boxes
#' @param box_cex width and height size expansion for boxes, histogram
#' circles, squares or lines
#' @param mar plot margins
#' @param return_bbox return only bounding box of the legend.
#' No legend is plotted.
#' @param col color of the symbols (for "prop") or color of the lines (for
#' "prop_line" and "grad_line")
#' @param lwd width(s) of the symbols borders (for "prop", "symb",
#' "choro_point", "choro_symb"),
#' width of the largest line (for "prop_line"), line width (for "choro_line"
#' and "typo_line"), vector of line widths (for "grad_line")
#' @param size size of the legend; 2 means two times bigger
#' @param cex size(s) of the symbols
#' @param pch type(s) of the symbols (0:25)
#' @param col_na color for missing values
#' @param cex_na size of the symbols for missing values
#' @param pch_na type of the symbols for missing values
#' @param horiz if TRUE plot an horizontal legend
#' @param adj adjust the postion of the legend in x and y directions.
#' @param frame_border border color of the frame
#' @import graphics
#' @return No value is returned, a legend is displayed.
#' @export
#' @details
#' Some arguments are available for all types of legend: `val`, `pos`, `title`,
#' `title_cex`, `val_cex`, `frame`, `bg`, `fg`, `size`, `adj`, `alpha`,
#' `return_bbox` and `mar`).
#'
#'
#' Relevant arguments for each specific legend types:
#' * `leg(type = "prop", val, inches, val_max, symbol, col, lwd, border, val_rnd, val_big, val_dec, self_adjust, horiz)`
#' * `leg(type = "choro", val, pal, val_rnd, val_big, val_dec, col_na, no_data, no_data_txt, box_border, box_cex, horiz)`
#' * `leg(type = "cont", val, pal, val_rnd, val_big, val_dec, col_na, no_data, no_data_txt, box_border, box_cex, horiz)`
#' * `leg(type = "typo", val, pal, col_na, no_data, no_data_txt, box_border, box_cex)`
#' * `leg(type = "symb", val, pal, pch, cex, lwd, pch_na, cex_na, col_na, no_data, no_data_txt)`
#' * `leg(type = "prop_line", val, col, lwd, val_rnd, val_big, val_dec)`
#' * `leg(type = "grad_line", val, col, lwd, val_rnd, val_big, val_dec)`
#' * `leg(type = "histo", val, pal, box_border, val_rnd, val_big, val_dec)`
#' * `leg(type = "choro_point", val, pal, symbol, border, cex, val_rnd, val_big, val_dec, col_na, no_data, no_data_txt, horiz)`
#' * `leg(type = "choro_line", val, pal, lwd, val_rnd, val_big, val_dec, col_na, no_data, no_data_txt)`
#' * `leg(type = "choro_symb", val, pal, pch, lwd, val_rnd, val_big, val_dec, col_na, no_data, no_data_txt)`
#' * `leg(type = "typo_line", val, pal, lwd, col_na, no_data, no_data_txt, box_cex)`

#' @examples
#' # minimal example
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1), asp = 1)
#' box()
#' leg(type = "prop", val = c(10, 50, 100), pos = "topleft")
#' leg(type = "choro", val = c(10, 20, 30, 40, 50), pos = "bottomleft")
#' leg(type = "typo", val = c("A", "B", "C"), pos = "top")
#' leg(type = "symb", val = c("A", "B", "C"), pos = "topright")
#' leg(type = "prop_line", val = c(5, 50, 100), pos = "bottom", lwd = 20)
#' leg(
#'   type = "grad_line", val = c(1, 4, 10, 15), pos = "bottomright",
#'   lwd = c(1, 5, 10)
#' )
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1), asp = 1)
#' leg(type = "prop", val = c(10, 50, 100), pos = "topleft", horiz = TRUE)
#' leg(type = "choro", val = c(10, 20, 30, 40, 50), pos = "left", horiz = TRUE)
#' leg(
#'   type = "cont", val = c(10, 20, 30, 40, 50), pos = "bottomleft",
#'   horiz = TRUE
#' )
#' leg(type = "choro_point", val = c(10, 20, 30, 40, 50), pos = "top")
#' leg(
#'   type = "choro_point", val = c(10, 20, 30, 40, 50), pos = "right",
#'   horiz = TRUE
#' )
#' leg(
#'   type = "cont", val = c(10, 20, 30, 40, 50), pos = "topright",
#'   horiz = FALSE
#' )
#' set.seed(46)
#' leg(
#'   type = "histo", val = hist(rnorm(1000), breaks = 5, plot = FALSE),
#'   pos = "bottomright"
#' )
#' box()
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1), asp = 1)
#' leg(type = "choro_line", val = c(10, 20, 30, 40, 50), pos = "topleft")
#' leg(type = "typo_line", val = c("A", "B", "C"), pal = "Set 2", pos = "top")
#' leg(type = "choro_symb", val = c(10, 20, 30, 40, 50), pos = "topright")
#'
#'
#' # full example
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' leg(
#'   type = "prop",
#'   val = c(5, 100),
#'   pos = "top",
#'   inches = .4,
#'   symbol = "circle",
#'   col = "#940000",
#'   lwd = 1,
#'   border = "#9494ff",
#'   val_rnd = 1,
#'   self_adjust = TRUE,
#'   title = "Proportional Symbols",
#'   title_cex = 1,
#'   val_cex = .8,
#'   bg = "grey10",
#'   fg = "yellow",
#'   frame = TRUE
#' )
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' leg(
#'   type = "choro",
#'   alpha = 1,
#'   val = c(10, 20, 30, 40, 50),
#'   pos = "top",
#'   pal = c("#7F000D", "#B56C6F", "#DBBABB", "#F1F1F1"),
#'   val_rnd = 2,
#'   col_na = "grey",
#'   no_data = TRUE,
#'   no_data_txt = "No data",
#'   box_border = "cornsilk",
#'   box_cex = c(2, 1),
#'   title = "Choropleth (sequential)"
#' )
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' leg(
#'   type = "typo",
#'   val = c("A", "B", "C"),
#'   pos = "top",
#'   pal = "Dynamic",
#'   col_na = "grey",
#'   no_data = TRUE,
#'   no_data_txt = "No data",
#'   box_cex = c(1.2, 1),
#'   title = "Typology (categories)"
#' )
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' leg(
#'   type = "symb",
#'   val = c("A", "B", "C"),
#'   pos = "top",
#'   pch = 21:23,
#'   cex = c(4, 4, 2),
#'   pal = "Inferno",
#'   lwd = 2,
#'   border = "red",
#'   col_na = "grey",
#'   pch_na = 3,
#'   cex_na = 1,
#'   no_data = TRUE,
#'   no_data_txt = "No data",
#'   title = "Symbols"
#' )
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' leg(
#'   type = "cont",
#'   val = c(1, 2, 3, 4, 5),
#'   pos = "top",
#'   pal = "Inferno",
#'   alpha = .7,
#'   val_rnd = 2,
#'   horiz = TRUE,
#'   box_cex = c(2, 1),
#'   title = "Continuous"
#' )
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' leg(
#'   type = "prop_line",
#'   val = c(54, 505, 1025),
#'   pos = "top",
#'   lwd = 15,
#'   col = "green",
#'   val_rnd = -1,
#'   box_cex = c(2, .5),
#'   title = "Proportional Lines",
#'   bg = "black",
#'   fg = "white",
#'   frame = TRUE
#' )
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' leg(
#'   type = "grad_line",
#'   val = c(1.25, 4.07, 10.001, 15.071),
#'   pos = "top",
#'   lwd = c(1, 7, 15),
#'   col = "#C130ff",
#'   val_rnd = 3,
#'   box_cex = c(2, 1),
#'   title = "Graduated Lines"
#' )
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' set.seed(46)
#' x <- rnorm(10000) * 1000
#' val <- hist(x, breaks = quantile(x, 0:10 / 10), plot = FALSE)
#' leg(
#'   type = "histo",
#'   alpha = 1,
#'   val = val,
#'   pos = "top",
#'   pal = "Teal",
#'   val_rnd = 0,
#'   val_big = " ",
#'   box_border = "cornsilk",
#'   box_cex = c(1, 2),
#'   title = "Histogram"
#' )
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' leg(
#'   type = "choro_point",
#'   alpha = 1,
#'   val = c(10, 20, 30, 40, 50),
#'   pos = "top",
#'   pal = c("#7F000D", "#B56C6F", "#DBBABB", "#F1F1F1"),
#'   val_rnd = 2,
#'   col_na = "grey",
#'   no_data = TRUE,
#'   no_data_txt = "No data",
#'   border = "tomato4",
#'   cex = 1.5,
#'   title = "Choropleth (sequential)"
#' )
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' leg(
#'   type = "choro_line",
#'   alpha = 1,
#'   val = c(10, 20, 30, 40, 50),
#'   pos = "top",
#'   pal = c("#7F000D", "#B56C6F", "#DBBABB", "#F1F1F1"),
#'   val_rnd = 1,
#'   lwd = 4,
#'   col_na = "grey",
#'   no_data = TRUE,
#'   no_data_txt = "No data",
#'   title = "Choropleth (sequential)"
#' )
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' leg(
#'   type = "choro_symb",
#'   alpha = 1,
#'   val = c(10, 20, 30, 40, 50),
#'   pch = 23,
#'   pos = "top",
#'   pal = c("#7F000D", "#B56C6F", "#DBBABB", "#F1F1F1"),
#'   val_rnd = 1,
#'   lwd = 1,
#'   col_na = "grey",
#'   no_data = TRUE,
#'   no_data_txt = "No data",
#'   title = "Choropleth (sequential)"
#' )
#'
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1))
#' leg(
#'   type = "typo_line",
#'   val = c("A", "B", "C"),
#'   lwd = 3,
#'   pos = "top",
#'   pal = c("red", "yellow", "green"),
#'   col_na = "black",
#'   no_data = TRUE,
#'   no_data_txt = "No data",
#'   box_cex = c(2, 1),
#'   frame = TRUE,
#'   title = "Typology (categories)"
#' )
#'
#'
#' # Positions
#' plot.new()
#' plot.window(xlim = c(0, 1), ylim = c(0, 1), asp = 1)
#' leg(
#'   type = "prop", val = c(10, 60, 100), pos = "bottomleft", adj = c(0, 4),
#'   title = "adj = c(0, 4)", frame = TRUE
#' )
#' leg(
#'   type = "choro", val = c(10, 50, 100), pos = "bottomright",
#'   adj = c(0, 8), title = "adj = c(0, 8)", frame = TRUE
#' )
#' leg(
#'   type = "prop", val = c(10, 50, 100), pos = "topleft",
#'   adj = c(0, -8), title = "adj = c(0, -8)"
#' )
#' box()
#' mtext(
#'   text = "A text on 1 line", side = 1, adj = .01,
#'   line = -1, cex = 1
#' )
#' mtext(
#'   text = "A text\non 2 lines", side = 1, adj = .99,
#'   line = -1, cex = 1
#' )
#' mtext(
#'   text = "A large text on 1 line", side = 3, adj = .01,
#'   line = -2, cex = 2
#' )
leg <- function(type,
                val,
                pos = "left",
                pal = "Inferno",
                alpha = NULL,
                col = "tomato4",
                inches = .3,
                val_max = NULL,
                symbol = "circle",
                self_adjust = FALSE,
                lwd = 0.7,
                border = "#333333",
                pch = seq_along(val),
                cex = rep(1, length(val)),
                title = "Legend Title",
                title_cex = 0.8 * size,
                val_cex = 0.6 * size,
                val_rnd = 0,
                val_dec = ".",
                val_big = "",
                col_na = "white",
                cex_na = 1,
                pch_na = 4,
                no_data = FALSE,
                no_data_txt = "No Data",
                box_border = "#333333",
                box_cex = c(1, 1),
                horiz = FALSE,
                frame_border = fg,
                frame = FALSE,
                bg = "#f7f7f7",
                fg = "#333333",
                size = 1,
                return_bbox = FALSE,
                mar = par("mar"),
                adj = c(0, 0)) {
  # test pos and current plot
  if (any(is.na(pos))) {
    return(invisible(NULL))
  }

  leg_test_input(pos)

  # test at least 2 values
  if (any(startsWith(x = type, c("choro", "grad", "cont")))) {
    leg_test_nval(val)
  }

  op <- par(mar = mar, xpd = TRUE, no.readonly = TRUE)
  on.exit(par(op), add = TRUE)

  args <- as.list(match.call())
  args <- args[!names(args) %in% c("type", "horiz", "mar")]
  args <- args[-1]
  h <- ""
  if (horiz) {
    h <- "_h"
  }

  ffun <- get(paste0("leg_", type, h))

  if (length(pos) == 1 && pos == "interactive") {
    args$pos <- interleg()
  }

  if (!missing(val_dec)) {
    args$val_dec <- leg_test_val_dec(val_dec)
  }

  args <- lapply(args, eval, parent.frame(), parent.frame())
  x <- grDevices::recordGraphics(
    expr = do.call(what = ffun, args = args),
    list = list(args = args, ffun = ffun),
    env = getNamespace("maplegend")
  )
  return(invisible(x))
}
