#' Generate p-Rep Test-Control Designs: Method III
#'
#' Constructs partially replicated (p-rep) test-control designs for
#' multiple environments using arrays derived from mutually orthogonal
#' Latin squares.
#'
#' @param v Total number of test lines. It must satisfy
#'   v = s(s - 1) - 3 for an odd integer s >= 5;
#'   for example: 17, 39, 69, or 117.
#'
#' @return A list containing design parameters, non-randomized layouts,
#'   and randomized layouts.
#'
#' @export
TCpRep3 <- function(v) {
  
  ############################################################
  ## Input Validation
  ############################################################
  
  if (length(v) != 1L ||
      is.na(v) ||
      v < 17 ||
      v != as.integer(v)) {
    
    stop(
      "'v' must be a single integer representing the total number of test lines.",
      call. = FALSE
    )
    
  }
  
  # Method III requires: v = s(s - 1) - 3
  s <- (1 + sqrt(13 + 4 * v)) / 2
  
  if (s != as.integer(s) ||
      s < 5 ||
      s %% 2 == 0) {
    
    stop(
      "'v' must satisfy v = s(s - 1) - 3 for an odd integer s >= 5; ",
      "for example: 17, 39, 69, or 117.",
      call. = FALSE
    )
    
  }
  
  s <- as.integer(s)
  vt <- v
  
  ############################################################
  ## Step 1: Generate s - 1 MOLS
  ############################################################
  
  mols <- vector("list", s - 1)
  
  for (k in 1:(s - 1)) {
    
    mols[[k]] <- outer(
      0:(s - 1),
      0:(s - 1),
      function(i, j) (i + k * j) %% s
    )
    
  }
  
  ############################################################
  ## Step 2: Position Arrays
  ############################################################
  
  position_arrays <- vector("list", s - 1)
  
  for (k in 1:(s - 1)) {
    
    P <- matrix(0, s, s)
    
    for (sym in 0:(s - 1)) {
      P[, sym + 1] <- which(mols[[k]] == sym)
    }
    
    position_arrays[[k]] <- P
  }
  
  ############################################################
  ## Step 3: Delete Selected Rows
  ############################################################
  
  temp_arrays <- lapply(
    position_arrays,
    function(x) x[-nrow(x), , drop = FALSE]
  )
  
  final_arrays <- vector("list", s - 1)
  
  for (i in 1:(s - 1)) {
    final_arrays[[i]] <- temp_arrays[[i]][-i, , drop = FALSE]
  }
  
  ############################################################
  ## Step 4: Create Blocks and Environments
  ############################################################
  
  blocks <- list()
  block_info <- data.frame()
  
  blk <- 1
  
  for (i in 1:(s - 1)) {
    
    env <- ceiling(i / 2)
    A <- final_arrays[[i]]
    
    for (j in 1:ncol(A)) {
      
      blocks[[blk]] <- as.numeric(A[, j])
      
      block_info <- rbind(
        block_info,
        data.frame(
          Environment = env,
          Array = i,
          Block = blk
        )
      )
      
      blk <- blk + 1
    }
  }
  
  env_design <- list()
  
  for (env in unique(block_info$Environment)) {
    
    idx <- which(block_info$Environment == env)
    
    M <- do.call(
      rbind,
      lapply(idx, function(ii) blocks[[ii]])
    )
    
    rownames(M) <- paste0("B", 1:nrow(M))
    
    env_design[[paste0("Environment_", env)]] <- M
  }
  
  ############################################################
  ## Design Parameters
  ############################################################
  
  vc <- 1L
  e <- (s - 1) / 2
  b <- s * (s - 1)
  rt <- s - 2
  rc <- 3 * (s - 2)
  k <- s - 2
  
  parameters <- data.frame(
    
    Parameter = c(
      "Number of Test Treatments (vt)",
      "Number of Controls (vc)",
      "Number of Environments (e)",
      "Number of Blocks (b)",
      "Test Replication (rt)",
      "Control Replication (rc)",
      "Block Size (k)"
    ),
    
    Value = c(
      vt,
      vc,
      e,
      b,
      rt,
      rc,
      k
    ),
    
    stringsAsFactors = FALSE
  )
  
  ############################################################
  ## Replace Highest Three Treatments with One Control
  ############################################################
  
  raw_treatment_count <- length(
    unique(unlist(blocks))
  )
  
  control_trts <- (raw_treatment_count - 2):raw_treatment_count
  
  for (env in seq_along(env_design)) {
    
    M <- env_design[[env]]
    
    M[M %in% control_trts] <- "C1"
    
    env_design[[env]] <- M
  }
  
  ############################################################
  ## Randomized Design
  ############################################################
  
  Randomized_Design <- lapply(
    env_design,
    function(env) {
      
      randomized <- t(
        apply(env, 1, sample)
      )
      
      rownames(randomized) <- rownames(env)
      
      randomized
    }
  )
  
  ############################################################
  ## Layouts
  ############################################################
  
  NonRandomized_Layout <- lapply(
    env_design,
    function(env) {
      
      data.frame(
        Block = rownames(env),
        Lines = apply(
          env,
          1,
          function(x) paste(x, collapse = " ")
        ),
        row.names = NULL,
        stringsAsFactors = FALSE
      )
    }
  )
  
  Randomized_Layout <- lapply(
    Randomized_Design,
    function(env) {
      
      data.frame(
        Block = rownames(env),
        Lines = apply(
          env,
          1,
          function(x) paste(x, collapse = " ")
        ),
        row.names = NULL,
        stringsAsFactors = FALSE
      )
    }
  )
  
  ############################################################
  ## Output
  ############################################################
  
  structure(
    list(
      Parameters = parameters,
      NonRandomized_Layout = NonRandomized_Layout,
      Randomized_Layout = Randomized_Layout
    ),
    class = "pRep3"
  )
  
}

#' Print p-Rep Test-Control Design: Method III
#'
#' @param x An object of class "pRep3".
#' @param ... Further arguments.
#'
#' @return The input object invisibly.
#'
#' @exportS3Method print pRep3
print.pRep3 <- function(x, ...) {
  
  cat("\nDesign Parameters\n\n")
  
  print(
    x$Parameters,
    row.names = FALSE
  )
  
  cat("\n----------------------------------------\n\n")
  
  cat("Non-Randomized Layout\n\n")
  
  for (i in names(x$NonRandomized_Layout)) {
    
    cat(
      sub("Environment_", "Environment ", i),
      "\n"
    )
    
    print(
      x$NonRandomized_Layout[[i]],
      row.names = FALSE
    )
    
    cat("\n")
  }
  
  cat("----------------------------------------\n\n")
  
  cat("Randomized Layout\n\n")
  
  for (i in names(x$Randomized_Layout)) {
    
    cat(
      sub("Environment_", "Environment ", i),
      "\n"
    )
    
    print(
      x$Randomized_Layout[[i]],
      row.names = FALSE
    )
    
    cat("\n")
  }
  
  invisible(x)
  
}

