#
# layered.R
#
# Simple mechanism for layered plotting
#
#  $Revision: 1.39 $  $Date: 2017/06/05 10:31:58 $
#

layered <- function(..., plotargs=NULL, LayerList=NULL) {
  argh <- list(...)
  if(length(argh) > 0 && !is.null(LayerList))
    stop("LayerList is incompatible with other arguments")
  out <- if(!is.null(LayerList)) LayerList else argh
  n <- length(out)
  if(sum(nzchar(names(out))) != n)
    names(out) <- paste("Layer", seq_len(n))
  if(is.null(plotargs)) {
    plotargs <- rep.int(list(list()), n)
  } else {
    if(!is.list(plotargs))
      stop("plotargs should be a list of lists")
    if(!all(unlist(lapply(plotargs, is.list))))
      plotargs <- list(plotargs)
    np <- length(plotargs)
    if(np == 1) plotargs <- rep(plotargs, n) else if(np != n)
      stop("plotargs should have one component for each element of the list")
  }
  names(plotargs) <- names(out)
  attr(out, "plotargs") <- plotargs
  class(out) <- c("layered", class(out))
  return(out)
}

print.layered <- function(x, ...) {
  splat("Layered object")
  if(length(x) == 0) splat("(no entries)")
  for(i in seq_along(x)) {
    cat(paste("\n", names(x)[i], ":\n", sep=""))
    print(x[[i]])
  }
  pl <- layerplotargs(x)
  hasplot <- (lengths(pl) > 0)
  if(any(hasplot)) 
    splat("Includes plot arguments for", commasep(names(pl)[hasplot]))
  invisible(NULL)
}

plot.layered <- function(x, ..., which=NULL, plotargs=NULL,
                         add=FALSE, show.all=!add, main=NULL,
                         do.plot=TRUE) {
  if(is.null(main))
    main <- short.deparse(substitute(x))
  n <- length(x)
  if(!is.null(plotargs)) {
    np <- length(plotargs)
    if(!(is.list(plotargs) && all(unlist(lapply(plotargs, is.list)))))
      stop("plotargs should be a list of lists")
  }
  ## select layers
  if(!is.null(which)) {
    x <- x[which]
    nw <- length(x)
    if(!is.null(plotargs)) {
      if(np == n) plotargs <- plotargs[which] else
      if(np == 1) plotargs <- rep(plotargs, nw) else
      if(np != nw) 
        stop("plotargs should have one component for each layer to be plotted")
    }
    n <- nw
  } else if(!is.null(plotargs)) {
    if(np == 1) plotargs <- rep(plotargs, n) else
    if(np != n) stop("plotargs should have one component for each layer")
  }
  ## remove null layers
  if(any(isnul <- unlist(lapply(x, is.null)))) {
    x <- x[!isnul]
    if(!is.null(plotargs))
      plotargs <- plotargs[!isnul]
    n <- length(x)
  }
  ## anything to plot?
  if(n == 0)
    return(invisible(NULL))
  ## Merge plotting arguments
  xplotargs <- layerplotargs(x)
  if(is.null(plotargs)) {
    plotargs <- xplotargs
  } else if(length(xplotargs) > 0) {
    for(i in 1:n)
      plotargs[[i]] <- resolve.defaults(plotargs[[i]], xplotargs[[i]])
  }
  ## Determine bounding box
  a <- plotEachLayer(x, ..., plotargs=plotargs, add=add,
                     show.all=show.all, do.plot=FALSE)
  if(!do.plot)
    return(a)
  bb <- as.rectangle(as.owin(a))
  ## Start plotting
  if(!add && !is.null(bb)) {
    ## initialise new plot using bounding box
    pt <- prepareTitle(main)
    plot(bb, type="n", main=pt$blank)
    add <- TRUE
  }
  # plot the layers
  out <- plotEachLayer(x, ..., main=main,
                       plotargs=plotargs, add=add,
                       show.all=show.all, do.plot=TRUE)
  return(invisible(out))
}

plotEachLayer <- function(x, ..., main,
                          plotargs, add, show.all, do.plot=TRUE) {
  main.given <- !missing(main)
  ## do.plot=TRUE    =>   plot the layers 
  ## do.plot=FALSE   =>   determine bounding boxes
  out <- boxes <- list()
  nama <- names(x)
  firstlayer <- TRUE
  for(i in seq_along(x)) {
    xi <- x[[i]]
    if(length(xi) == 0) {
      # null layer - no plotting
      out[[i]] <- boxes[[i]] <- NULL
    } else {
      ## plot layer i on top of previous layers if any.
      ## By default,
      ##    - show all graphic elements of the first component only;
      ##    - show title 'firstmain' on first component;
      ##    - do not show any component names.
      add.i <- add || !firstlayer
      if(main.given) {
        main.i <- if(firstlayer) main else ""
      } else {
        show.all.i <- resolve.1.default(list(show.all=FALSE),
                                         list(...), 
                                         plotargs[[i]])
        main.i <- if(show.all.i) nama[i] else ""
      }
      dflt <- list(main=main.i,
                   show.all=show.all && firstlayer)
      pla.i <- plotargs[[i]]
      defaultplot <- !(".plot" %in% names(pla.i))
      ## plot layer i, or just determine bounding box
      if(defaultplot &&
         inherits(xi, c("ppp", "psp", "owin",
                        "lpp", "linnet", 
                        "im", "msr", "layered"))) {
        ## plot method for 'xi' has argument 'do.plot'.
        mplf <-
          if(inherits(xi, c("ppp", "lpp"))) list(multiplot=FALSE) else list()
        out[[i]] <- outi <- do.call(plot,
                                    resolve.defaults(list(x=xi,
                                                          add=add.i,
                                                          do.plot=do.plot),
                                                     list(...),
                                                     mplf,
                                                     pla.i,
                                                     dflt))
        boxes[[i]] <- as.rectangle(as.owin(outi))
      } else {
        ## plot method for 'xi' does not have argument 'do.plot'
        if(do.plot) {
          if(defaultplot) {
            plotfun <- "plot"
          } else {
            plotfun <- pla.i[[".plot"]]
            pla.i <- pla.i[names(pla.i) != ".plot"]
          }
          out[[i]] <- outi <- do.call(plotfun,
                                      resolve.defaults(list(x=xi,
                                                            add=add.i),
                                                       list(...),
                                                       pla.i,
                                                       dflt))
        }
        ## convert layer i to box
        boxi <- try(as.rectangle(xi), silent=TRUE)
        boxes[[i]] <- if(!inherits(boxi, "try-error")) boxi else NULL
      }
      firstlayer <- FALSE
    }
  }
  ## one box to bound them all
  if(!all(unlist(lapply(boxes, is.null))))
    attr(out, "bbox") <- do.call(boundingbox, boxes)
  return(out)
}


"[.layered" <- function(x, i, j, drop=FALSE, ...) {
  i.given <- !missing(i) && !is.null(i)
  j.given <- !missing(j) && !is.null(j)
  if(!i.given && !j.given)
    return(x)
  p <- attr(x, "plotargs")
  x <- unclass(x)
  nx <- length(x)
  if(i.given) {
    if(is.owin(i)) {
      #' spatial window subset
      nonemp <- (lengths(x) != 0)
      x[nonemp] <- lapply(x[nonemp], "[", i=i, ...)
    } else {
      #' vector subset index
      x <- x[i]
      p <- p[i]
      nx <- length(x)
    }
  }
  if(j.given) {
    nonemp <- (lengths(x) != 0)
    x[nonemp] <- lapply(x[nonemp], "[", i=j, ...)
  }
  if(drop && nx == 1)
    return(x[[1L]])
  y <- layered(LayerList=x, plotargs=p)
  return(y)
}

"[[<-.layered" <- function(x, i, value) {
  x[i] <- if(!is.null(value)) list(value) else NULL
  return(x)
}

"[<-.layered" <- function(x, i, value) {
  p <- layerplotargs(x)
  ## invoke list method
  y <- x
  class(y) <- "list"
  y[i] <- value
  # make it a 'layered' object too
  class(y) <- c("layered", class(y))
  # update names and plotargs
  if(any(blank <- !nzchar(names(y)))) {
    names(y)[blank] <- paste("Layer", which(blank))
    pnew <- rep(list(list()), length(y))
    names(pnew) <- names(y)
    m <- match(names(y), names(x))
    mok <- !is.na(m)
    pnew[mok] <- p[m[mok]]
    layerplotargs(y) <- pnew
  } else layerplotargs(y) <- layerplotargs(x)[names(y)]
  return(y)
}

layerplotargs <- function(L) {
  stopifnot(inherits(L, "layered"))
  attr(L, "plotargs")
}

"layerplotargs<-" <- function(L, value) {
  if(!inherits(L, "layered"))
    L <- layered(L)
  if(!is.list(value))
    stop("Replacement value should be a list, or a list-of-lists")
  n <- length(L)
  if(!all(unlist(lapply(value, is.list)))) 
    value <- unname(rep(list(value), n))
  if(length(value) != n) {
    if(length(value) == 1) value <- unname(rep(value, n)) else
    stop("Replacement value is wrong length")
  }
  if(is.null(names(value))) names(value) <- names(L) else
  if(!identical(names(value), names(L)))
    stop("Mismatch in names of list elements")
  attr(L, "plotargs") <- value
  return(L)
}

applytolayers <- function(L, FUN, ...) {
  # Apply FUN to each **non-null** layer,
  # preserving the plot arguments
  pla <- layerplotargs(L)
  if(length(L) > 0) {
    ok <- !unlist(lapply(L, is.null))
    L[ok] <- lapply(L[ok], FUN, ...)
  }
  Z <- layered(LayerList=L, plotargs=pla)
  return(Z)
}
  
shift.layered <- function(X, vec=c(0,0), ...) {
  if(length(list(...)) > 0) {
    if(!missing(vec)) 
      warning("Argument vec ignored; overridden by other arguments")
    ## ensure the same shift is applied to all layers
    s <- shift(X[[1L]], ...)
    vec <- getlastshift(s)
  }
  Y <- applytolayers(X, shift, vec=vec)
  attr(Y, "lastshift") <- vec
  return(Y)
}

affine.layered <- function(X, ...) {
  applytolayers(X, affine, ...)
}

rotate.layered <- function(X, ..., centre=NULL) {
  if(!is.null(centre)) {
    X <- shift(X, origin=centre)
    negorigin <- getlastshift(X)
  } else negorigin <- NULL
  Y <- applytolayers(X, rotate, ...)
  if(!is.null(negorigin))
    Y <- shift(Y, -negorigin)
  return(Y)
}

reflect.layered <- function(X) {
  applytolayers(X, reflect)
}

flipxy.layered <- function(X) {
  applytolayers(X, flipxy)
}

scalardilate.layered <- function(X, ...) {
  applytolayers(X, scalardilate, ...)
}
  
rescale.layered <- function(X, s, unitname) {
  if(missing(s)) s <- NULL
  if(missing(unitname)) unitname <- NULL
  applytolayers(X, rescale, s=s, unitname=unitname) 
}


as.owin.layered <- local({

  as.owin.layered <- function(W, ..., fatal=TRUE) {
    if(length(W) == 0) {
      if(fatal) stop("Layered object is empty: no window data")
      return(NULL)
    }
    ## remove null layers
    isnul <- unlist(lapply(W, is.null))
    W <- W[!isnul]
    if(length(W) == 0) {
      if(fatal) stop("Layered object has no window data")
      return(NULL)
    }
    Wlist <- lapply(unname(W), as.owin, ..., fatal=fatal)
    Wlist <- lapply(Wlist, rescue.rectangle)
    Wlist <- lapply(Wlist, puffbox)
    Z <- Wlist[[1L]]
    if(length(Wlist) > 1) {
      same <- unlist(lapply(Wlist[-1L], identical, y=Z))
      if(!all(same))
        Z <- do.call(union.owin, Wlist)
    }
    return(Z)
  }

  puffbox <- function(W) {
    ## union.owin will delete boxes that have width zero or height zero
    ## so 'puff' them out slightly
    ss <- sidelengths(Frame(W))
    if(ss[1L] == 0) W$xrange <- W$xrange + 1e-6 * c(-1,1) * ss[2L]
    if(ss[2L] == 0) W$yrange <- W$yrange + 1e-6 * c(-1,1) * ss[1L]
    return(W)
  }
  
  as.owin.layered
})


domain.layered <- Window.layered <- function(X, ...) { as.owin(X) }

as.layered <- function(X) {
  UseMethod("as.layered")
}

as.layered.default <- function(X) {
  if(is.list(X) && all(sapply(X, is.sob))) layered(LayerList=X) else 
  layered(X)
}

as.layered.ppp <- function(X) {
  if(!is.marked(X)) return(layered(X))
  if(is.multitype(X)) return(layered(LayerList=split(X)))
  mX <- marks(X)
  if(!is.null(d <- dim(mX)) && d[2L] > 1) {
    mx <- as.data.frame(marks(X))
    Y <- lapply(mx, setmarks, x=X)
    return(layered(LayerList=Y))
  }
  return(layered(X))
}


  
