#' Cartesian product of many vectors and matrices
#'
#' @param ... vectors, factors or matrices \cr
#' Or  ... can be a list containing the levels vectors as its elements.
#' @param rev = 0 to make the fist elements of ... change first
#' @param as.data.frame = 1 to make the result a data frame.
#' @param rownames = 1 to return the compressed version as the rownames.
#' @param colnames = 1 to use the names of ... as the colnames.
#'
#' @return A matrix or data frame.
#'
#' @details When all the inputs are vectors, \code{cprod} is equivalent to
#' \code{expand.grid1}.\cr
#' 
#' When both \code{A} and \code{B} are matrices, 
#' \code{cprod( A, B )} is equivalent to
#' \preformatted{
#'   Reduce( rbind, lapply(as.data.frame(t(A))
#'        ,  function(x) cbind(repmat(matrix(x,1,ncol(A)),nrow(B)),B)) )
#' }
#'
#' @examples
#' cprod(1:2,3:5)
#' cprod(1:2,3:4, rev=0)
#' cprod(matrix(1:6,3,2, byrow=1),1:2)
#' cprod(matrix(1:6,3,2, byrow=1),matrix(1:4,2,2,byrow=1))
#'
#' # when ... is a list
#' ll <- list(a=c("a","b"),bbb=1:3)
#' cprod(ll, colnames=0)
#' cprod(ll, colnames=1)
#'
#' # ANOVA data
#' levels <- c(2,3)
#' ll <- lapply(levels,function(x)seq(1:x))
#' names(ll) <- c("A","B")
#' data <- cprod(ll, colnames=1)
#'
#' # generate 3D grid of x0
#' x0 <- 1:3
#' ndim <- 3
#' x <- cprod( rep(list(x0),ndim) )
#' Print(x)
#'
#' @export
#'

cprod <- function( ..., rev=1, as.data.frame=0, rownames=0, colnames=0 ){
 # cartesian product of many vectors
 # Shin-ichi Mayekawa
 # 130211
 # old version (111121,27,130110) is obsolete.
 # remove NULL elements from ...: 130219
 # comments: 20140221
 # matrix input: 20140222,23
 # bugfix: 20140815 dnc
 # bugfix: 20150626,0712c
 # bugfix for cprod(1,1): 20150729
 # list input: 20150729
 # name=name<- fixed: 20150803
 # arg type removed: 20150804
 # character mat: 20200723
 #
 # Args:
 #  x1, x2, ..., xq  vectors of length n1, n2, ..., nq
 #
 #  rev              = 0 if x1 varies first
 #  rownames         = 1 to return the compressed version as the rownames
 #  colnames         = 1 to use names of ... as the column names
 #  as.data.frame    = 1 to make the result a data frame
 #
 # Value:
 #  If several vectos are given:
 #   (n1 x n2 x ... x nq) x q  matrix of the cartesian products
 #    where x1 varies the fastest when rev=F.
 #

 name <- as.character(substitute(list(...)))[-1L]
 val=list(...)
 locnull=which(unlist(lapply(val,is.null)))
 # Print(name,val,locnull)
 if( length(locnull) > 0 ){
  name=name[-locnull]
  val=val[-locnull]
 }
 if( is.list(val[[1]]) ){
  name=names(val[[1]])
  # if( colnames == 0 ) name=paste("Var",1:length(name), sep="")
  val=val[[1]]
  names(val)=NULL
 }
 nval=length(val)

 # Print(name, val, nval)

 # mark matrix elements of ... and replace them by index vectors
 # mat has non null dimensions which do not contain 1
 locmat0=!unlist(lapply(lapply(val,dim),function(a) any(a==1) | is.null(a)))
 locmat=which(locmat0)
 nmat=length(locmat)

 matlist=list(); ncollist=0; colnameslist=list()
 if( nmat > 0 ){
  matlist=val[locmat]
  val[locmat]=lapply(matlist,function(a) seq(1:nrow(a)))
  ncollist=unlist(lapply(matlist,ncol))
  colnameslist=lapply(matlist,colnames)
 }

 #Print(matlist,val,name)

 # Here val contains vectors
 if( rev != 1 ){
  res=expand.grid( val )
  if( !as.data.frame )
   res=as.matrix(res,nrow(res))
 }
 else{
  res=t( apply( expand.grid( rev(val) ), 1, rev ) ) # This is a matrix.
  colnames(res)=rev(colnames(res))

  #  if( nrow(res) == 1 ){
  #   res=t(res); colnames(res)="Var1"
  #  } # 20150729
  if( as.data.frame )
   res=data.frame(res)
 }

 # if( rev ) colnames(res)=rev(colnames(res))
 if( colnames == 1 ){
  # if( rev ) name=rev(name)
  colnames(res)=name
 }

 rownames(res)=1:nrow(res)
 if ( rownames == -1 )
  rownames(res)=NULL
 else if( rownames == 1 )
  rownames(res)=apply(res,1,function(a){paste(a,collapse=":")})

 # restore matrix elements
 if( nmat > 0 ){
  res1=as.data.frame( matrix(0,nrow(res),ncol(res)+sum(ncollist)-nmat) )
  nextcol=1; nextmat=1
  for( j in 1:nval ){
   if( locmat0[j] ){
    res1[,nextcol:(nextcol+ncollist[nextmat]-1)]=
     matlist[[nextmat]][as.integer(res[,j]),]
    nextcol=nextcol+ncollist[nextmat]
    nextmat=nextmat+1
   }
   else{
    res1[,nextcol]=res[,j]
    nextcol=nextcol+1
   }
  }
  res=res1
 }

 if( all(sapply(res,mode) == "numeric") ||
     all(sapply(res,mode) == "character") ) res=as.matrix(res) # 20200723

 return( res )

} # end of cprod


