#  File src/library/grid/R/gpar.R
#  Part of the R package, https://www.R-project.org
#
#  Copyright (C) 1995-2020 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/


# A "gpar" object is a list of graphics parameters
# A graphics parameter is a name-value pair

gpar <- function(...) {
  gp <- validGP(list(...))
  class(gp) <- "gpar"
  gp
}

is.gpar <- function(x) {
  inherits(x, "gpar")
}

print.gpar <- function(x, ...) {
  print(unclass(x), ...)
  invisible(x)
}

validGP <- function(gpars) {
  # Check a (non-NULL) gpar is not of length 0
  check.length <- function(gparname) {
    if (length(gpars[[gparname]]) == 0)
      stop(gettextf("'gpar' element '%s' must not be length 0", gparname),
           domain = NA)
  }
  # Check a gpar is numeric and not NULL
  numnotnull <- function(gparname) {
    if (!is.na(match(gparname, names(gpars)))) {
      if (is.null(gpars[[gparname]]))
        gpars[[gparname]] <<- NULL
      else {
        check.length(gparname)
        gpars[[gparname]] <<- as.numeric(gpars[[gparname]])
      }
    }
  }
  checkNA <- function(gparname) {
      if (!is.na(match(gparname, names(gpars)))) {
          if (any(is.na(gpars[[gparname]]))) {
              # ALL NA gets removed (ignored)
              if (all(is.na(gpars[[gparname]]))) {
                  gpars[[gparname]] <<- NULL
              } else {
                  stop(gettextf("mixture of missing and non-missing values for %s",
                                gparname),
                       domain=NA)
              }
          }
      }
  }
  # fontsize, lineheight, cex, lwd should be numeric and not NULL
  numnotnull("fontsize")
  checkNA("fontsize")
  numnotnull("lineheight")
  checkNA("lineheight")
  numnotnull("cex")
  checkNA("cex")
  numnotnull("lwd")
  numnotnull("lex")
  # gamma defunct in 2.7.0
  if ("gamma" %in% names(gpars)) {
    warning("'gamma' 'gpar' element is defunct")
    gpars$gamma <- NULL
  }
  numnotnull("alpha")
  # col and fill are converted in C code
  # BUT still want to check length > 0
  if (!is.na(match("col", names(gpars)))) {
      if (is.null(gpars$col))
          gpars$col <- NULL
      else
          check.length("col")
  }
  if (!is.na(match("fill", names(gpars)))) {
      if (is.null(gpars$fill)) {
          gpars$fill <- NULL
      } else {
          ## fill can be a simple colour (NA, integer, string)
          ## OR a "GridPattern" OR a list of "GridPattern"s
          if (!is.pattern(gpars$fill)) {
              if (is.list(gpars$fill)) {
                  if (!all(sapply(gpars$fill, is.pattern)))
                      stop("'fill' gpar list components must all be patterns")
                  class(gpars$fill) <- "GridPatternList"
              }
              check.length("fill")
          }
      }
  }
  # lty converted in C code
  # BUT still want to check for NULL and check length > 0
  if (!is.na(match("lty", names(gpars)))) {
    if (is.null(gpars$lty))
      gpars$lty <- NULL
    else
      check.length("lty")
  }
  if (!is.na(match("lineend", names(gpars)))) {
    if (is.null(gpars$lineend))
      gpars$lineend <- NULL
    else
      check.length("lineend")
  }
  if (!is.na(match("linejoin", names(gpars)))) {
    if (is.null(gpars$linejoin))
      gpars$linejoin <- NULL
    else
      check.length("linejoin")
  }
  # linemitre should be larger than 1
  numnotnull("linemitre")
  if (!is.na(match("linemitre", names(gpars)))) {
    if (any(gpars$linemitre < 1))
      stop("invalid 'linemitre' value")
  }
  # alpha should be 0 to 1
  if (!is.na(match("alpha", names(gpars)))) {
    if (any(gpars$alpha < 0) || any(gpars$alpha > 1))
      stop("invalid 'alpha' value")
  }
  # font should be integer and not NULL
  if (!is.na(match("font", names(gpars)))) {
    if (is.null(gpars$font))
      gpars$font <- NULL
    else {
      check.length("font")
      gpars$font <- as.integer(gpars$font)
    }
  }
  # fontfamily should be character
  if (!is.na(match("fontfamily", names(gpars)))) {
    if (is.null(gpars$fontfamily))
      gpars$fontfamily <- NULL
    else {
      check.length("fontfamily")
      gpars$fontfamily <- as.character(gpars$fontfamily)
      checkNA("fontfamily")
    }
  }
  # fontface can be character or integer;  map character to integer
  # store value in font
  # Illegal to specify both font and fontface
  if (!is.na(match("fontface", names(gpars)))) {
    if (!is.na(match("font", names(gpars))))
      stop("must specify only one of 'font' and 'fontface'")
    gpars$font <-
	if (is.null(gpars$fontface)) NULL # remove it
	else {
	    check.length("fontface")
	    if (is.numeric(gpars$fontface))
		as.integer(gpars$fontface)
	    else
		vapply(as.character(gpars$fontface),
		       function(ch) # returns integer
		       switch(ch,
			      plain = 1L,
			      bold  = 2L,
			      italic=, oblique = 3L,
			      bold.italic = 4L,
			      symbol= 5L,
					# These are Hershey variants
			      cyrillic=5L,
			      cyrillic.oblique=6L,
			      EUC   = 7L,
			      stop("invalid fontface ", ch)), 0L)
	}
    ## Remove fontface
    gpars$fontface <- NULL
  }
  gpars
}

# Method for subsetting "gpar" objects
`[.gpar` <- function(x, index, ...) {
    if (length(x) == 0)
        return(gpar())
    maxn <- do.call("max", lapply(x, length))
    newgp <- lapply(x, rep, length.out=maxn)
    newgp <- lapply(X = newgp, FUN = `[`, index, ...)
    class(newgp) <- "gpar"
    newgp
}

# possible gpar names
# The order must match the GP_* values in grid.h
.grid.gpar.names <- c("fill", "col", "gamma", "lty", "lwd", "cex",
                      "fontsize", "lineheight", "font", "fontfamily",
                      "alpha", "lineend", "linejoin", "linemitre",
                      "lex", "gradientFill",
                      # Keep fontface at the end because it is never
                      # used in C code (it gets mapped to font)
                      "fontface")

set.gpar <- function(gp, grob=NULL) {
  if (!is.gpar(gp))
    stop("argument must be a 'gpar' object")
  gp <- validGP(gp)
  temp <- grid.Call(C_getGPar)
  # gamma defunct in 2.7.0
  if ("gamma" %in% names(gp)) {
      warning("'gamma' 'gpar' element is defunct")
      gp$gamma <- NULL
  }
  # Special case "cex" (make it cumulative)
  if (match("cex", names(gp), nomatch=0L))
    tempcex <- temp$cex * gp$cex
  else
    tempcex <- temp$cex
  # Special case "alpha" (make it cumulative)
  if (match("alpha", names(gp), nomatch=0L))
    tempalpha <- temp$alpha * gp$alpha
  else
    tempalpha <- temp$alpha
  # Special case "lex" (make it cumulative)
  if (match("lex", names(gp), nomatch=0L))
    templex <- temp$lex * gp$lex
  else
    templex <- temp$lex
  ## resolve fill - could be a simple colour OR a "GridPattern"
  if (is.pattern(gp$fill)) {
      if (is.null(grob)) {
          class(gp$fill) <- c("GridViewportPattern", class(gp$fill))
      } else {
          if (inherits(grob, "gTree")) {
              class(gp$fill) <- c("GridGTreePattern", class(gp$fill))
          } else {
              class(gp$fill) <- c("GridGrobPattern", class(gp$fill))
          }
      }
  } else if (is.list(gp$fill)) {
      if (is.null(grob)) {
          ## Silently use just first pattern
          gp$fill <- gp$fill[[1]]
          class(gp$fill) <- c("GridViewportPatternList", class(gp$fill))
      } else {
          if (inherits(grob, "gTree")) {
              class(gp$fill) <- c("GridGTreePatternList", class(gp$fill))
          } else {
              class(gp$fill) <- c("GridGrobPatternList", class(gp$fill))
          }
      }      
  }
  # All other gpars
  temp[names(gp)] <- gp
  temp$cex <- tempcex
  temp$alpha <- tempalpha
  temp$lex <- templex
  if (is.null(grob)) {
      ## Do this as a .Call.graphics to get it onto the base display list
      grid.Call.graphics(C_setGPar, temp)
  } else {
      grid.Call(C_setGPar, temp)
  }
}

get.gpar <- function(names=NULL) {
  if (is.null(names)) {
    result <- grid.Call(C_getGPar)
    # drop gamma
    result$gamma <- NULL
  } else {
    if (!is.character(names) ||
        !all(names %in% .grid.gpar.names))
      stop("must specify only valid 'gpar' names")
    # gamma deprecated
    if ("gamma" %in% names) {
      warning("'gamma' 'gpar' element is defunct")
      names <- names[-match("gamma", names)]
    }
    result <- unclass(grid.Call(C_getGPar))[names]
  }
  class(result) <- "gpar"
  result
}

# When editing a gp slot, only update the specified gpars
# Assume gp is NULL or a gpar
# assume newgp is a gpar (and not NULL)
mod.gpar <- function(gp, newgp) {
  if (is.null(gp))
    gp <- newgp
  else
    gp[names(newgp)] <- newgp
  gp
}

