#' Set certain options
#'
#' `set_opts()` sets certain package options. To view these options, use `show_opts()`.
#' For more advanced control and detailed customization, experienced  users can
#' also employ [options()] and [show_options()] (refer to [surveytable-options]
#' for further information).
#'
#' If you are not setting a particular option, leave it as `NULL`.
#'
#' `mode` can be either `"general"` or `"NCHS"` and has the following meaning:
#'
#' * `"general"`:
#'    * Round counts to the nearest integer -- same as `count = "int"`.
#'    * Do not look for low-precision estimates -- same as `lpe = FALSE`.
#'    * Retain missing values -- same as `drop_na = FALSE`.
#'    * Percentage CI's: use standard Korn-Graubard CI's -- same as `adj = "none"`.
#'
#' * `"nchs"`:
#'    * Round counts to the nearest 1,000 -- same as `count = "1k"`.
#'    * Identify low-precision estimates -- same as `lpe = TRUE`.
#'    * Drop missing values -- same as `drop_na = TRUE`.
#'    * Percentage CI's: adjust Korn-Graubard CI's for the number of degrees of
#'    freedom, matching the SUDAAN calculation -- same as `adj = "nchs"`. This
#'    is appropriate for some, but not all, NCHS data systems. For some NCHS
#'    data systems, such as NHIS, you might need to set `adj` to one of the other values.
#'
#' `adj` specifies the adjustment to the Korn and Graubard confidence intervals for
#' proportions. See `svyciprop_adjusted()` for details.
#'
#' `output` determines how the output is printed:
#'
#' * `"auto"` (default): automatically select the table-making package, depending on the
#' destination (such as screen, HTML, or PDF / LaTeX).
#' * `"huxtable"`, `"gt"`, `"kableExtra"`, `"flextable"`: use this table-making package. Be sure
#' that this package is installed.
#' * `"screen"`: print plain-text tables to the screen.
#' * `"Excel"`, `"Excel_v1"`: print to an Excel workbook. Please specify the name of an Excel
#' file using the `file` argument. Before using Excel printing, please be sure to install these
#' packages: `openxlsx2` and `mschart`.
#' * `"Word"`: print to a Word document. Please specify the name of a Word
#' file using the `file` argument. Before using Word printing, please be sure to install these
#' packages: `flextable` and `officer`.
#' * `"CSV"`: print to a comma-separated values (CSV) file. Please specify the name of a
#' CSV file using the `file` argument.
#'
#' `raw = TRUE` prints unformatted / raw values. This is useful for getting lots of
#' significant digits. It is supported with `output = "screen"`, `output = "CSV"`,
#' and `output = "Excel"`. Also see [as.data.frame.astra_table()] and
#' [restructure()].
#'
#' @param reset reset all options to their default values?
#' @param mode `"general"` or `"NCHS"`. See below for details.
#' @param adj adjustment to the Korn and Graubard confidence intervals for proportions. See
#' `svyciprop_adjusted()` for details.
#' @param output specify how the output is printed: `"auto"` (default); `"huxtable"`, `"gt"`,
#' `"kableExtra"`, `"flextable"`; `"screen"`. For the following output types, please
#' also specify the `file` argument: `"Excel"`, `"Excel_v1"`, `"Word"`, `"CSV"`.
#' @param raw print unformatted / raw values?
#' @param file file name (see `output`).
#' @param .file_temp place `file` in a temporary folder?
#' @param count round counts to the nearest integer (`"int"`) or one thousand (`"1k"`).
#' @param lpe identify low-precision estimates?
#' @param drop_na drop missing values (`NA`)? Categorical variables only.
#' @param max_levels a categorical variable can have at most this many levels.
#' Used to avoid printing huge tables.
#'
#' @return (Nothing.)
#'
#' @family options
#' @family print
#'
#' @export
#'
#' @examples
#' set_survey(namcs2019sv)
#'
#' # Round counts to the nearest one thousand:
#' set_opts(count = "1k")
#' tab("AGER")
#' set_opts(count = "int")
#'
#' show_opts()
set_opts = function(
    reset = NULL
    , mode = NULL
    , adj = NULL
    , output = NULL
    , raw = NULL
    , file = NULL
    , .file_temp = NULL
    , count = NULL
    , lpe = NULL
    , drop_na = NULL
    , max_levels = NULL
    ) {

  #### !!! If making changes, update: .onLoad(), set_opts(), show_opts(), .check_options()

  ## Reset has to go ahead of the other options
  if (!is.null(reset)) {
    assert_that(is.flag(reset), reset %in% c(TRUE, FALSE))
    if (reset) {
      message("* Resetting all options to their default values.")
      env$survey = NULL
      .onLoad()
    }
  }

  ## Mode has to go ahead of the other options
  if (!is.null(mode)) {
    mode %<>% .mymatch(c("nchs", "general"))
    if (mode == "nchs") {
      message("* Mode: NCHS.")
      options(surveytable.svyciprop_adj = "nchs"
        , surveytable.tx_count = ".tx_count_1k"
        , surveytable.names_count = c("n", "Number (000)", "SE (000)", "LL (000)", "UL (000)")
        , surveytable.find_lpe = TRUE
        , surveytable.drop_na = TRUE)
    } else if (mode == "general") {
      message("* Mode: General.")
      options(surveytable.svyciprop_adj = "none"
        , surveytable.tx_count = ".tx_count_int"
        , surveytable.names_count = c("n", "Number", "SE", "LL", "UL")
        , surveytable.find_lpe = FALSE
        , surveytable.drop_na = FALSE)
    }
  }

  if (!is.null(adj)) {
    adj %<>% .mymatch(c("none", "nchs", "nhis"))
    if (adj == "none") {
      message("* Korn and Graubard confidence intervals for proportions.")
    } else if (adj == "nchs") {
      message("* Korn and Graubard confidence intervals for proportions with an adjustment that might be required by some (though not all) NCHS data systems.")
    } else if (adj == "nhis") {
      message("* Korn and Graubard confidence intervals for proportions with an adjustment that might be required by NHIS.")
    }
    options(surveytable.svyciprop_adj = adj)
  }

  print_final = getOption("astra.print")
  if (is.null(print_final)) {
    print_final = ".print_auto"
  }
  raw_final = getOption("surveytable.raw")
  if (is.null(raw_final)) {
    raw_final = FALSE
  }
  if (!is.null(output)) {
    output %<>% .mymatch(c("huxtable", "gt", "kableExtra", "flextable"
                           , "auto", "screen"
                           , "excel", "excel_v1", "word", "csv"))
    print_final = .astra_print_for_output(output)
  }
  if (!is.null(raw)) {
    assert_that(is.flag(raw), raw %in% c(TRUE, FALSE))
    raw_final = raw
  }
  .check_raw_output(raw = raw_final, print = print_final)

  if (!is.null(output)) {
    if (output == "auto") {
      message("* Printing with huxtable for screen, gt for HTML, or kableExtra for PDF.")
      options(astra.print = ".print_auto"
              , astra.file = "", astra.file_show = "")
    } else if (output %in% c("huxtable", "gt", "kableextra", "flextable") ) {
      message(glue("* Printing with {output}."))
      options(astra.print = .astra_print_for_output(output)
              , astra.file = "", astra.file_show = "")
    } else if (output == "screen") {
      .set_output_screen()
    } else if (output %in% c("excel", "excel_v1", "word", "csv")) {
      .set_output_file(output = output, file = file, .file_temp = .file_temp)
    }
  }

  if (!is.null(raw)) {
    if (raw) {
      message("* Generating unformatted / raw values.")
    } else {
      message("* Generating rounded / formatted values.")
    }
    options(surveytable.raw = raw)
  }

  if (!is.null(count)) {
    if (getOption("surveytable.raw")) {
      message("* To perform rounding, first call set_opts(raw = FALSE).")
    } else {
      count %<>% .mymatch(c("int", "1k"))
      if (count == "int") {
        message("* Rounding counts to the nearest integer.")
        options(surveytable.tx_count = ".tx_count_int"
                , surveytable.names_count = c("n", "Number", "SE", "LL", "UL"))
      } else if (count == "1k") {
        message("* Rounding counts to the nearest thousand.")
        options(surveytable.tx_count = ".tx_count_1k"
                , surveytable.names_count = c("n", "Number (000)", "SE (000)", "LL (000)", "UL (000)"))
      }
    }
  }

  if (!is.null(lpe)) {
    assert_that(is.flag(lpe), lpe %in% c(TRUE, FALSE))
    if (lpe) {
      message("* Identifying low-precision estimates.")
    } else {
      message("* Not identifying low-precision estimates.")
    }
    options(surveytable.find_lpe = lpe)
  }

  if (!is.null(drop_na)) {
    assert_that(is.flag(drop_na), drop_na %in% c(TRUE, FALSE))
    if (drop_na) {
      message("* Dropping missing values. Showing knowns only.")
    } else {
      message("* Retaining missing values.")
    }
    options(surveytable.drop_na = drop_na)
  }

  if (!is.null(max_levels)) {
    assert_that(is.count(max_levels))
    message(paste0("* Setting maximum number of levels to: ", max_levels))
    options(surveytable.max_levels = max_levels)
  }

  .check_options()
  invisible(NULL)
}

.check_raw_output = function(raw, print = NULL) {
  assert_that(is.flag(raw), raw %in% c(TRUE, FALSE))
  if (is.null(print)) {
    print = getOption("astra.print")
  }
  info = .astra_print_info(print = print)
  assert_that(!isTRUE(raw) || isTRUE(info$raw)
              , msg = glue('raw = TRUE is only supported with output = "screen", "CSV", or "Excel". Current output is "{info$output}".'))
  invisible(NULL)
}

.set_output_screen = function() {
  message("* Printing to the screen.")
  options(astra.print = ".print_screen"
          , astra.file = "", astra.file_show = "")
}

.set_output_file = function(output, file, .file_temp) {
  assert_that(output %in% c("excel", "excel_v1", "word", "csv"))
  type = switch(output
                , excel = "Excel"
                , excel_v1 = "Excel"
                , word = "Word"
                , csv = "CSV")
  extension = switch(output
                     , excel = ".xlsx"
                     , excel_v1 = ".xlsx"
                     , word = ".docx"
                     , csv = ".csv")
  assert_that(is.string(file), nzchar(file)
              , msg = glue("For {type} printing, please specify a file name using the file argument."))
  if (!endsWith(tolower(file), extension)) {
    file = glue("{file}{extension}")
  }
  if (!isTRUE(.file_temp)) {
    file %<>% normalizePath(mustWork = FALSE)
    file_show = file
  } else {
    file = file.path(tempdir(), file)
    file_show = file %>% basename()
  }
  message(glue("* Printing to {type} file {file_show}."))
  if (file.exists(file)) {
    message("* NOTE: file already exists!")
  }
  options(astra.print = .astra_print_for_output(output)
          , astra.file = file, astra.file_show = file_show)
}
