#' test the basics of tidy/augment/glance output: is a data frame, no row names
check_tidiness <- function(o) {
  testthat::expect_is(o, "tbl_df")
  # testthat::expect_equal(rownames(o), as.character(seq_len(nrow(o))))
}


#' check the output of a tidy function
check_tidy <- function(o, exp.row = NULL, exp.col = NULL, exp.names = NULL) {
  check_tidiness(o)

  if (!is.null(exp.row)) {
    testthat::expect_equal(nrow(o), exp.row)
  }
  if (!is.null(exp.col)) {
    testthat::expect_equal(ncol(o), exp.col)
  }
  if (!is.null(exp.names)) {
    testthat::expect_true(all(exp.names %in% colnames(o)))
  }
}


#' check the output of an augment function
check_augment <- function(au, original = NULL, exp.names = NULL,
                          same = NULL) {
  check_tidiness(au)

  if (!is.null(original)) {
    # check that all rows in original appear in output
    testthat::expect_equal(nrow(au), nrow(original))
    # check that columns are the same
    for (column in same) {
      testthat::expect_equal(au[[column]], original[[column]])
    }
  }

  if (!is.null(exp.names)) {
    testthat::expect_true(all(exp.names %in% colnames(au)))
  }
}


#' add NAs to a vector randomly
#'
#' @param v vector to add NAs to
#' @param number number of NAs to add
#'
#' @return vector with NAs added randomly
add_NAs <- function(v, number) {
  if (number >= length(v)) {
    stop("Would replace all or more values with NA")
  }
  v[sample(length(v), number)] <- NA
  v
}

#' check an augmentation function works as expected when given NAs
#'
#' @param func A modeling function that takes a dataset and additional
#' arguments, including na.action
#' @param .data dataset to test function on; must have at least 3 rows
#' and no NA values
#' @param column a column included in the model to be replaced with NULLs
#' @param column2 another column in the model; optional
#' @param ... extra arguments, not used
#'
#' @export
check_augment_NAs <- function(func, .data, column, column2 = NULL, ...) {
  # test original version, with and without giving data to augment
  obj <- class(func(.data))[1]

  test_that(paste("augment works with", obj), {
    m <- func(.data)
    au <- augment(m)
    check_augment(au, .data)
    au_d <- augment(m, .data)
    check_augment(au_d, .data, same = colnames(.data))
  })

  # add NAs
  if (nrow(.data) < 3) {
    stop(".data must have at least 3 rows in NA testing")
  }

  check_omit <- function(au, dat) {
    NAs <- is.na(dat[[column]])

    check_augment(au, dat[!NAs, ], same = c(column, column2))
    if (!is.null(au$.rownames)) {
      testthat::expect_equal(rownames(dat)[!NAs], au$.rownames)
    }
  }

  check_exclude <- function(au, dat) {
    check_augment(au, dat, colnames(dat))
    # check .fitted and .resid columns have NAs in the right place
    for (col in c(".fitted", ".resid")) {
      if (!is.null(au[[col]])) {
        testthat::expect_equal(is.na(au[[col]]), is.na(dat[[column]]))
      }
    }
  }

  # test augment with na.omit (factory-fresh setting in R)
  # and test with or without rownames
  num_NAs <- min(5, nrow(.data) - 2)
  .dataNA <- .data
  .dataNA[[column]] <- add_NAs(.dataNA[[column]], num_NAs)

  test_that(paste("augment works with", obj, "with na.omit"), {
    .dataNA_noname <- unrowname(.dataNA)
    for (d in list(.dataNA, .dataNA_noname)) {
      m <- func(d, na.action = "na.omit")
      au <- augment(m)
      check_omit(au, d)
      au_d <- augment(m, d)
      check_omit(au_d, d)
    }
  })

  for (rnames in c(TRUE, FALSE)) {
    msg <- paste("augment works with", obj, "with na.exclude")
    if (!rnames) {
      msg <- paste(msg, "without rownames")
    }
    test_that(msg, {
      d <- if (rnames) {
        .dataNA
      } else {
        unrowname(.dataNA)
      }
      m <- func(d, na.action = "na.exclude")
      # without the data argument, it works like na.exclude
      testthat::expect_warning(au <- augment(m), "na.exclude")
      check_omit(au, d)
      # with the data argument, it keeps the NAs
      au_d <- augment(m, d)
      check_exclude(au_d, d)
    })
  }
}
