

#' @name write.xlsx
#' @title write data to an xlsx file
#' @description write a data.frame or list of data.frames to an xlsx file
#' @author Alexander Walker
#' @param x object or a list of objects that can be handled by \code{\link{writeData}} to write to file
#' @param file xlsx file name
#' @param asTable write using writeDataTable as opposed to writeData
#' @param ... optional parameters to pass to functions:
#' \itemize{
#'   \item{createWorkbook}
#'   \item{addWorksheet}
#'   \item{writeData}
#'   \item{freezePane}
#'   \item{saveWorkbook}
#' }
#'
#' see details.
#' @details Optional parameters are:
#'
#' \bold{createWorkbook Parameters}
#' \itemize{
#'   \item{\bold{creator}}{ A string specifying the workbook author}
#' }
#'
#' \bold{addWorksheet Parameters}
#' \itemize{
#'   \item{\bold{sheetName}}{ Name of the worksheet}
#'   \item{\bold{gridLines}}{ A logical. If \code{FALSE}, the worksheet grid lines will be hidden.}
#'   \item{\bold{tabColour}}{ Colour of the worksheet tab. A valid colour (belonging to colours())
#'   or a valid hex colour beginning with "#".}
#'   \item{\bold{zoom}}{ A numeric between 10 and 400. Worksheet zoom level as a percentage.}
#' }
#'
#' \bold{writeData/writeDataTable Parameters}
#' \itemize{
#'   \item{\bold{startCol}}{ A vector specifying the starting column(s) to write df}
#'   \item{\bold{startRow}}{ A vector specifying the starting row(s) to write df}
#'   \item{\bold{xy}}{ An alternative to specifying startCol and startRow individually.
#'  A vector of the form c(startCol, startRow)}
#'   \item{\bold{colNames or col.names}}{ If \code{TRUE}, column names of x are written.}
#'   \item{\bold{rowNames or row.names}}{ If \code{TRUE}, row names of x are written.}
#'   \item{\bold{headerStyle}}{ Custom style to apply to column names.}
#'   \item{\bold{borders}}{ Either "surrounding", "columns" or "rows" or NULL.  If "surrounding", a border is drawn around the
#' data.  If "rows", a surrounding border is drawn a border around each row. If "columns", a surrounding border is drawn with a border
#' between each column.  If "\code{all}" all cell borders are drawn.}
#'   \item{\bold{borderColour}}{ Colour of cell border}
#'   \item{\bold{borderStyle}}{ Border line style.}
#'   \item{\bold{keepNA}} {If \code{TRUE}, NA values are converted to #N/A (or \code{na.string}, if not NULL) in Excel, else NA cells will be empty. Defaults to FALSE.}
#'   \item{\bold{na.string}} {If not NULL, and if \code{keepNA} is \code{TRUE}, NA values are converted to this string in Excel. Defaults to NULL.}
#' }
#'
#' \bold{freezePane Parameters}
#' \itemize{
#'   \item{\bold{firstActiveRow}} {Top row of active region to freeze pane.}
#'   \item{\bold{firstActiveCol}} {Furthest left column of active region to freeze pane.}
#'   \item{\bold{firstRow}} {If \code{TRUE}, freezes the first row (equivalent to firstActiveRow = 2)}
#'   \item{\bold{firstCol}} {If \code{TRUE}, freezes the first column (equivalent to firstActiveCol = 2)}
#' }
#'
#' \bold{colWidths Parameters}
#' \itemize{
#'   \item{\bold{colWidths}} {Must be value "auto". Sets all columns containing data to auto width.}
#' }
#'
#'
#' \bold{saveWorkbook Parameters}
#' \itemize{
#'   \item{\bold{overwrite}}{ Overwrite existing file (Defaults to TRUE as with write.table)}
#' }
#'
#'
#' columns of x with class Date or POSIXt are automatically
#' styled as dates and datetimes respectively.
#' @seealso \code{\link{addWorksheet}}
#' @seealso \code{\link{writeData}}
#' @seealso \code{\link{createStyle}} for style parameters
#' @return A workbook object
#' @examples
#'
#' ## write to working directory
#' options("openxlsx.borderColour" = "#4F80BD") ## set default border colour
#' \dontrun{
#' write.xlsx(iris, file = "writeXLSX1.xlsx", colNames = TRUE, borders = "columns")
#' write.xlsx(iris, file = "writeXLSX2.xlsx", colNames = TRUE, borders = "surrounding")
#' }
#'
#'
#' hs <- createStyle(
#'   textDecoration = "BOLD", fontColour = "#FFFFFF", fontSize = 12,
#'   fontName = "Arial Narrow", fgFill = "#4F80BD"
#' )
#' \dontrun{
#' write.xlsx(iris,
#'   file = "writeXLSX3.xlsx",
#'   colNames = TRUE, borders = "rows", headerStyle = hs
#' )
#' }
#'
#' ## Lists elements are written to individual worksheets, using list names as sheet names if available
#' l <- list("IRIS" = iris, "MTCATS" = mtcars, matrix(runif(1000), ncol = 5))
#' \dontrun{
#' write.xlsx(l, "writeList1.xlsx", colWidths = c(NA, "auto", "auto"))
#' }
#'
#' ## different sheets can be given different parameters
#' \dontrun{
#' write.xlsx(l, "writeList2.xlsx",
#'   startCol = c(1, 2, 3), startRow = 2,
#'   asTable = c(TRUE, TRUE, FALSE), withFilter = c(TRUE, FALSE, FALSE)
#' )
#' }
#'
#' @export
write.xlsx <- function(x, file, asTable = FALSE, ...) {


  ## set scientific notation penalty

  params <- list(...)

  ## Possible parameters

  #---createWorkbook---#
  ## creator

  #---addWorksheet---#
  ## sheetName
  ## gridLines
  ## tabColour = NULL
  ## zoom = 100
  ## header = NULL
  ## footer = NULL
  ## evenHeader = NULL
  ## evenFooter = NULL
  ## firstHeader = NULL
  ## firstFooter = NULL

  #---writeData---#
  ## startCol = 1,
  ## startRow = 1,
  ## xy = NULL,
  ## colNames = TRUE,
  ## rowNames = FALSE,
  ## headerStyle = NULL,
  ## borders = NULL,
  ## borderColour = "#4F81BD"
  ## borderStyle
  ## keepNA = FALSE
  ## na.string = NULL

  #----writeDataTable---#
  ## startCol = 1
  ## startRow = 1
  ## xy = NULL
  ## colNames = TRUE
  ## rowNames = FALSE
  ## tableStyle = "TableStyleLight9"
  ## tableName = NULL
  ## headerStyle = NULL
  ## withFilter = TRUE

  #---freezePane---#
  ## firstActiveRow = NULL
  ## firstActiveCol = NULL
  ## firstRow = FALSE
  ## firstCol = FALSE


  #---saveWorkbook---#
  #   overwrite = TRUE

  if (!is.logical(asTable)) {
    stop("asTable must be a logical.")
  }

  creator <- ifelse("creator" %in% names(params), params$creator, "")
  title <- params$title ### will return NULL of not exist
  subject <- params$subject ### will return NULL of not exist
  category <- params$category ### will return NULL of not exist


  sheetName <- "Sheet 1"
  if ("sheetName" %in% names(params)) {
    if (any(nchar(params$sheetName) > 31)) {
      stop("sheetName too long! Max length is 31 characters.")
    }

    sheetName <- as.character(params$sheetName)

    if ("list" %in% class(x) & length(sheetName) == length(x)) {
      names(x) <- sheetName
    }
  }

  tabColour <- NULL
  if ("tabColour" %in% names(params)) {
    tabColour <- validateColour(params$tabColour, "Invalid tabColour!")
  }

  zoom <- 100
  if ("zoom" %in% names(params)) {
    if (is.numeric(params$zoom)) {
      zoom <- params$zoom
    } else {
      stop("zoom must be numeric")
    }
  }

  ## AddWorksheet
  gridLines <- TRUE
  if ("gridLines" %in% names(params)) {
    if (all(is.logical(params$gridLines))) {
      gridLines <- params$gridLines
    } else {
      stop("Argument gridLines must be TRUE or FALSE")
    }
  }

  overwrite <- TRUE
  if ("overwrite" %in% names(params)) {
    if (is.logical(params$overwrite)) {
      overwrite <- params$overwrite
    } else {
      stop("Argument overwrite must be TRUE or FALSE")
    }
  }


  withFilter <- TRUE
  if ("withFilter" %in% names(params)) {
    if (is.logical(params$withFilter)) {
      withFilter <- params$withFilter
    } else {
      stop("Argument withFilter must be TRUE or FALSE")
    }
  }

  startRow <- 1
  if ("startRow" %in% names(params)) {
    if (all(startRow > 0)) {
      startRow <- params$startRow
    } else {
      stop("startRow must be a positive integer")
    }
  }

  startCol <- 1
  if ("startCol" %in% names(params)) {
    if (all(startCol > 0)) {
      startCol <- params$startCol
    } else {
      stop("startCol must be a positive integer")
    }
  }

  colNames <- TRUE
  if ("colNames" %in% names(params)) {
    if (is.logical(params$colNames)) {
      colNames <- params$colNames
    } else {
      stop("Argument colNames must be TRUE or FALSE")
    }
  }

  ## to be consistent with write.csv
  if ("col.names" %in% names(params)) {
    if (is.logical(params$col.names)) {
      colNames <- params$col.names
    } else {
      stop("Argument col.names must be TRUE or FALSE")
    }
  }


  rowNames <- FALSE
  if ("rowNames" %in% names(params)) {
    if (is.logical(params$rowNames)) {
      rowNames <- params$rowNames
    } else {
      stop("Argument colNames must be TRUE or FALSE")
    }
  }

  ## to be consistent with write.csv
  if ("row.names" %in% names(params)) {
    if (is.logical(params$row.names)) {
      rowNames <- params$row.names
    } else {
      stop("Argument row.names must be TRUE or FALSE")
    }
  }

  xy <- NULL
  if ("xy" %in% names(params)) {
    if (length(params$xy) != 2) {
      stop("xy parameter must have length 2")
    }
    xy <- params$xy
  }


  headerStyle <- NULL
  if ("headerStyle" %in% names(params)) {
    if (length(params$headerStyle) == 1) {
      if ("Style" %in% class(params$headerStyle)) {
        headerStyle <- params$headerStyle
      } else {
        stop("headerStyle must be a style object.")
      }
    } else {
      if (all(sapply(params$headerStyle, function(x) "Style" %in% class(x)))) {
        headerStyle <- params$headerStyle
      } else {
        stop("headerStyle must be a style object.")
      }
    }
  }

  borders <- NULL
  if ("borders" %in% names(params)) {
    borders <- tolower(params$borders)
    if (!all(borders %in% c("surrounding", "rows", "columns", "all"))) {
      stop("Invalid borders argument")
    }
  }

  borderColour <- getOption("openxlsx.borderColour", "black")
  if ("borderColour" %in% names(params)) {
    borderColour <- params$borderColour
  }

  borderStyle <- getOption("openxlsx.borderStyle", "thin")
  if ("borderStyle" %in% names(params)) {
    borderStyle <- validateBorderStyle(params$borderStyle)
  }

  keepNA <- FALSE
  if ("keepNA" %in% names(params)) {
    if (!"logical" %in% class(keepNA)) {
      stop("keepNA must be a logical.")
    } else {
      keepNA <- params$keepNA
    }
  }

  na.string <- NULL
  if ("na.string" %in% names(params)) {
    na.string <- as.character(params$na.string)
  }


  tableStyle <- "TableStyleLight9"
  if ("tableStyle" %in% names(params)) {
    tableStyle <- params$tableStyle
  }


  ## auto column widths
  colWidths <- ""
  if ("colWidths" %in% names(params)) {
    colWidths <- params$colWidths
  }


  ## create new Workbook object
  wb <- createWorkbook(creator = creator, title = title, subject = subject, category = category)


  ## If a list is supplied write to individual worksheets using names if available
  nSheets <- 1
  if ("list" %in% class(x)) {
    nms <- names(x)
    nSheets <- length(x)

    if (is.null(nms)) {
      nms <- paste("Sheet", 1:nSheets)
    } else if (any("" %in% nms)) {
      nms[nms %in% ""] <- paste("Sheet", (1:nSheets)[nms %in% ""])
    } else {
      nms <- make.unique(nms)
    }

    if (any(nchar(nms) > 31)) {
      warning("Truncating list names to 31 characters.")
      nms <- substr(nms, 1, 31)
    }

    ## make all inputs as long as the list
    if (!is.null(tabColour)) {
      if (length(tabColour) != nSheets) {
        tabColour <- rep_len(tabColour, length.out = nSheets)
      }
    }

    if (length(zoom) != nSheets) {
      zoom <- rep_len(zoom, length.out = nSheets)
    }

    if (length(gridLines) != nSheets) {
      gridLines <- rep_len(gridLines, length.out = nSheets)
    }

    if (length(withFilter) != nSheets) {
      withFilter <- rep_len(withFilter, length.out = nSheets)
    }

    if (length(colNames) != nSheets) {
      colNames <- rep_len(colNames, length.out = nSheets)
    }

    if (length(rowNames) != nSheets) {
      rowNames <- rep_len(rowNames, length.out = nSheets)
    }

    if (length(startRow) != nSheets) {
      startRow <- rep_len(startRow, length.out = nSheets)
    }

    if (length(startCol) != nSheets) {
      startCol <- rep_len(startCol, length.out = nSheets)
    }

    if (!is.null(headerStyle)) {
      headerStyle <- lapply(1:nSheets, function(x) {
        return(headerStyle)
      })
    }

    if (length(borders) != nSheets & !is.null(borders)) {
      borders <- rep_len(borders, length.out = nSheets)
    }

    if (length(borderColour) != nSheets) {
      borderColour <- rep_len(borderColour, length.out = nSheets)
    }

    if (length(borderStyle) != nSheets) {
      borderStyle <- rep_len(borderStyle, length.out = nSheets)
    }

    if (length(keepNA) != nSheets) {
      keepNA <- rep_len(keepNA, length.out = nSheets)
    }

    if (length(na.string) != nSheets & !is.null(na.string)) {
      na.string <- rep_len(na.string, length.out = nSheets)
    }

    if (length(asTable) != nSheets) {
      asTable <- rep_len(asTable, length.out = nSheets)
    }

    if (length(tableStyle) != nSheets) {
      tableStyle <- rep_len(tableStyle, length.out = nSheets)
    }

    if (length(colWidths) != nSheets) {
      colWidths <- rep_len(colWidths, length.out = nSheets)
    }

    for (i in 1:nSheets) {
      wb$addWorksheet(nms[[i]], showGridLines = gridLines[i], tabColour = tabColour[i], zoom = zoom[i])

      if (asTable[i]) {
        writeDataTable(
          wb = wb,
          sheet = i,
          x = x[[i]],
          startCol = startCol[[i]],
          startRow = startRow[[i]],
          xy = xy,
          colNames = colNames[[i]],
          rowNames = rowNames[[i]],
          tableStyle = tableStyle[[i]],
          tableName = NULL,
          headerStyle = headerStyle[[i]],
          withFilter = withFilter[[i]],
          keepNA = keepNA[[i]],
          na.string = na.string[[i]]
        )
      } else {
        writeData(
          wb = wb,
          sheet = i,
          x = x[[i]],
          startCol = startCol[[i]],
          startRow = startRow[[i]],
          xy = xy,
          colNames = colNames[[i]],
          rowNames = rowNames[[i]],
          headerStyle = headerStyle[[i]],
          borders = borders[[i]],
          borderColour = borderColour[[i]],
          borderStyle = borderStyle[[i]],
          keepNA = keepNA[[i]],
          na.string = na.string[[i]]
        )
      }

      if (colWidths[i] %in% "auto") {
        setColWidths(wb, sheet = i, cols = 1:ncol(x[[i]]) + startCol[[i]] - 1L, widths = "auto")
      }
    }
  } else {
    wb$addWorksheet(sheetName, showGridLines = gridLines, tabColour = tabColour, zoom = zoom)

    if (asTable) {
      if (!"data.frame" %in% class(x)) {
        stop("x must be a data.frame is asTable == TRUE")
      }

      writeDataTable(
        wb = wb,
        sheet = 1,
        x = x,
        startCol = startCol,
        startRow = startRow,
        xy = xy,
        colNames = colNames,
        rowNames = rowNames,
        tableStyle = tableStyle,
        tableName = NULL,
        headerStyle = headerStyle,
        keepNA = keepNA,
        na.string = na.string
      )
    } else {
      writeData(
        wb = wb,
        sheet = 1,
        x = x,
        startCol = startCol,
        startRow = startRow,
        xy = xy,
        colNames = colNames,
        rowNames = rowNames,
        headerStyle = headerStyle,
        borders = borders,
        borderColour = borderColour,
        borderStyle = borderStyle,
        keepNA = keepNA,
        na.string = na.string
      )
    }

    if (colWidths[1] %in% "auto") {
      setColWidths(wb, sheet = 1, cols = 1:ncol(x) + startCol - 1L, widths = "auto")
    }
  }

  ### --Freeze Panes---###
  ## firstActiveRow = NULL
  ## firstActiveCol = NULL
  ## firstRow = FALSE
  ## firstCol = FALSE

  freezePanes <- FALSE
  firstActiveRow <- rep_len(1L, length.out = nSheets)
  if ("firstActiveRow" %in% names(params)) {
    firstActiveRow <- params$firstActiveRow
    freezePanes <- TRUE
    if (length(firstActiveRow) != nSheets) {
      firstActiveRow <- rep_len(firstActiveRow, length.out = nSheets)
    }
  }

  firstActiveCol <- rep_len(1L, length.out = nSheets)
  if ("firstActiveCol" %in% names(params)) {
    firstActiveCol <- params$firstActiveCol
    freezePanes <- TRUE
    if (length(firstActiveCol) != nSheets) {
      firstActiveCol <- rep_len(firstActiveCol, length.out = nSheets)
    }
  }

  firstRow <- rep_len(FALSE, length.out = nSheets)
  if ("firstRow" %in% names(params)) {
    firstRow <- params$firstRow
    freezePanes <- TRUE
    if ("list" %in% class(x) & length(firstRow) != nSheets) {
      firstRow <- rep_len(firstRow, length.out = nSheets)
    }
  }

  firstCol <- rep_len(FALSE, length.out = nSheets)
  if ("firstCol" %in% names(params)) {
    firstCol <- params$firstCol
    freezePanes <- TRUE
    if ("list" %in% class(x) & length(firstCol) != nSheets) {
      firstCol <- rep_len(firstCol, length.out = nSheets)
    }
  }

  if (freezePanes) {
    for (i in 1:nSheets) {
      freezePane(
        wb = wb,
        sheet = i,
        firstActiveRow = firstActiveRow[i],
        firstActiveCol = firstActiveCol[i],
        firstRow = firstRow[i],
        firstCol = firstCol[i]
      )
    }
  }




  saveWorkbook(wb = wb, file = file, overwrite = overwrite)

  invisible(wb)
}
