#' Extract Residuals and Fitted Values from \code{afex_aov} objects
#'
#' Extract Residuals and Fitted Values from \code{afex_aov} objects.
#'
#' @author Mattan S. Ben-Shachar
#'
#' @example examples/examples.residuals.R
#'
#' @param object \code{afex_aov} object.
#' @param append If set to \code{TRUE} returns the residuals/fitted values
#'   appended as an additional column to the long data. Recommended when data
#'   was aggregated across within conditions.
#' @param colname_residuals,colname_fitted Name of the appended column when
#'   \code{append = TRUE}.
#' @param ... Additional arguments passed to
#'   \code{residuals.lm}/\code{fitted.lm}.
#'
#' @return A vector of residuals/fitted values corresponding to the data in
#'   \code{object$data$long}, or if \code{append = TRUE} a data frame with an
#'   additional column of residuals/fitted values.
#'
#' @export
#' @importFrom stats fitted reshape residuals
residuals.afex_aov <- function(
  object,
  append = FALSE,
  colname_residuals = ".residuals",
  ...
) {
  if (!append && attr(object, "data_changed")) {
    message(
      "Data was changed during ANOVA calculation. ",
      "Thus, residuals cannot be added to original data.",
      "\nresiduals(..., append = TRUE) will return data and residuals."
    )
  }

  e <- residuals(object$lm, ...)
  .clean_model_values(
    object,
    model_values = e,
    values_colname = colname_residuals,
    append = append
  )
}

#' @export
#' @rdname residuals.afex_aov
fitted.afex_aov <- function(
  object,
  append = FALSE,
  colname_fitted = ".fitted",
  ...
) {
  if (!append && attr(object, "data_changed")) {
    message(
      "Data was changed during ANOVA calculation. ",
      "Thus, fitted values cannot be added to original data.",
      "\nfitted(..., append = TRUE) will return data and fitted values."
    )
  }

  e <- fitted(object$lm, ...)
  .clean_model_values(
    object,
    model_values = e,
    values_colname = colname_fitted,
    append = append
  )
}


#' @keywords internal
.clean_model_values <- function(object, model_values, values_colname, append) {
  if (length(attr(object, "within")) > 0) {
    # In long format
    mv <- data.frame(model_values)
    varying <- colnames(mv)
    mv[attr(object, "id")] <- object$data$wide[attr(object, "id")]
    mv <- reshape(
      mv,
      direction = "long",
      varying = varying,
      v.names = values_colname,
      times = varying,
      timevar = ".time.",
      idvar = attr(object, "id")
    )

    # add within data
    code <- data.frame(.time. = varying, .index. = seq_along(varying))
    index <- object$data$idata
    index$.index. <- seq_len(nrow(index))
    index <- merge(code, index, by = ".index.")

    mv <- merge(mv, index, by = ".time.")
    mv$.time. <- NULL
    mv$.index. <- NULL

    # add between data
    between_data <- object$data$long
    mv <- merge(
      between_data,
      mv,
      by = c(attr(object, "id"), names(attr(object, "within")))
    )
  } else {
    mv <- object$data$long
    mv[[values_colname]] <- model_values
  }

  if (append) {
    return(mv)
  } else {
    return(setNames(mv[[values_colname]], rownames(mv)))
  }
}
