# ==============================================================================
# svySE: tablas simples sin diseno muestral
# svySE: unweighted simple indicator tables
# Archivo / File: R/simple.R
# ==============================================================================


#' Calculate unweighted simple indicator tables
#'
#' Calcula frecuencias y porcentajes simples para uno o mas indicadores sin
#' utilizar factores de expansion ni variables del diseno muestral.
#'
#' Calculates unweighted frequencies and percentages for one or more indicators
#' without using sampling weights or survey design variables.
#'
#' @param data Base de datos previamente cargada en R.
#'   Data frame previously loaded in R.
#'
#' @param indicators Vector de texto con los nombres de los indicadores.
#'   Character vector containing the indicator names.
#'
#' @param group_vars Variables utilizadas para agrupar los resultados.
#'   Variables used to group the results.
#'
#' @param group_labels Etiquetas de las variables de agrupacion.
#'   Labels for the grouping variables.
#'
#' @param division Variable opcional para generar resultados separados por
#'   categoria. Si es `NULL`, solo se calcula el total.
#'   Optional variable used to generate results by category. If `NULL`, only
#'   the total is calculated.
#'
#' @param target Valor que identifica la categoria de interes.
#'   Value identifying the target category.
#'
#' @param valid_values Valores permitidos en los indicadores.
#'   Values allowed in the indicators.
#'
#' @param pct_mult Multiplicador utilizado para expresar los porcentajes.
#'   Multiplier used to express percentages.
#'
#' @param verbose Si es `TRUE`, muestra el avance del calculo.
#'   If `TRUE`, displays calculation progress.
#'
#' @param strict Si es `TRUE`, detiene el calculo cuando encuentra valores fuera
#'   de `valid_values`. Si es `FALSE`, los excluye con una advertencia.
#'   If `TRUE`, stops when values outside `valid_values` are found. If `FALSE`,
#'   they are excluded with a warning.
#'
#' @details
#' Los resultados generados por esta funcion describen solamente los registros
#' observados en la muestra. Como no se utilizan factores de expansion ni
#' componentes del diseno muestral, los porcentajes no deben interpretarse como
#' estimaciones representativas de la poblacion.
#'
#' The results generated by this function describe only the records observed in
#' the sample. Because sampling weights and survey design components are not
#' used, percentages should not be interpreted as population estimates.
#'
#' @return Objeto de clase `"svySE_simple_result"`.
#'   Object of class `"svySE_simple_result"`.
#'
#' @export
svySE_simple <- function(
    data,
    indicators,
    group_vars,
    group_labels = group_vars,
    division = NULL,
    target = 1,
    valid_values = c(0, 1),
    pct_mult = 100,
    verbose = TRUE,
    strict = FALSE
) {
  
  # ---------------------------------------------------------------------------
  # Validaciones generales
  # General validations
  # ---------------------------------------------------------------------------
  
  svySE_chk_df(data)
  svySE_chk_chr(indicators, "indicators")
  svySE_chk_chr(group_vars, "group_vars")
  svySE_chk_chr(group_labels, "group_labels")
  svySE_chk_bool(verbose, "verbose")
  svySE_chk_bool(strict, "strict")
  
  if (length(target) != 1 || is.na(target)) {
    svySE_abort(
      title = "Valor objetivo invalido / Invalid target value.",
      details = "`target` debe contener un solo valor no perdido.",
      vars = list(target = target)
    )
  }
  
  if (length(valid_values) < 2 || any(is.na(valid_values))) {
    svySE_abort(
      title = "Valores validos incorrectos / Invalid valid values.",
      details = paste(
        "`valid_values` debe contener al menos dos valores no perdidos.",
        "`valid_values` must contain at least two non-missing values."
      ),
      vars = list(valid_values = valid_values)
    )
  }
  
  if (!(target %in% valid_values)) {
    svySE_abort(
      title = "El valor objetivo no es valido / Target is not valid.",
      details = "`target` debe estar incluido dentro de `valid_values`.",
      vars = list(
        target = target,
        valid_values = valid_values
      )
    )
  }
  
  if (
    !is.numeric(pct_mult) ||
    length(pct_mult) != 1 ||
    is.na(pct_mult) ||
    !is.finite(pct_mult) ||
    pct_mult <= 0
  ) {
    svySE_abort(
      title = "Multiplicador de porcentaje invalido / Invalid percentage multiplier.",
      details = "`pct_mult` debe ser un numero positivo y finito.",
      vars = list(pct_mult = pct_mult)
    )
  }
  
  if (!is.null(division)) {
    svySE_chk_chr(division, "division")
    
    if (length(division) != 1) {
      svySE_abort(
        title = "Variable de division invalida / Invalid division variable.",
        details = paste(
          "`division` debe contener una sola variable.",
          "`division` must contain one variable."
        ),
        vars = list(division = division)
      )
    }
  }
  
  # ---------------------------------------------------------------------------
  # Validar existencia de variables
  # Validate variable existence
  # ---------------------------------------------------------------------------
  
  svySE_chk_vars(data, indicators, "indicators")
  svySE_chk_vars(data, group_vars, "group_vars")
  
  if (!is.null(division)) {
    svySE_chk_vars(data, division, "division")
  }
  
  if (length(group_labels) != length(group_vars)) {
    svySE_abort(
      title = "Etiquetas de grupo incompatibles / Incompatible group labels.",
      details = paste(
        "`group_labels` debe tener la misma longitud que `group_vars`.",
        "`group_labels` must have the same length as `group_vars`."
      ),
      vars = list(
        group_vars = group_vars,
        group_labels = group_labels
      )
    )
  }
  
  svySE_chk_required_no_na(
    data = data,
    vars = group_vars,
    context = "variables de agrupacion / grouping variables"
  )
  
  if (!is.null(division)) {
    svySE_warn_na_division(data, division)
  }
  
  # ---------------------------------------------------------------------------
  # Copia interna
  # Internal copy
  # ---------------------------------------------------------------------------
  
  data <- as.data.frame(data)
  
  data$.__svySE_group_id__ <- do.call(
    paste,
    c(data[group_vars], sep = " | ")
  )
  
  groups_master <- sort(unique(data$.__svySE_group_id__))
  
  if (length(groups_master) == 0) {
    svySE_abort(
      title = "No se encontraron grupos validos / No valid groups found.",
      vars = list(group_vars = group_vars)
    )
  }
  
  # ---------------------------------------------------------------------------
  # Calculo por indicador
  # Calculation by indicator
  # ---------------------------------------------------------------------------
  
  out <- list()
  
  for (ind in indicators) {
    
    if (isTRUE(verbose)) {
      message("Procesando tabla simple / Processing simple table: ", ind)
    }
    
    x <- data[[ind]]
    
    invalid_values <- setdiff(
      unique(x[!is.na(x)]),
      valid_values
    )
    
    if (length(invalid_values) > 0) {
      
      msg <- paste0(
        "El indicador `", ind,
        "` contiene valores fuera de `valid_values`: ",
        paste(invalid_values, collapse = ", "),
        ". Estos registros seran excluidos."
      )
      
      if (isTRUE(strict)) {
        svySE_abort(
          title = "Indicador con valores no permitidos / Indicator with invalid values.",
          details = msg,
          vars = list(
            indicator = ind,
            valid_values = valid_values,
            invalid_values = invalid_values,
            observed_values = svySE_observed_values(x)
          )
        )
      } else {
        warning(msg, call. = FALSE)
      }
    }
    
    data_ind <- data[
      !is.na(data[[ind]]) &
        data[[ind]] %in% valid_values,
      ,
      drop = FALSE
    ]
    
    if (nrow(data_ind) == 0) {
      svySE_abort(
        title = "Indicador sin registros validos / Indicator without valid records.",
        details = paste0(
          "El indicador `", ind,
          "` no tiene registros dentro de `valid_values`."
        ),
        vars = list(
          indicator = ind,
          valid_values = valid_values
        )
      )
    }
    
    data_ind$.__svySE_cat__ <- ifelse(
      data_ind[[ind]] == target,
      1,
      0
    )
    
    # -------------------------------------------------------------------------
    # Crear divisiones
    # Create divisions
    # -------------------------------------------------------------------------
    
    filters <- list(
      TOTAL = list(
        name = "TOTAL",
        data = data_ind
      )
    )
    
    if (!is.null(division)) {
      
      categories <- sort(unique(data_ind[[division]]))
      categories <- categories[!is.na(categories)]
      
      if (length(categories) == 0) {
        svySE_abort(
          title = paste(
            "Variable de division sin categorias validas /",
            "Division variable without valid categories."
          ),
          vars = list(division = division)
        )
      }
      
      for (category in categories) {
        
        filters[[as.character(category)]] <- list(
          name = as.character(category),
          data = data_ind[
            data_ind[[division]] == category,
            ,
            drop = FALSE
          ]
        )
      }
    }
    
    simple_list <- list()
    
    for (i in seq_along(filters)) {
      
      f <- filters[[i]]
      
      simple_list[[f$name]] <- svySE_simple_one(
        data = f$data,
        group_vars = group_vars,
        groups_master = groups_master,
        pct_mult = pct_mult
      )
    }
    
    out[[ind]] <- list(
      simple = simple_list
    )
  }
  
  # ---------------------------------------------------------------------------
  # Resultado
  # Result
  # ---------------------------------------------------------------------------
  
  result <- list(
    results = out,
    meta = list(
      indicators = indicators,
      group_vars = group_vars,
      group_labels = group_labels,
      strata = NULL,
      cluster = NULL,
      weight = NULL,
      division = division,
      div_weight = NULL,
      target = target,
      valid_values = valid_values,
      pct_mult = pct_mult,
      strict = strict
    )
  )
  
  class(result) <- c(
    "svySE_simple_result",
    "list"
  )
  
  result
}


# ==============================================================================
# Calculo de una tabla simple
# Calculate one simple table
# ==============================================================================

#' @keywords internal
svySE_simple_one <- function(
    data,
    group_vars,
    groups_master,
    pct_mult = 100
) {
  
  metric_names <- svySE_cols_tab_all()
  
  out <- data.frame()
  
  groups_in_data <- sort(unique(data$.__svySE_group_id__))
  
  for (g in groups_master) {
    
    if (!(g %in% groups_in_data)) {
      
      row_na <- svySE_na_row(
        group_id = g,
        group_vars = group_vars,
        metric_names = metric_names
      )
      
      out <- rbind(out, row_na)
      next
    }
    
    group_data <- data[
      data$.__svySE_group_id__ == g,
      ,
      drop = FALSE
    ]
    
    group_row <- group_data[
      1,
      group_vars,
      drop = FALSE
    ]
    
    n_total <- nrow(group_data)
    n_target <- sum(group_data$.__svySE_cat__ == 1, na.rm = TRUE)
    n_other <- sum(group_data$.__svySE_cat__ == 0, na.rm = TRUE)
    
    metrics <- data.frame(
      freq_0 = n_other,
      pct_0 = if (n_total > 0) {
        n_other / n_total * pct_mult
      } else {
        NA_real_
      },
      freq_1 = n_target,
      pct_1 = if (n_total > 0) {
        n_target / n_total * pct_mult
      } else {
        NA_real_
      },
      freq_total = n_total,
      pct_total = pct_mult
    )
    
    out <- rbind(
      out,
      cbind(group_row, metrics)
    )
  }
  
  # ---------------------------------------------------------------------------
  # Fila total
  # Total row
  # ---------------------------------------------------------------------------
  
  total_group <- as.data.frame(
    as.list(rep("NACIONAL", length(group_vars))),
    stringsAsFactors = FALSE
  )
  
  names(total_group) <- group_vars
  
  n_total <- nrow(data)
  n_target <- sum(data$.__svySE_cat__ == 1, na.rm = TRUE)
  n_other <- sum(data$.__svySE_cat__ == 0, na.rm = TRUE)
  
  total_metrics <- data.frame(
    freq_0 = n_other,
    pct_0 = if (n_total > 0) {
      n_other / n_total * pct_mult
    } else {
      NA_real_
    },
    freq_1 = n_target,
    pct_1 = if (n_total > 0) {
      n_target / n_total * pct_mult
    } else {
      NA_real_
    },
    freq_total = n_total,
    pct_total = pct_mult
  )
  
  out <- rbind(
    cbind(total_group, total_metrics),
    out
  )
  
  rownames(out) <- NULL
  
  out
}


# ==============================================================================
# Impresion del resultado simple
# Print simple result
# ==============================================================================

#' Print an svySE simple result
#'
#' Imprime un resumen del resultado generado por `svySE_simple()`.
#'
#' Prints a summary of the result generated by `svySE_simple()`.
#'
#' @param x Objeto de clase `"svySE_simple_result"`.
#' @param ... Argumentos adicionales.
#'
#' @return Invisiblemente `x`.
#'
#' @export
print.svySE_simple_result <- function(x, ...) {
  
  cat("svySE simple result\n")
  cat("--------------------------------------------------\n")
  cat(
    "Indicators :",
    paste(x$meta$indicators, collapse = ", "),
    "\n"
  )
  cat(
    "Groups     :",
    paste(x$meta$group_vars, collapse = ", "),
    "\n"
  )
  cat(
    "Division   :",
    if (is.null(x$meta$division)) {
      "NULL"
    } else {
      x$meta$division
    },
    "\n"
  )
  cat("Target     :", x$meta$target, "\n")
  cat(
    "Weighted   : No\n"
  )
  cat(
    "Warning    : Results describe the observed sample only.\n"
  )
  
  invisible(x)
}